Merge pull request #21 from emmanuelsrlok/develop

Develop
This commit is contained in:
Emmanuel HC
2025-10-01 11:23:26 -05:00
committed by GitHub
9 changed files with 686 additions and 7 deletions

View File

@@ -9,7 +9,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = PQM_PM9000_TCP ; Select here the name of the configuration you want to download
default_envs = Susol_Smart_MCCB_TCP ; Select here the name of the configuration you want to download
[env]
upload_port = COM15

View File

@@ -0,0 +1,33 @@
# EQUIPMENT_TYPE MANUFACTURER MODEL TCP
## Brief Introduction
Equipment specifc details that make it different from other devices
## List of Equipmentt
This cofiguration has been used for these models:
* **Model**: 09-15-22
* **Model**: 09-15-23
* **Model**: 09-15-25
## Hardware Prerequisites
The code is written for an ESP8266/ESP32-style microcontroller with WiFi capabilities.
* **Microcontroller**: [Firebeetle 2 ESP32.](https://www.dfrobot.com/product-2231.html)
---
## States and Strategies
Provide a brief description of what variables and strategies were used in this configuraiton
### Standby State
* **Equipment running**: set to 0
* **Common Alarm**: set to 0
* **SAT temperature**: set to 85
### Running State
* **Equipment running**: set to 1
* **SAT temperature**: **Ramp Strategy** set to 65 deg setpoint
### Fail State
* **Commong Alarm**: set to 1
* **SAT temperature**: **Ramp Strategy** set to 105 deg setpointset

View File

@@ -0,0 +1,92 @@
/**
* @file State_Fail.cpp
* @brief Implementation of the FailState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the FailState, which defines
* the behavior of the equipment when it has entered a fault condition.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new FailState object with a list of active alarms.
*
* This constructor receives a list of alarm descriptions and creates strategies
* to set the corresponding Modbus points to a value of 1, indicating an
* active alarm. It also initializes a PID strategy for the 'CW Valve Position'
* to maintain its state during the fault.
* @param activeAlarms A vector of strings, where each string is the
* description of a Modbus point to be set as an active alarm.
*/
template<>
FailState<ModbusIP>::FailState(const std::vector<std::string>& activeAlarms) {
// Simulate a failure: set common alarm and a specific fan alarm.
for (const auto& alarmName : activeAlarms){
addStrategy(alarmName, new SingleValueStrategy(1.0f, 0.0f, 1000));
}
addStrategy("CW Valve Position", new PIDStrategy("RAT Setpoint", 1000, "RAT"));
}
/**
* @brief Executes the fail state's logic for one update cycle.
*
* This method checks the "Alarm Reset" Modbus point for a command to
* transition back to Standby, which would typically happen after a fault
* is cleared by a user. If no transition is requested, it continues to apply
* the failure strategies (e.g., keeping alarm bits active).
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* FailState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Fail update function");
Modbus_Point<ModbusIP>* alarmReset = equipment->getModbus_Point("Alarm Reset");
int nextStateId = alarmReset ? alarmReset->getValue() : 0;
if (nextStateId == 1){
return new StandbyState<ModbusIP>();
}
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the fail state.
* Sets the "Alarm Common" point to 1 to indicate a general fault condition.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Fail State...");
Modbus_Point<ModbusIP>* alarm_common = equipment->getModbus_Point("Alarm Common");
alarm_common->setValue(1);
}
/**
* @brief Logic to execute once when exiting the fail state.
* Clears the "Alarm Common" point to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Fail State...");
Modbus_Point<ModbusIP>* alarm_common = equipment->getModbus_Point("Alarm Common");
alarm_common->setValue(0);
}

View File

@@ -0,0 +1,187 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Totalizer.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("CW Valve Position", new PIDStrategy("RAT Setpoint", 1000, "RAT"));
addStrategy("Operating Hours EC Fan #1", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #2", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #3", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #4", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #5", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #6", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #7", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #8", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #9", new TotalizerStrategy(10000));
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. It reads the "ON/OFF Command By BMS" point. If it's 0, it transitions to StandbyState.
* 2. It reads the "Fault Code" point. If it's non-zero, it transitions to FailState,
* passing the corresponding alarm description.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
Modbus_Point<ModbusIP>* On_Off_Command = equipment->getModbus_Point("ON/OFF Command By BMS");
int nextStateId = On_Off_Command ? On_Off_Command->getValue() : 0;
Serial.println(nextStateId);
if (nextStateId == 0){
return new StandbyState<ModbusIP>();
}
Modbus_Point<ModbusIP>* faultCode = equipment->getModbus_Point("Fault Code");
int faultCodeValue = faultCode ? faultCode->getValue() : 0;
switch (faultCodeValue){
case 1:
return new FailState<ModbusIP>({"Alarm SAT Sensor Fault"});
case 2:
return new FailState<ModbusIP>({"Alarm RAH Sensor Fault"});
case 3:
return new FailState<ModbusIP>({"Alarm RAT Sensor Fault"});
case 4:
return new FailState<ModbusIP>({"Alarm Filter DP Sensor Fault"});
case 5:
return new FailState<ModbusIP>({"Alarm Flooding"});
case 6:
return new FailState<ModbusIP>({"Alarm Dirty Filter"});
case 7:
return new FailState<ModbusIP>({"Alarm High RAT"});
case 8:
return new FailState<ModbusIP>({"Alarm Low RAT"});
case 9:
return new FailState<ModbusIP>({"Alarm High SAT"});
case 10:
return new FailState<ModbusIP>({"Alarm Low SAT"});
case 11:
return new FailState<ModbusIP>({"Alarm High RAH"});
case 12:
return new FailState<ModbusIP>({"Alarm Low RAH"});
case 13:
return new FailState<ModbusIP>({"Alarm Phase Failure"});
case 14:
return new FailState<ModbusIP>({"Alarm Condensate Pump"});
case 15:
return new FailState<ModbusIP>({"Alarm Smoke"});
case 16:
return new FailState<ModbusIP>({"Alarm Fire"});
case 17:
return new FailState<ModbusIP>({"Alarm EC Fan #1"});
case 18:
return new FailState<ModbusIP>({"Alarm EC Fan #2"});
case 19:
return new FailState<ModbusIP>({"Alarm EC Fan #3"});
case 20:
return new FailState<ModbusIP>({"Alarm EC Fan #4"});
case 21:
return new FailState<ModbusIP>({"Alarm EC Fan #5"});
case 22:
return new FailState<ModbusIP>({"Alarm EC Fan #6"});
case 23:
return new FailState<ModbusIP>({"Alarm EC Fan #7"});
case 24:
return new FailState<ModbusIP>({"Alarm EC Fan #8"});
case 25:
return new FailState<ModbusIP>({"Alarm EC Fan #9"});
default:
break;
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Run Status" for all EC fans to 1 to indicate they are active.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
const std::vector<std::string> motorStatusDescriptions = {
"Run Status EC Fan #1", "Run Status EC Fan #2", "Run Status EC Fan #3",
"Run Status EC Fan #4", "Run Status EC Fan #5", "Run Status EC Fan #6",
"Run Status EC Fan #7", "Run Status EC Fan #8", "Run Status EC Fan #9"
};
// Loop through and set all motor statuses to 0
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(1);
}
}
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
const std::vector<std::string> motorStatusDescriptions = {
"Run Status EC Fan #1", "Run Status EC Fan #2", "Run Status EC Fan #3",
"Run Status EC Fan #4", "Run Status EC Fan #5", "Run Status EC Fan #6",
"Run Status EC Fan #7", "Run Status EC Fan #8", "Run Status EC Fan #9"
};
// Loop through and set all motor statuses to 0
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(0);
}
}
}

View File

@@ -0,0 +1,135 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the StandbyState, which defines
* the behavior of the equipment when it is in an idle or standby mode.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new StandbyState object.
*
* In this state, the equipment is idle. This constructor initializes strategies
* to bring the system to a safe, idle condition. It sets a stable value for
* the SAT reading and creates ramp strategies to bring the CW valve and all
* EC fan speeds down to zero.
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
addStrategy("SAT Reading", new SingleValueStrategy(100.0f, 0.1f, 1000));
addStrategy("CW Valve Position", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #1", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #2", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #3", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #4", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #5", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #6", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #7", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #8", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Speed EC Fan #9", new RampStrategy(0.0f, 5.0f, 1000));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method applies the strategies defined for the standby state (e.g.,
* ramping values to zero).
*
* @warning This method currently does not check for a command to transition to the
* Running state. This logic needs to be added to allow the unit to start.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
int On_Off_Command = getPointValue(equipment, "ON/OFF Command By BMS");
Serial.printf("ON_OFF COmmand %f. \n", On_Off_Command);
if (On_Off_Command == 1){
return new RunningState<ModbusIP>();
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* This method performs cleanup by setting all alarm points and all EC fan
* run status points to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
// A list of all alarm descriptions
const std::vector<std::string> alarmDescriptions = {
"Alarm SAT Sensor Fault", "Alarm RAH Sensor Fault", "Alarm RAT Sensor Fault",
"Alarm Filter DP Sensor Fault", "Alarm Flooding", "Alarm Dirty Filter",
"Alarm High RAT", "Alarm Low RAT", "Alarm High SAT", "Alarm Low SAT",
"Alarm High RAH", "Alarm Low RAH", "Alarm Common", "Alarm Phase Failure",
"Alarm Condensate Pump", "Alarm Smoke", "Alarm Fire", "Alarm EC Fan #1",
"Alarm EC Fan #2", "Alarm EC Fan #3", "Alarm EC Fan #4", "Alarm EC Fan #5",
"Alarm EC Fan #6", "Alarm EC Fan #7", "Alarm EC Fan #8", "Alarm EC Fan #9"
};
// A list of all motor run status descriptions
const std::vector<std::string> motorStatusDescriptions = {
"Run Status EC Fan #1", "Run Status EC Fan #2", "Run Status EC Fan #3",
"Run Status EC Fan #4", "Run Status EC Fan #5", "Run Status EC Fan #6",
"Run Status EC Fan #7", "Run Status EC Fan #8", "Run Status EC Fan #9"
};
// Loop through and set all alarms to 0
for (const auto& desc : alarmDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(0);
}
}
// Loop through and set all motor statuses to 0
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(0);
}
}
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Standby State...");
}

View File

@@ -0,0 +1,142 @@
/**
* @file config.h
* @brief Main configuration file for the CRAH Unit (TCP) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 2014, 0, "Main Power Supply"}, //Read Only, Value x 10 (e.g. 2V = 20)
{HR, 4002, 0, "Heater SSR Stage"}, //Read Only, Value x 100 (e.g. 10% = 1000)
{HR, 6001, 0, "Control Input"}, //Read Only, Value x 100 (e.g. 10% = 1000)
{HR, 6005, 0, "Room RH"}, //Read Only, Value x 100 (e.g. 10% RH = 1000)
{HR, 6009, 0, "Supply High Limit RH"}, //Read Only, Value x 100 (e.g. 10% RH = 1000)
{HR, 6013, 0, "Water Temperature"}, //Read Only, Value x 100 (10ºF = 1000)
{HR, 6015, 0, "SSR Temperature"}, //Read Only, Value x 100 (10ºF = 1000)
{HR, 6016, 0, "Cabinet Temperature"}, //Read Only, Value x 100 (10ºF = 1000)
{HR, 6017, 0, "Current Sensor 1"}, //Read Only, Value x 100 (e.g. 10A = 1000)
{HR, 6018, 0, "Current Sensor 2"}, //Read Only, Value x 100 (e.g. 10A = 1000)
{HR, 6023, 0, "Power Output Feedback"}, //Read Only, Value x 100 (e.g. 10% = 1000)
{HR, 6024, 7800, "Water Level"}, //Read Only, Value x 100 (e.g. 10% = 1000)
{HR, 6029, 0, "Room RH Network Reading"}, //*Writable, Value x 100 (e.g. 10% RH= 1000) Room RH reading from PLC
{HR, 6030, 0, "Room RH Setpoint"}, //*Writable, Value x 100 (e.g. 10% RH= 1000) Room disired RH from PLC
{HR, 6036, 0, "Room Demand"}, //Read Only, Value x 100 (e.g. 10% RH = 1000)
{HR, 6037, 0, "Supply High Limit Network Reading"}, //Writable, Value x 100 (e.g. 10% RH= 1000)
{HR, 6038, 8000, "Supply High Limit Setpoint"}, //Writable, Value x 100 (e.g. 10% RH = 1000)
{HR, 6042, 0, "Supply High Limit Demand"}, //Read Only, Value x 100 (e.g. 10% RH = 1000)
{HR, 6043, 0, "Humidity Control Network Demand"}, //Writable, Value x 100 (e.g. 10% = 1000)
{HR, 6045, 0, "Humidity Demand"}, //Read Only, Value x 100 (e.g. 10% = 1000)
{HR, 6047, 0, "System Power Output"}, //Read Only, Value x 100 (e.g. 10% = 1000)
{HR, 6049, 0, "Boiler Demand"}, //Writable, Value x 100 (e.g. 10% = 1000)
{HR, 6051, 0, "Boiler Power Output"}, //*Read Only, Value x 100 (e.g. 10% = 1000)
{HR, 6052, 0, "Boiler Run Time"}, //Read Only, Hours (h), Value x 100 (e.g. 10 h = 1000)
{HR, 6056, 0, "Boiler On Time"}, //Read Only, Hours (h), Value x 100 (e.g. 10 h = 1000)
{HR, 6060, 0, "Boiler Service Run Time"}, //Read Only, Hours (h), Value x 100 (e.g. 10 h = 1000)
{HR, 6064, 0, "Boiler Service On Time"}, //Read Only, Hours (h), Value x 100 (e.g. 10 h = 1000)
{HR, 6084, 0, "Boiler Manual Cal Time"}, //Read Only, Hours (h), Value x 100 (e.g. 10 h = 1000)
{HR_10x, 1, 0, "Air Flow"}, //Read Only, 0=Closed, 1=Open
{HR_10x, 2, 0, "Supply High Limit"}, //Read Only, 0=Closed, 1=Open
{HR_10x, 3, 0, "Interlock"}, //Read Only, 0=Closed, 1=Open
{HR_10x, 5, 0, "Water Leak Detection"}, //Read Only, 0=Ok, 1=Leak
{HR_10x, 6, 0, "Thermal Cutout"}, //Read Only, 0=Closed, 1=Open
{HR_10x, 9, 0, "Contactors PCB Fuse"}, //Read Only, 0=Normal, 1=Blown Fuse
{HR_10x, 10, 0, "Control PCB Fuse"}, //Read Only, 0=Normal, 1=Blown Fuse
{HR_10x, 1001, 0, "Alarm Warning Relay"}, //Read Only, 0=Off, 1=On
{HR_10x, 1002, 0, "Service Warning Relay"}, //Read Only, 0=Off, 1=On
{HR_10x, 1003, 0, "Water Level Valve"}, //Read Only, 0=Off, 1=On
{HR_10x, 1004, 0, "Tank Water Valve"}, //Read Only, 0=Off, 1=On
{HR_10x, 1005, 0, "Drain Cooler Valve"}, //Read Only, 0=Off, 1=On
{HR_10x, 1006, 0, "Drain Pump"}, //Read Only, 0=Off, 1=On
{HR_10x, 1007, 0, "Drain Valve"}, //Read Only, 0=Off, 1=On
{HR_10x, 1008, 0, "Main Contactor"}, //Read Only, 0=Off, 1=On
{HR_10x, 1009, 0, "Heater Stage 1"}, //Read Only, 0=Off, 1=On
{HR_10x, 1010, 0, "Heater Stage 2"}, //Read Only, 0=Off, 1=On
{HR_10x, 1011, 0, "Heater Stage 3"}, //Read Only, 0=Off, 1=On
{HR_10x, 1012, 0, "SDU Fan"}, //Read Only, 0=Off, 1=On
{HR_10x, 1013, 0, "Alarm LED"}, //Read Only, 0=Off, 1=On
{HR_10x, 1014, 0, "Power LED"}, //Read Only, 0=Off, 1=On
{HR_10x, 1015, 0, "Buzzer"}, //Read Only, 0=Off, 1=On
{HR_10x, 2001, 0, "Manual Water Cal State"}, //Read Only, 0=Ok, 1=Required
{HR_10x, 2002, 0, "Water Level Low"}, //Read Only, 0=Inactive, 1=Active
{HR_10x, 2003, 0, "Water Level High"}, //Read Only, 0=Inactive, 1=Active
{HR_10x, 2004, 0, "Foam Sensor"}, //Read Only, 0=NoFoam, 1=Foam
{HR_10x, 2005, 0, "SDU Fan Fault"}, //Read Only, 0=Off, 1=On
{HR_10x, 2007, 0, "Boiler Service Due"}, //Read Only, 0=No, 1=Yes
{HR_10x, 2008, 0, "Foam"}, //Read Only, 0=Ok, 1=Detected
{HR_10x, 2016, 0, "AntiFreeze Warning"}, //Read Only, 0=Inactive, 1=Drain
{HR_10x, 5016, 0, "Humidity Control Cutout State"}, //Read Only, 0=Off, 1=Normal, 2=LowLimit, 3=HighLimit, 4=NoAirFlow, 5=Interlock
{HR_10x, 5018, 0, "Boiler Request"}, //Writable, 0=None, 1=Reset Alarms, 2=Drain, 3=Reset Counters, 4=Filling, 5=WaterCalib
{HR_10x, 5019, 0, "Boiler State"}, //Read Only, 0=Off, 1=Idle, 2=LineRinse, 3=TankRinse, 4=Filling, 5=Draining, 6=Heating, 7=Boiling, 8=Alarm
{HR_10x, 5021, 0, "Boiler Alarm"}, //Read Only, 0=Normal, 1=FailedPump, 2=FillTimeout, 3=BlockedPiping, 4=HeatTimeout, 5=Overheat, 6=WaterLeak, 7=Service, 9=TankBlocked, 10=RefillDelay
{HR_10x, 5025, 1, "System Power State"}, //*Writable, 0=Off, 1=On
{HR_10x, 5026, 0, "Water Level Probe Warning"}, //Read Only, 0=OK, 1=Replace
{HR_10x, 5027, 0, "Water Level Probe Failure"}, //Read Only, 0=None, 1=Capacitive, 2=Resistive, 3=Both
{HR_10x, 5028, 0, "Water Level Probe Alarm"} //Read Only, 0=OK, 1=Defect, 2=NoCalib
};
//Size of modbus map used in FOR cycles, automatically calculated.
/**
* @brief The total number of entries in the `mb_map` array.
* This is calculated at compile time and used for iterating over the map.
*/
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
/** @brief The main loop update interval in milliseconds. */
int interval = 250;
/** @} */ // End of ModbusMapConfig group
#endif // CONFIG_H

View File

@@ -0,0 +1,86 @@
/**
* @file main.cpp
* @brief Main execution program for the CRAH Unit (TCP) Emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* @details This file contains the main execution program for an Arduino-based emulator of a CRAH unit.
* The program uses a Wi-Fi connection to communicate via the Modbus IP protocol.
*
* The setup() function initializes the following:
* - Serial communication for debugging.
* - Wi-Fi connection using credentials from config.h.
* - A Modbus TCP server.
* - Modbus points (Coils, Holding Registers, etc.) based on a predefined map in config.h.
*
* The loop() function continuously:
* - Services the Modbus TCP server to handle incoming requests.
* - Periodically calls the main update loop for the emulated equipment, which
* manages state transitions and behavior strategies.
*
* @see config.h for Wi-Fi and Modbus configuration.
* @see Equipment.h for the main equipment logic.
* @see State.h for different equipment states.
* @see Strategies/Strategy_Behavior.h for value generation strategies.
* @see Modbus_Point.h for the base class for all Modbus points.
*/
//=================================================================================================================================
//Libraries and declaration of variables.
#include <WiFi.h>
#include "config.h"
#include "ModbusPoints/Modbus_PointFactory.h"
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
//=================================================================================================================================
/**
* @brief Initializes the application.
* @details This function runs once at startup. It configures the serial communication,
* Wi-Fi, and the Modbus server. It also creates and initializes all the Modbus points
* based on the `mb_map` array in `config.h`.
*/
void setup() {
Serial.begin(115200); //Serial comm start
WiFi.config(local_IP, gateway, subnet); // Wifi service start
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("Connected!!");
mb.server(); //Modbus server start
Serial.println("Server Created");
Serial.println(map_size);
for(int i = 0; i < map_size; i++){
Modbus_Point<ModbusIP>* point = createModbus_Point(&mb, mb_map[i].category, mb_map[i].address, mb_map[i].value, mb_map[i].description);
if (point) {
point->addToModbusServer();
EquipmentInstance.addModbus_Point(mb_map[i].description, point);
}
}
Serial.println("All modbus Points created");
Serial.println("Setup function ended");
}
//=================================================================================================================================
/**
* @brief The main application loop.
* @details This function runs repeatedly after setup() has completed. It performs two main actions:
* 1. It continuously services the Modbus server by calling `mb.task()` to handle
* incoming requests from a Modbus master.
* 2. At a fixed interval (defined in `config.h`), it calls `EquipmentInstance.update()`
* to run the emulator's internal state machine and behavior logic.
*/
void loop() {
mb.task();
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
unsigned long startTime = millis();
EquipmentInstance.update();
unsigned long endTime = millis();
unsigned long elapsedTime = endTime - startTime;
Serial.printf("Control Execution time: %d ms\n", elapsedTime);
}
}

View File

@@ -86,9 +86,13 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
float load = static_cast<float>(I_load);
float rating = static_cast<float>(I_rating);
float real_load = rating * (load/100.0f);
setPointValue(equipment, "Amps A", real_load);
setPointValue(equipment, "Amps B", real_load);
setPointValue(equipment, "Amps C", real_load);
Strategy_Behavior* ampsA_svs = getStrategy("Amps A");
Strategy_Behavior* ampsB_svs = getStrategy("Amps B");
Strategy_Behavior* ampsC_svs = getStrategy("Amps C");
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(real_load);
float pf = getPointValue(equipment, "PF");
@@ -112,7 +116,7 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "CB Position", 2048);
setPointValue(equipment, "CB Position", 4096);
}

View File

@@ -23,8 +23,8 @@
#include <ModbusIP_ESP8266.h>
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 30, 241); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 30, 1); /**< @brief The gateway IP address. */
IPAddress local_IP(172, 17, 33, 132); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;