Complied V2 version, ready to start testing and start developing GUI

This commit is contained in:
Emmanuel HC
2026-07-09 02:38:18 -05:00
parent c4a30b62f8
commit 9b68b219cd
447 changed files with 12911 additions and 29278 deletions

156
lib/Core/ConfigParser.h Normal file
View 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

View File

@@ -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.

View File

@@ -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>

View File

@@ -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
View 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