New folder structure for source

This commit is contained in:
2025-09-17 14:42:23 -05:00
parent 76082914f1
commit f120580ca0
16 changed files with 3 additions and 3 deletions

View File

@@ -0,0 +1,88 @@
/**
* @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 "States/State_Standby.h"
#include "States/State_Fail.h"
#include "ModbusPoints/Modbus_Point.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_PID.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new FailState object.
*
* 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 valve position.
*/
template<>
FailState<ModbusRTU>::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 "Clear Alm" 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.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* FailState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Fail update function");
Modbus_Point<ModbusRTU>* clearAlm = equipment->getModbus_Point("Clear Alm");
int nextStateId = clearAlm ? clearAlm->getValue() : 0;
if (nextStateId == 1){
return new StandbyState<ModbusRTU>();
}
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the fail state. Sets the main alarm bit.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Fail State...");
Modbus_Point<ModbusRTU>* alarm_common = equipment->getModbus_Point("Alarm Common");
alarm_common->setValue(1);
}
/**
* @brief Logic to execute once when exiting the fail state. Clears the main alarm bit.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Fail State...");
Modbus_Point<ModbusRTU>* alarm_common = equipment->getModbus_Point("Alarm Common");
alarm_common->setValue(0);
}

View File

@@ -0,0 +1,151 @@
/**
* @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 "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "Strategies/Strategy_Behavior.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Totalizer.h"
#include "Equipment/Equipment.h"
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.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<ModbusRTU>::RunningState() {
addStrategy("Actual Capacity", new PIDStrategy("Active SP", 1000, "Supply Temp"));
addStrategy("Comp1 Percent RLA", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Comp2 Percent RLA", new RampStrategy(0.0f, 5.0f, 1000));
}
/**
* @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<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
int CH_Enable_SP = getPointValue(equipment, "Chiller Enable SP");
if (CH_Enable_SP == 0){
return new StandbyState<ModbusRTU>();
}
// Declare currentSP outside the switch so it's accessible later.
float highCapacityLimit = getPointValue(equipment, "Active Capacity Limit");
Strategy_Behavior* PID_Strat = getStrategy("Actual Capacity");
static_cast<PIDStrategy*>(PID_Strat)->setLimits(0.0f, highCapacityLimit);
float currentSP = 50.0f; // Default value
int currentMode = getPointValue(equipment, "Chiller Mode Output");
// Determine the correct setpoint based on the current operating mode.
switch(currentMode){
case 1:
currentSP = getPointValue(equipment, "Ice SP");
currentSP = currentSP - 20;
break; // Added break to prevent fall-through
case 2:
currentSP = getPointValue(equipment, "Cool SP");
currentSP = currentSP + 20;
break; // Added break
default:
// The default value is already set.
break;
}
Strategy_Behavior* ramp_strategy1 = getStrategy("Comp1 Percent RLA");
Strategy_Behavior* ramp_strategy2 = getStrategy("Comp2 Percent RLA");
int actualCapacity = getPointValue(equipment, "Actual Capacity");
if (actualCapacity < 50){
actualCapacity = actualCapacity * 2;
if (actualCapacity > 100) actualCapacity = 100;
static_cast<RampStrategy*>(ramp_strategy1)->setTarget(actualCapacity);
static_cast<RampStrategy*>(ramp_strategy2)->setTarget(0);
} else {
if (actualCapacity > 100) actualCapacity = 100;
static_cast<RampStrategy*>(ramp_strategy1)->setTarget(actualCapacity);
int actualCapacity2 = (actualCapacity - 50)*4;
if (actualCapacity2 > 100) actualCapacity2 = 100;
static_cast<RampStrategy*>(ramp_strategy2)->setTarget(actualCapacity2);
}
// 1. Get the strategy by its name.
Strategy_Behavior* strategy = getStrategy("Actual Capacity");
// 2. Check if the strategy exists and is a PID type.
if (strategy && strategy->isPID()) {
// 3. Cast it to a PIDStrategy pointer and call setSetpoint.
static_cast<PIDStrategy*>(strategy)->setSetpoint(currentSP);
}
float OutdoorTemp = getPointValue(equipment, "Outdoor Air Temp");
Serial.printf("Outdoor Temp: %f\n", OutdoorTemp);
if (OutdoorTemp >50.0f) {
setPointValue(equipment, "Chiller Mode SP", 1.0f);
setPointValue(equipment, "Chiller Mode Output", 1.0f);
}else {
setPointValue(equipment, "Chiller Mode SP", 2.0f);
setPointValue(equipment, "Chiller Mode Output", 2.0f);
}
setPointValue(equipment, "Active SP", currentSP);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Chiller Sts" point to indicate the unit is running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::enterState(Equipment<ModbusRTU>* 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, "Run Enabled", 1);
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Chiller Sts" point to indicate the unit is no longer running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}

View File

@@ -0,0 +1,88 @@
/**
* @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 "States/State_Running.h"
#include "States/State_Fail.h"
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.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 several
* strategies to generate random values for various status points, simulating
* a live but non-operational unit.
*/
template<>
StandbyState<ModbusRTU>::StandbyState() {
addStrategy("Comp1 Percent RLA", new RampStrategy(0,5,1000));
addStrategy("Comp2 Percent RLA", new RampStrategy(0,5,1000));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method checks the "Chiller On-Off" Modbus point for a command to
* transition to the Running state. If no transition is requested, it applies
* the strategies defined for the standby state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* StandbyState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
int CH_Enable_SP = getPointValue(equipment, "Chiller Enable SP");
if (CH_Enable_SP == 1){
return new RunningState<ModbusRTU>();
}
float OutdoorTemp = getPointValue(equipment, "Outdoor Air Temp");
Serial.printf("Outdoor Temp: %f\n", OutdoorTemp);
if (OutdoorTemp >50.0f) {
setPointValue(equipment, "Chiller Mode SP", 1);
}else {
setPointValue(equipment, "Chiller Mode SP", 2);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* Sets the "Chiller Sts" point to indicate the unit is not running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Run Enabled", 0);
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Standby State...");
}

View File

@@ -0,0 +1,130 @@
/**
* @file config.h
* @brief Main configuration file for the Daikin Chiller (RTU) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains important configurations for the Modbus RTU communication
* and the specific register map for the emulated device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <ModbusRTU.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
/**
* @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, 100, 0, "State Control"}, //Internal to control from Modscan
{HR, 101, 0, "Fault Code"},
{HR_FLOAT, 102, 0, "Supply Temp"},
{HR, 0, 0, "Chiller Local-Network"},
{HR, 1, 0, "Chiller Enable Output"},
{HR, 2, 0, "Run Enabled"},
{HR, 3, 0, "Chiller Capacity Limited"},
{HR, 4, 0, "Alm Digital Output"},
{HR, 6, 0, "Evap Flow Switch Sts"},
{HR, 7, 0, "Cond Flow Switch Sts"},
{HR, 8, 0, "Chiller On-Off"},
{HR, 9, 0, "Chiller Enable SP"},
{HR, 10, 0, "Clear Alm"},
{HR, 11, 0, "Chiller Mode Output"},
{HR_10x, 12, 0, "Active SP"},
{HR_10x, 13, 0, "Actual Capacity"},
{HR_10x, 14, 0, "Active Capacity Limit"},
{HR, 15, 0, "Chiller Sts"},
{HR_10x, 16, 0, "Evap Entering Fluid Temp"},
{HR_10x, 17, 0, "Evap Leaving Fluid Temp"},
{HR, 18, 0, "Evap Fluid Flow Rate"},
{HR_10x, 19, 0, "Cond Entering Fluid Temp"},
{HR_10x, 20, 0, "Cond Leaving Fluid Temp"},
{HR, 21, 0, "Cond Fluid Flow Rate"},
{HR_10x, 24, 0, "Outdoor Air Temp"},
{HR, 25, 0, "Chiller Current"},
{HR, 27, 0, "Total Kw"},
{HR, 28, 0, "Warning Alm Idx"},
{HR, 29, 0, "Problem Alm Idx"},
{HR, 30, 0, "Fault Alm Idx"},
{HR, 31, 0, "Warning Alm Code"},
{HR, 32, 0, "Problem Alm Code"},
{HR, 33, 0, "Fault Alm Code"},
{HR, 34, 0, "Chiller Mode SP"},
{HR_10x, 35, 0, "Cool SP"},
{HR_10x, 36, 0, "Ice SP"},
{HR_10x, 38, 0, "Capacity Limit SP"},
{HR_10x, 39, 0, "Cond Refrig Pressure"},
{HR_10x, 40, 0, "Cond Saturated Refrig Temp"},
{HR_10x, 41, 0, "Evap Refrig Pressure"},
{HR_10x, 42, 0, "Evap Saturated Refrig Temp"},
{HR, 65, 0, "Comp Suction Refrig Temp"},
{HR_10x, 68, 0, "Comp Discharge Refrig Temp"},
{HR, 69, 0, "Comp1 Percent RLA"},
{HR, 70, 0, "Comp1 Current"},
{HR, 71, 0, "Comp Voltage"},
{HR, 72, 0, "Comp Power"},
{HR, 73, 0, "Comp Starts"},
{HR, 74, 0, "Comp Run Hours"},
{HR, 75, 0, "Comp Run Hours"},
{HR, 82, 0, "Comp2 Percent RLA"},
{HR, 303, 0, "Evap Pump Run Hours"},
{HR, 304, 0, "Evap Pump Run Hours"},
{HR, 305, 0, "Evap Pump Sts"},
{HR, 316, 0, "Units"},
{HR, 317, 0, "Chiller Model"},
{HR, 1849, 0, "Oil Feed Pessure"},
{HR, 1854, 0, "Wtrside Econo State"},
{HR, 1855, 0, "Wtrside Econo En SP"},
};
//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;
#endif // CONFIG_H

View File

@@ -0,0 +1,78 @@
/**
* @file main.cpp
* @brief Main execution program for the Daikin Chiller (RTU) Emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* @details This file contains the main execution program for an Arduino-based
* emulator of a Daikin Chiller unit. The program communicates via the
* Modbus RTU protocol over a serial connection.
*
* The setup() function initializes the following:
* - Serial communication for debugging.
* - A Modbus RTU server with parameters from config.h.
* - Modbus points (Coils, Holding Registers, etc.) based on a predefined map in config.h.
*
* The loop() function continuously:
* - Services the Modbus RTU 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 Modbus RTU and register map 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 <Arduino.h>
#include "config.h"
#include "ModbusPoints/Modbus_PointFactory.h"
//=================================================================================================================================
/**
* @brief Initializes the application.
* @details This function runs once at startup. It configures the serial communication
* for debugging and the Modbus RTU server. It then creates and initializes all
* the Modbus points based on the `mb_map` array in `config.h`.
*/
const int rtsPin = 4;
void setup() {
Serial.begin(115200);
Serial.println("Setup function started");
Serial2.begin(BAUDRATE, SERIAL_8N1, RX_PIN, TX_PIN);
mb.begin(&Serial2, RST_PIN); // Start the server
mb.slave(MODBUS_ID); // Set the slave ID
for(int i = 0; i < map_size; i++){
Modbus_Point<ModbusRTU>* 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("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

@@ -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,152 @@
/**
* @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, 15, 0, "State Control"}, //Internal to control from Modscan
{HR, 16, 0, "Fault Code"},
{HR_FLOAT, 18, 0, "RAT"}, //Internal Fault code from Modscan
{HR_FLOAT, 1, 0, "SAT Setpoint"},
{HR_FLOAT, 681, 0, "RAT Setpoint"},
{HR_FLOAT, 111, 0, "High RAT Limit"},
{HR_FLOAT, 114, 0, "Low RAT Limit"},
{HR_FLOAT, 118, 0, "High SAT Limit"},
{HR_FLOAT, 122, 0, "Low SAT Limit"},
{HR_FLOAT, 685, 0, "High RAH Limit"},
{HR_FLOAT, 689, 0, "Low RAH Limit"},
{HR, 5, 0, "Setting the EC Fan Max Speed"},
{HR, 695, 0, "Setting the EC Fan Min Speed"},
{HR_FLOAT, 693, 0, "Setting Room Temp"},
{HR, 691, 0, "Setting EC Fan Speed "},
{DI, 146, 0, "Alarm SAT Sensor Fault"},
{DI, 1246, 0, "Alarm RAH Sensor Fault"},
{DI, 1245, 0, "Alarm RAT Sensor Fault"},
{DI, 1250, 0, "Alarm Filter DP Sensor Fault"},
{DI, 51, 0, "Alarm Flooding"},
{DI, 1096, 0, "Alarm Dirty Filter"},
{DI, 1367, 0, "Alarm High RAT"},
{DI, 1099, 0, "Alarm Low RAT"},
{DI, 118, 0, "Alarm High SAT"},
{DI, 122, 0, "Alarm Low SAT"},
{DI, 1307, 0, "Alarm High RAH"},
{DI, 1308, 0, "Alarm Low RAH"},
{DI, 1342, 0, "Alarm Common"},
{DI, 148, 0, "Alarm Phase Failure"},
{DI, 1370, 0, "Alarm Condensate Pump"},
{DI, 1368, 0, "Alarm Smoke"},
{DI, 1369, 0, "Alarm Fire"},
{DI, 131, 0, "Alarm EC Fan #1"},
{DI, 132, 0, "Alarm EC Fan #2"},
{DI, 133, 0, "Alarm EC Fan #3"},
{DI, 134, 0, "Alarm EC Fan #4"},
{DI, 135, 0, "Alarm EC Fan #5"},
{DI, 136, 0, "Alarm EC Fan #6"},
{DI, 1360, 0, "Alarm EC Fan #7"},
{DI, 1361, 0, "Alarm EC Fan #8"},
{DI, 1362, 0, "Alarm EC Fan #9"},
{DI, 138, 0, "Run Status EC Fan #1"},
{DI, 139, 0, "Run Status EC Fan #2"},
{DI, 140, 0, "Run Status EC Fan #3"},
{DI, 141, 0, "Run Status EC Fan #4"},
{DI, 142, 0, "Run Status EC Fan #5"},
{DI, 143, 0, "Run Status EC Fan #6"},
{DI, 1363, 0, "Run Status EC Fan #7"},
{DI, 1364, 0, "Run Status EC Fan #8"},
{DI, 1365, 0, "Run Status EC Fan #9"},
{IR_FLOAT, 99, 0, "SAT Reading"},
{IR_FLOAT, 70, 0, "RAH Reading"},
{IR_FLOAT, 101, 0, "RAT Reading"},
{IR_FLOAT, 106, 0, "Filter DP Reading"},
{IR_FLOAT, 496, 0, "CW Valve Position"},
{IR, 53, 0, "Speed EC Fan #1"},
{IR, 228, 0, "Speed EC Fan #2"},
{IR, 229, 0, "Speed EC Fan #3"},
{IR, 230, 0, "Speed EC Fan #4"},
{IR, 231, 0, "Speed EC Fan #5"},
{IR, 232, 0, "Speed EC Fan #6"},
{IR, 678, 0, "Speed EC Fan #7"},
{IR, 679, 0, "Speed EC Fan #8"},
{IR, 680, 0, "Speed EC Fan #9"},
{IR, 274, 0, "Operating Hours EC Fan #1"},
{IR, 233, 0, "Operating Hours EC Fan #2"},
{IR, 244, 0, "Operating Hours EC Fan #3"},
{IR, 235, 0, "Operating Hours EC Fan #4"},
{IR, 236, 0, "Operating Hours EC Fan #5"},
{IR, 245, 0, "Operating Hours EC Fan #6"},
{IR, 486, 0, "Operating Hours EC Fan #7"},
{IR, 487, 0, "Operating Hours EC Fan #8"},
{IR, 488, 0, "Operating Hours EC Fan #9"},
{COIL, 301, 0, "ON/OFF Command By BMS"},
{COIL, 302, 0, "Enable Off By Supervisory"},
{COIL, 264, 0, "Alarm Reset"}
};
//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

@@ -0,0 +1,77 @@
/**
* @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 "States/State_Standby.h"
#include "States/State_Fail.h"
#include "ModbusPoints/Modbus_Point.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_PID.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new FailState object.
*
* 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 valve position.
*/
template<>
FailState<ModbusRTU>::FailState(const std::vector<std::string>& activeAlarms) {
// Simulate a failure: set common alarm and a specific fan alarm.
}
/**
* @brief Executes the fail state's logic for one update cycle.
*
* This method checks the "Clear Alm" 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.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* FailState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Fail update function");
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the fail state. Sets the main alarm bit.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Fail State...");
}
/**
* @brief Logic to execute once when exiting the fail state. Clears the main alarm bit.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Fail State...");
}

View File

@@ -0,0 +1,131 @@
/**
* @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 "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "Strategies/Strategy_Behavior.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Totalizer.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Equipment/Equipment.h"
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.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<ModbusRTU>::RunningState() {
//Add strategies
//addStrategy("Actual Capacity", new PIDStrategy("Active SP", 1000, "Supply Temp"));
addStrategy("Output Frequency", new SingleValueStrategy(60.0f, 0.2, 1000 ));
addStrategy("Output Voltage", new SingleValueStrategy(600.0f, 10, 1000 ));
addStrategy("Motor Speed Used", new RampStrategy(0.0f, 5.0f, 1000 ));
addStrategy("Motor Speed estimated", new RampStrategy(0.0f, 100.0f, 1000 ));
addStrategy("Motor Current", new RampStrategy(0.0f, 9.0f, 1000 ));
addStrategy("Motor Torque", new RampStrategy(0.0f, 10.0f, 1000 ));
addStrategy("Hours Run", new TotalizerStrategy(1000));
addStrategy("Inverter Temperature", new RampStrategy(120.0f, 0.5f, 1000 ));
}
/**
* @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<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
int VFD_Start_Stop = getPointValue(equipment, "Start/Stop");
if (VFD_Start_Stop == 0){
return new StandbyState<ModbusRTU>();
}
float currentSP = getPointValue(equipment, "Speed Cmd");
Strategy_Behavior* motorstrategy = getStrategy("Motor Speed Used");
// 2. Check if the strategy exists and is a PID type.
if (motorstrategy) {
// 3. Cast it to a PIDStrategy pointer and call setSetpoint.
static_cast<RampStrategy*>(motorstrategy)->setTarget(currentSP);
}
float rpm = currentSP *200;
Strategy_Behavior* speedstrategy = getStrategy("Motor Speed estimated");
// 2. Check if the strategy exists and is a PID type.
if (speedstrategy) {
// 3. Cast it to a PIDStrategy pointer and call setSetpoint.
static_cast<RampStrategy*>(speedstrategy)->setTarget(rpm);
}
float current = currentSP *0.9;
Strategy_Behavior* currentstrategy = getStrategy("Motor Current");
// 2. Check if the strategy exists and is a PID type.
if (currentstrategy) {
// 3. Cast it to a PIDStrategy pointer and call setSetpoint.
static_cast<RampStrategy*>(currentstrategy)->setTarget(current);
}
float torque = currentSP * 372.85f;
Strategy_Behavior* torquestrategy = getStrategy("Motor Torque");
// 2. Check if the strategy exists and is a PID type.
if (torquestrategy) {
// 3. Cast it to a PIDStrategy pointer and call setSetpoint.
static_cast<RampStrategy*>(torquestrategy)->setTarget(torque);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Chiller Sts" point to indicate the unit is running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Chiller Sts" point to indicate the unit is no longer running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
setPointValue(equipment, "Output Frequency", 0.0f);
}

View File

@@ -0,0 +1,88 @@
/**
* @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 "States/State_Running.h"
#include "States/State_Fail.h"
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_SingleValue.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 several
* strategies to generate random values for various status points, simulating
* a live but non-operational unit.
*/
template<>
StandbyState<ModbusRTU>::StandbyState() {
addStrategy("Output Frequency", new SingleValueStrategy(0.1f, 0.2f, 1000 ));
addStrategy("Output Voltage", new SingleValueStrategy(0.1f, 0.1f, 1000 ));
addStrategy("Motor Speed Used", new RampStrategy(0.0f, 10.0f, 1000 ));
addStrategy("Motor Speed estimated", new RampStrategy(0.0f, 200.0f, 1000 ));
addStrategy("Motor Current", new RampStrategy(0.0f, 20.0f, 1000 ));
addStrategy("Motor Torque", new RampStrategy(0.0f, 20.0f, 1000 ));
addStrategy("Inverter Temperature", new RampStrategy(120.0f, 0.5f, 1000 ));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method checks the "Chiller On-Off" Modbus point for a command to
* transition to the Running state. If no transition is requested, it applies
* the strategies defined for the standby state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* StandbyState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
int VFD_Start_Stop = getPointValue(equipment, "Start/Stop");
if (VFD_Start_Stop == 1){
return new RunningState<ModbusRTU>();
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* Sets the "Chiller Sts" point to indicate the unit is not running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
}

View File

@@ -0,0 +1,99 @@
/**
* @file config.h
* @brief Main configuration file for the Daikin Chiller (RTU) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains important configurations for the Modbus RTU communication
* and the specific register map for the emulated device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <ModbusRTU.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
/**
* @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, 149, 0, "Speed Cmd"},
{HR, 151, 0, "Start/Stop"},
{HR, 100, 0, "Motor Speed Used"},
{HR, 101, 0, "Motor Speed estimated"},
{HR_10x, 105, 0, "Output Frequency"},
{HR_10x, 106, 0, "Motor Current"},
{HR_10x, 109, 0, "Motor Torque"},
{HR_10x, 110, 0, "DC Voltage"},
{HR, 112, 0, "Output Voltage"},
{HR_10x, 113, 0, "Output Power"}, //max 372580
{HR_10x, 119, 0, "Inverter kWh cnt"},
{HR, 502, 0, "Hours Run"},
{HR_10x, 510, 0, "Inverter Temperature"},
{HR, 521, 0, "HOA Status Word"},
{HR, 410, 0, "Last Fault"},
{HR, 411, 0, "2nd to last Fault"},
{HR, 412, 0, "3rd to last Fault"},
{HR, 439, 0, "Event Word Param"},
{HR, 610, 0, "Status Word 1"},
{HR, 615, 0, "Status Word 2"},
{HR, 616, 0, "Status Word 3"},
{HR, 617, 0, "Status Word 4"},
{HR, 618, 0, "Status Word 5"},
{HR, 619, 0, "Status Word 6"},
{HR, 620, 0, "Status Word 7"},
{HR, 621, 0, "Status Word 8"},
};
//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;
#endif // CONFIG_H

View File

@@ -0,0 +1,78 @@
/**
* @file main.cpp
* @brief Main execution program for the Daikin Chiller (RTU) Emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* @details This file contains the main execution program for an Arduino-based
* emulator of a Daikin Chiller unit. The program communicates via the
* Modbus RTU protocol over a serial connection.
*
* The setup() function initializes the following:
* - Serial communication for debugging.
* - A Modbus RTU server with parameters from config.h.
* - Modbus points (Coils, Holding Registers, etc.) based on a predefined map in config.h.
*
* The loop() function continuously:
* - Services the Modbus RTU 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 Modbus RTU and register map 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 <Arduino.h>
#include "config.h"
#include "ModbusPoints/Modbus_PointFactory.h"
//=================================================================================================================================
/**
* @brief Initializes the application.
* @details This function runs once at startup. It configures the serial communication
* for debugging and the Modbus RTU server. It then creates and initializes all
* the Modbus points based on the `mb_map` array in `config.h`.
*/
const int rtsPin = 4;
void setup() {
Serial.begin(115200);
Serial.println("Setup function started");
Serial2.begin(BAUDRATE, SERIAL_8N1, RX_PIN, TX_PIN);
mb.begin(&Serial2, RST_PIN); // Start the server
mb.slave(MODBUS_ID); // Set the slave ID
for(int i = 0; i < map_size; i++){
Modbus_Point<ModbusRTU>* 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("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);
}
}