Complied V2 version, ready to start testing and start developing GUI
This commit is contained in:
156
lib/Core/ConfigParser.h
Normal file
156
lib/Core/ConfigParser.h
Normal file
@@ -0,0 +1,156 @@
|
||||
#ifndef CONFIG_PARSER_H
|
||||
#define CONFIG_PARSER_H
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#include <ModbusRTU.h>
|
||||
// Core inclusion
|
||||
#include "Equipment/Equipment.h"
|
||||
#include "ModbusPoints/Modbus_Point.h"
|
||||
#include "ModbusPoints/Modbus_PointFactory.h"
|
||||
#include "Storage.h"
|
||||
|
||||
// Current Strategies inclusions
|
||||
#include "Strategies/Strategy_Behavior.h"
|
||||
#include "Strategies/Strategy_Ramp.h"
|
||||
#include "Strategies/Strategy_Random.h"
|
||||
#include "Strategies/Strategy_Saw.h"
|
||||
#include "Strategies/Strategy_Square.h"
|
||||
#include "Strategies/Strategy_SingleValue.h"
|
||||
|
||||
class ConfigParser {
|
||||
private:
|
||||
// Create the Equipment generic instance using ModbusIP as default
|
||||
// Note: The original Equipment code use templates, it is possible to instantiate with ModbusIP
|
||||
static Equipment<ModbusIP>* _equipmentInstance;
|
||||
static WiFiServer* _pythonServer;
|
||||
static int _pythonPort;
|
||||
static int _modeIsTCP;
|
||||
public:
|
||||
|
||||
static Equipment<ModbusIP>* getEquipmentInstance() {
|
||||
if (_equipmentInstance == nullptr) {
|
||||
_equipmentInstance = new Equipment<ModbusIP>();
|
||||
}
|
||||
return _equipmentInstance;
|
||||
}
|
||||
/**
|
||||
* @brief Start the TCP socket server to listen Python though WiFi
|
||||
*/
|
||||
static void initPythonTCPServer(int port = 8888){
|
||||
_pythonPort = port;
|
||||
_pythonServer = new WiFiServer(_pythonPort);
|
||||
_pythonServer->begin();
|
||||
Serial.print(F("[ConfigParser] Python server started on port: "));
|
||||
Serial.println(_pythonPort);
|
||||
}
|
||||
/**
|
||||
* @brief Check if Python send Json through Wi-Fi and apply
|
||||
*/
|
||||
static void checkPythonTCPClient(ModbusIP& mbIP, ModbusRTU& mbRTU, bool* modeIsTCP){
|
||||
if (_pythonServer==nullptr) return;
|
||||
WiFiClient client = _pythonServer->available();
|
||||
if (client) {
|
||||
Serial.println(F("[ConfigParser] Python connected over Wi-Fi..."));
|
||||
String jsonConfig = client.readString();
|
||||
// Apply configuration if it is a valid JSON
|
||||
apply(jsonConfig, mbIP, mbRTU, modeIsTCP);
|
||||
// Store backup configuration in LittleFS
|
||||
Storage::saveConfigFile(jsonConfig);
|
||||
client.println("OK");
|
||||
client.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main fucntion: Deserialize the Json and configure Modbus and strategies
|
||||
*/
|
||||
static void apply(const String& jsonConfig, ModbusIP& mbIP, ModbusRTU& mbRTU, bool* modeIsTCP) {
|
||||
Serial.println(F("[ConfigParser] Apply new configuration..."));
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, jsonConfig);
|
||||
if (error) {
|
||||
Serial.print(F("[ConfigParser] Deserialize Json Error: "));
|
||||
Serial.println(error.f_str());
|
||||
return;
|
||||
}
|
||||
// 1. Clean Hardware and RAM: Clear all Modbus points and strategies in the Equipment instance
|
||||
Equipment<ModbusIP>* eq = getEquipmentInstance();
|
||||
eq->clearEquipment();
|
||||
// 2. Network and Modbus protocol configuration.
|
||||
if (doc.containsKey("connection")){
|
||||
JsonObject connection = doc["connection"];
|
||||
String type = connection["type"] | "TCP";
|
||||
if (type.equalsIgnoreCase("TCP")){
|
||||
*modeIsTCP = true;
|
||||
int port = connection["port"] | 502;
|
||||
mbIP.server(port);
|
||||
Serial.print(F("[ConfigParser] Modbus TCP server started in port: "));
|
||||
Serial.println(port);
|
||||
} else {
|
||||
*modeIsTCP = false;
|
||||
int slaveId = connection["slave_id"] | 1;
|
||||
mbRTU.server(slaveId);
|
||||
Serial.print(F("[ConfigParser] Modbus RTU server started in slave ID: "));
|
||||
Serial.println(slaveId);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Rebuild the tags and strategies.
|
||||
if (doc.containsKey("points")){
|
||||
JsonArray pointsArray = doc["points"];
|
||||
Serial.print(F("[ConfigParser] Mapping requester registers: "));
|
||||
Serial.println(pointsArray.size());
|
||||
|
||||
for(JsonObject p : pointsArray){
|
||||
int address = p["address"];
|
||||
int category = p["cateogry"];
|
||||
int initVal = p["init_val"] | 0;
|
||||
const char* desc = p["desc"] | "Empty_Desc";
|
||||
|
||||
Modbus_Point<ModbusIP>* newPoint = nullptr;
|
||||
|
||||
if (*modeIsTCP) {
|
||||
newPoint = createModbus_Point(&mbIP, category, address, initVal, desc);
|
||||
} else {
|
||||
newPoint = createModbus_Point((ModbusIP*)&mbRTU, category, address, initVal, desc);
|
||||
}
|
||||
if (newPoint != nullptr) {
|
||||
newPoint->addToModbusServer();
|
||||
|
||||
if (p.containsKey("strategy")){
|
||||
const char* stratType = p["strategy"];
|
||||
unsigned long interval = p["interval"] | 1000;
|
||||
|
||||
Strategy_Behavior* strategy = nullptr;
|
||||
if (strcmp(stratType, "ramp") == 0) {
|
||||
strategy = new RampStrategy((float)(p["target"] | 0.0f), (float)(p["step"] | 100.0f), (int)(p["interval"] | 1000));
|
||||
}
|
||||
else if (strcmp(stratType, "random") == 0) {
|
||||
strategy = new RandomStrategy(interval);
|
||||
}
|
||||
else if (strcmp(stratType, "saw") == 0) {
|
||||
strategy = new SawStrategy((float)(p["min"] | 0.0f), (float)(p["max"] | 100.0f), (float)(p["step"] | 5.0f), (int)(p["interval"] | 1000));
|
||||
}
|
||||
else if (strcmp(stratType, "square") == 0) {
|
||||
strategy = new SquareStrategy((float)(p["lower"] | 0.0f), (float)(p["upper"] | 100.0f), (int)(p["interval"] | 1000));
|
||||
}
|
||||
else if (strcmp(stratType, "single") == 0 || strcmp(stratType, "none") == 0) {
|
||||
strategy = new SingleValueStrategy((float)(p["setpoint"] | 50.0f), (float)(p["noise"] | 0.5f), (int)(p["interval"] | 1000));
|
||||
}
|
||||
}
|
||||
eq->addModbus_Point(desc, newPoint);
|
||||
}
|
||||
}
|
||||
Serial.println(F("[ConfigParser] Tag deployment completed successfully."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline Equipment<ModbusIP>* ConfigParser::_equipmentInstance = nullptr;
|
||||
inline WiFiServer* ConfigParser::_pythonServer = nullptr;
|
||||
inline int ConfigParser::_modeIsTCP = true;
|
||||
inline int ConfigParser::_pythonPort = 8888;
|
||||
#endif
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "ModbusPoints/Modbus_Point.h"
|
||||
#include "States/State_Standby.h"
|
||||
|
||||
// Forward Declarations
|
||||
@@ -36,6 +37,7 @@ public:
|
||||
* Initializes the device in the default initial state (Standby).
|
||||
*/
|
||||
Equipment();
|
||||
~Equipment();
|
||||
|
||||
Equipment(T* server);
|
||||
/** @brief The main update loop for the equipment, called repeatedly. Delegates to the current state. */
|
||||
@@ -80,6 +82,9 @@ public:
|
||||
*/
|
||||
void setModbus_Point(const std::string& description, float value);
|
||||
|
||||
void updateAllPoints();
|
||||
void clearEquipment();
|
||||
|
||||
private:
|
||||
State<T>* _state; /**< @brief Pointer to the current state object. */
|
||||
std::map<std::string, Modbus_Point<T>*> _points;/**< @brief Map of all Modbus points, keyed by description. */
|
||||
@@ -89,20 +94,22 @@ private:
|
||||
|
||||
template<typename T>
|
||||
Equipment<T>::Equipment() : _server(nullptr) {
|
||||
this->_state = new StandbyState<T>();
|
||||
this->_state->enterState(this);
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Equipment<T>::~Equipment() {
|
||||
clearEquipment();}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructs a new Equipment object with a server instance.
|
||||
* @param server Pointer to the Modbus server instance (e.g., ModbusIP, ModbusRTU).
|
||||
*/
|
||||
template<typename T>
|
||||
Equipment<T>::Equipment(T* server)
|
||||
: _server(server)
|
||||
: _server(server), _state(nullptr), _stateId(0)
|
||||
{
|
||||
this->_state = new StandbyState<T>();
|
||||
this->_state->enterState(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,6 +169,28 @@ Modbus_Point<T>* Equipment<T>::getModbus_Point(const std::string& description) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Equipment<T>::updateAllPoints() {
|
||||
unsigned long currentMillis = millis();
|
||||
// Iterate safely though the map of points and call updateStrategy on each one
|
||||
for (auto const& [description, point] : _points) {
|
||||
if (point != nullptr) {
|
||||
point->updateStrategy(currentMillis); //Each point execute its own strategy update
|
||||
}
|
||||
}
|
||||
}
|
||||
template<typename T>
|
||||
void Equipment<T>::clearEquipment() {
|
||||
// Delete all Modbus points
|
||||
for (auto const& [description, point] : _points) {
|
||||
if (point != nullptr) {
|
||||
delete point;
|
||||
}
|
||||
}
|
||||
_points.clear();
|
||||
Serial.println(F("[Equipment] Modbus memory map successfully cleared."));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the value of a specific Modbus point.
|
||||
* Finds the point by its description and calls its `setValue` method.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#ifndef Modbus_Point_h
|
||||
#define Modbus_Point_h
|
||||
#include <string.h>
|
||||
#include "Strategies/Strategy_Behavior.h"
|
||||
/**
|
||||
* @enum PointType
|
||||
* @brief Identifies the logical type of a Modbus point.
|
||||
@@ -68,7 +69,18 @@ public:
|
||||
virtual int getValue() const = 0;
|
||||
|
||||
// --- Dirty Flag ---
|
||||
void setStrategy(Strategy_Behavior* strategy) {
|
||||
if (_strategy != nullptr) delete _strategy;
|
||||
_strategy = strategy;
|
||||
}
|
||||
|
||||
void updateStrategy(unsigned long currentMillis) {
|
||||
if (_strategy != nullptr && _strategy->isReady(currentMillis)) {
|
||||
float currentVal = this->getValue();
|
||||
float newVal = _strategy->execute(currentVal);
|
||||
this->setValue(newVal);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the point's value has changed since the last write.
|
||||
* @return True if the value is dirty, false otherwise.
|
||||
@@ -88,7 +100,8 @@ protected:
|
||||
int _address; /**< @brief The Modbus address of this point. */
|
||||
int _value; /**< @brief The current internal value of this point. */
|
||||
char _description[35]; /**< @brief A descriptive name for this point. */
|
||||
bool _dirty = false; /**< @brief Flag to track if the value has changed and needs to be written. */
|
||||
bool _dirty = false;
|
||||
Strategy_Behavior* _strategy = nullptr; /**< @brief Flag to track if the value has changed and needs to be written. */
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -19,8 +19,20 @@
|
||||
#include "Modbus_LongDecorator.h"
|
||||
#include "Modbus_FloatDecorator.h"
|
||||
#include <Arduino.h>
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
|
||||
|
||||
enum ModbusCategory {
|
||||
COIL = 1,
|
||||
DI = 2,
|
||||
IR = 3,
|
||||
IR_10x = 31,
|
||||
IR_LONG = 32,
|
||||
IR_FLOAT = 33,
|
||||
HR = 4,
|
||||
HR_10x = 41,
|
||||
HR_LONG = 42,
|
||||
HR_FLOAT = 43
|
||||
};
|
||||
/**
|
||||
* @brief Creates and decorates a Modbus_Point object based on its type.
|
||||
*
|
||||
@@ -55,56 +67,57 @@ Modbus_Point<T>* createModbus_Point(T* server, int category, int address, int va
|
||||
*/
|
||||
template<typename T>
|
||||
Modbus_Point<T>* createModbus_Point(T* server, int category, int address, int value, const char* description) {
|
||||
ModbusCategory mc = static_cast<ModbusCategory>(category);
|
||||
switch (category) {
|
||||
case COIL:
|
||||
case 1:
|
||||
Serial.printf("Creating Coil: %s\n", description);
|
||||
return new Modbus_Coil<T>(server, address, value, description);
|
||||
|
||||
case DI:
|
||||
case 2:
|
||||
Serial.printf("Creating Digital Input: %s\n", description);
|
||||
return new Modbus_Ists<T>(server, address, value, description);
|
||||
|
||||
case IR:
|
||||
case 3:
|
||||
Serial.printf("Creating Input Register: %s\n", description);
|
||||
return new Modbus_Ireg<T>(server, address, value, description);
|
||||
|
||||
case IR_10x: {
|
||||
case 31: {
|
||||
Serial.printf("Creating Scaled Input Register (10x): %s\n", description);
|
||||
Modbus_Point<T>* point = new Modbus_Ireg<T>(server, address, value, description);
|
||||
return new Modbus_ScaleDecorator<T>(point);
|
||||
}
|
||||
case IR_LONG: {
|
||||
case 32: {
|
||||
Serial.printf("Creating Input Register Long: %s\n", description);
|
||||
// Create the low and high word registers for the 32-bit long
|
||||
Modbus_Point<T>* point = new Modbus_Ireg<T>(server, address, 0, description);
|
||||
Modbus_Point<T>* highOrderPoint = new Modbus_Ireg<T>(server, address + 1, 0, "");
|
||||
return new Modbus_LongDecorator<T>(point, highOrderPoint);
|
||||
}
|
||||
case IR_FLOAT: {
|
||||
case 33: {
|
||||
Serial.printf("Creating Input Register Float: %s\n", description);
|
||||
// Create the low and high word registers for the 32-bit float
|
||||
Modbus_Point<T>* point = new Modbus_Ireg<T>(server, address, 0, description);
|
||||
Modbus_Point<T>* highOrderPoint = new Modbus_Ireg<T>(server, address + 1, 0, "");
|
||||
return new Modbus_FloatDecorator<T>(point, highOrderPoint);
|
||||
}
|
||||
case HR:
|
||||
case 4:
|
||||
Serial.printf("Creating Holding Register: %s\n", description);
|
||||
return new Modbus_Hreg<T>(server, address, value, description);
|
||||
|
||||
case HR_10x: {
|
||||
case 41: {
|
||||
Serial.printf("Creating Scaled Holding Register (10x): %s\n", description);
|
||||
// Create a base holding register and wrap it with the scaling decorator
|
||||
Modbus_Point<T>* point = new Modbus_Hreg<T>(server, address, value, description);
|
||||
return new Modbus_ScaleDecorator<T>(point);
|
||||
}
|
||||
case HR_LONG: {
|
||||
case 42: {
|
||||
Serial.printf("Creating Holding Register Long: %s\n", description);
|
||||
// Create the low and high word registers for the 32-bit long
|
||||
Modbus_Point<T>* point = new Modbus_Hreg<T>(server, address, 0, description);
|
||||
Modbus_Point<T>* highOrderPoint = new Modbus_Hreg<T>(server, address + 1 , 0, "");
|
||||
return new Modbus_LongDecorator<T>(point, highOrderPoint);
|
||||
}
|
||||
case HR_FLOAT: {
|
||||
case 43: {
|
||||
Serial.printf("Creating Holding Register Float: %s\n", description);
|
||||
// Create the low and high word registers for the 32-bit float
|
||||
Modbus_Point<T>* point = new Modbus_Hreg<T>(server, address, 0, description);
|
||||
|
||||
41
lib/Core/Storage.h
Normal file
41
lib/Core/Storage.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef STORAGE_H
|
||||
#define STORAGE_H
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h> //Built-in ESP32 library for file system management
|
||||
|
||||
class Storage {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialice LittleFS and read configuration file.
|
||||
* @return String with the JSON content or an empty string if the file does not exist or cannot be read.
|
||||
*/
|
||||
static String readConfigFile() {
|
||||
if(!LittleFS.begin(true)) {
|
||||
Serial.println("[Storage] Error to initialize LittleFS");
|
||||
return "";
|
||||
}
|
||||
if (!LittleFS.exists("/config.json")) {
|
||||
Serial.println("[Storage] Configuration file does not exist");
|
||||
return "";
|
||||
}
|
||||
File file = LittleFS.open("/config.json", "r");
|
||||
if (!file) {
|
||||
Serial.println("[Storage] Error opening configuration file");
|
||||
return "";
|
||||
}
|
||||
String content = file.readString();
|
||||
file.close();
|
||||
return content;
|
||||
}
|
||||
static void saveConfigFile(const String& jsonConfig) {
|
||||
File file = LittleFS.open("/config.json", "w");
|
||||
if (!file) {
|
||||
Serial.println("[Storage] Error opening configuration file for writing");
|
||||
return;
|
||||
}
|
||||
file.print(jsonConfig);
|
||||
file.close();
|
||||
Serial.println("[Storage] Configuration file saved");
|
||||
}
|
||||
};
|
||||
#endif
|
||||
Reference in New Issue
Block a user