Merge pull request #37 from emmanuelsrlok/rdavis/PHX3_CRAH_LIEBERT_80_SLAB_TCP

Added PHX3 EG CRAH
This commit is contained in:
Emmanuel HC
2025-11-04 08:27:39 -06:00
committed by GitHub
9 changed files with 807 additions and 2 deletions

View File

@@ -10,7 +10,7 @@
[platformio]
default_envs = HUM_DriSteem_RTS_RX36_TCP ; Select here the name of the configuration you want to download
default_envs = PHX3_CRAH_LIEBERT_80_SLAB_TCP ; Select here the name of the configuration you want to download
[env]
upload_port = COM9
@@ -213,9 +213,16 @@ board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_src_filter = -<*> +<BMS/VFD/PHX3_VFD_ABB_ACH580_RTU>
[env:PHX3_CRAH_LIEBERT_80_SLAB_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP>
[env:HUM_DriSteem_RTS_RX36_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<BMS/HUM/HUM_DriSteem_RTS_RX36_TCP>
build_src_filter = -<*> +<BMS/HUM/HUM_DriSteem_RTS_RX36_TCP>

View File

@@ -0,0 +1,58 @@
# CRAH Liebert 80 125 SLAB TCP
## Brief Introduction
This version of the LIEBERT 80 SLAB Electrical Gallery CRAH has different registers from the existing
"CRAH_LIEBERT_80_125_SLAB_TCP" code, hence a new instance was created. The logic in this code is also
unique from the existing LIEBERT_80_125 CRAH unit.
This implementation assumes that on/off control and supply, return air temp setpoints are sent to CRAH unit
from Ignition- there is not an associated PLC program.
## List of Equipment
This configuration has been used for these models:
* **PHX3 Liebert CW084DC1A1SDM7 SLAB**: 10-27-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
On/Off Control by Coil 25
Supply Air Temp Setpoint used for PID control of Fluid Control Valves 1&2
Return Air Temp Setpoint used for PID control of Fan Speed
Unsure of difference between Fluid Control Valves 1 & 2, for this simulation they are assumed to operate the same
We have reached out to vendor for clarification of these two valves.
### Standby State
* **Unit Status**: set to 2 (standby)
* **Free Cool Status**: set to 1 whenever in Standby Mode - Assuming whenever in Standby Mode will operate in Free Cooling mode
* **Fan Speed**: ramp to 0
* **Return Humidity**: saw 0 to 80, increments of 5
* **Return Air Temp**: single value 80 +/- 1
* **Supply Air Temp**: single value 80 +/- 1
* **Supply Air Flow**: ramp to 0
* **Fluid Control Valve Position 1**: ramp to 0
* **Fluid Control Valve Position 2**: ramp to 0
### Running State
* **Unit Status, Supply Fan Status, Cooling Status**: set to 1
* **Return Humidity**: saw 0 to 80, increments of 5 (same as Standby Mode)
* **Return Air Temp**: saw 62 to 110, increments of 2
* **Supply Air Temp**: saw 64 to 86, increments of 1
* **Supply Air Flow**: saw 7 to 10, increments of 1
* **Fan Speed**: PID control (Return Air Temp Setpoint, Return Air Temp)
* **Fluid Control Valve Position 1**: PID control (Supply Air Temp Setpoint, Supply Air Temp)
* **Fluid Control Valve Position 2**: PID control (Supply Air Temp Setpoint, Supply Air Temp)
### Fail State
* **Unit Status**: set to 0 (off)
* **Free Cool Status**: set to 0
* **Fan Speed**: ramp to 0
* **Return Humidity**: saw 0 to 80, increments of 5
* **Return Air Temp**: single value 80 +/- 1
* **Supply Air Temp**: single value 80 +/- 1
* **Supply Air Flow**: ramp to 0
* **Fluid Control Valve Position 1**: ramp to 0
* **Fluid Control Valve Position 2**: ramp to 0

View File

@@ -0,0 +1,99 @@
/**
* @file StateUtils.cpp
* @brief Implementation of the StateUtils class.
* @author Robert J. Davis
* @date 2025-10-24
*
* This file contains implementation of utility functions that are used in multiple States.
*/
#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 "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include "StateUtils.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief This function will update the Alarm status DI bits according to the Alarm Commands from Coils (Modscan)
* It will also update the Common Alarm: if any alarm is active, the Common alarm will also be active.
*
* This is a function used in the update() of the Standby, Running, and Fail States.
*
*/
void updateAlarms(Equipment<ModbusIP>* equipment){
const std::vector<std::string> alarmDescriptions = {
"Alarm Fan Overload", "Alarm Loss of Air", "Alarm Compressor 1A Overload", "Alarm Compressor 2A Overload",
"Alarm Smoke Detected", "Alarm Water Detected", "Alarm Standby Unit On", "Alarm CP High Water",
"Alarm Room Sensor Failure", "Alarm Power Loss", "Alarm High Return Air Temp", "Alarm Low Return Air Temp",
"Alarm High Return Humidity", "Alarm Low Return Humidity", "Alarm Clogged Filter", "Alarm Supply Sensor Failure",
"Alarm Unit Network Failure", "Alarm High Supply Temp", "Alarm Low Supply Temp", "Alarm Compressor 1 Short Cycle",
"Alarm Compressor 2 Short Cycle", "Alarm Fan Failure", "Alarm Circuit 1 Low Pressure", "Alarm Circuit 2 Low Pressure",
"Alarm Circuit 1 High Pressure", "Alarm Circuit 2 High Pressure", "Alarm High Return Air Dew Point",
"Alarm Low Return Air Dew Point", "Alarm Compressor 1 Over Temp", "Alarm Compressor 2 Over Temp",
"Common Alarm", "Alarm Pump Failure", "Alarm Comm Loss Condenser 1", "Alarm Comm Loss Condenser 2",
"Alarm Compressor 1B Overload", "Alarm Compressor 2B Overload"
};
const std::vector<std::string> alarmCommands = {
"Alarm Fan Overload ON", "Alarm Loss of Air ON", "Alarm Compressor 1A Overload ON", "Alarm Compressor 2A Overload ON",
"Alarm Smoke Detected ON", "Alarm Water Detected ON", "Alarm Standby Unit On ON", "Alarm High Water ON",
"Alarm Room Sensor Failure ON", "Alarm Power Loss ON", "Alarm High Return Air Temp ON", "Alarm Low Return Air Temp ON",
"Alarm High Return Humidity ON", "Alarm Low Return Humidity ON", "Alarm Clogged Filter ON", "Alarm Supply Sensor Failure ON",
"Alarm Unit Network Failure ON", "Alarm High Supply Temp ON", "Alarm Low Supply Temp ON", "Alarm Compressor 1 Short Cycle ON",
"Alarm Compressor 2 Short Cycle ON", "Alarm Fan Failure ON", "Alarm Circuit 1 Low Pressure ON", "Alarm Circuit 2 Low Pressure ON",
"Alarm Circuit 1 High Pressure ON", "Alarm Circuit 2 High Pressure ON", "Alarm High Return Air Dew Point ON",
"Alarm Low Return Air Dew Point ON", "Alarm Compressor 1 Over Temp ON", "Alarm Compressor 2 Over Temp ON",
"Common Alarm ON", "Alarm Pump Failure ON", "Alarm Comm Loss Condenser 1 ON", "Alarm Comm Loss Condenser 2 ON",
"Alarm Compressor 1B Overload ON", "Alarm Compressor 2B Overload ON"
};
int numAlarms = 0;
for (int i =0; i< alarmCommands.size() && i < alarmDescriptions.size(); ++i) {
Modbus_Point<ModbusIP>* commandPoint = equipment->getModbus_Point(alarmCommands[i]);
Modbus_Point<ModbusIP>* alarmPoint = equipment->getModbus_Point(alarmDescriptions[i]);
if (commandPoint) {
alarmPoint->setValue(commandPoint->getValue());
if (alarmPoint->getValue() == 1) numAlarms++;
}
}
if (numAlarms >= 1) equipment->setModbus_Point("Common Alarm", 1);
else equipment->setModbus_Point("Common Alarm", 0);
}
/**
* @brief Updates Dehumidifier Mode
*
* This function will update the Dehumidifier Mode based on Dehumidifier Mode Command (Coil 1)
* received from Modscan. This is for simulation purposes only - in practice, the Chiller
* will transition to Dehumidifier mode based on its own internal logic.
*
* For ease of testing, this is a function used in the update() of the Standby, Running, and Fail States.
*
*/
void updateDehumidifier(Equipment<ModbusIP>* equipment){
Modbus_Point<ModbusIP>* DehumidifierCommand = equipment->getModbus_Point("Dehumidifier Mode ON");
Modbus_Point<ModbusIP>* DehumidifierStatus = equipment->getModbus_Point("Dehumidifier Status");
if (DehumidifierCommand->getValue() == 1) {
DehumidifierStatus->setValue(1);
}
else {
DehumidifierStatus->setValue(0);
}
}

View File

@@ -0,0 +1,43 @@
/**
* @file config.h
* @brief StateUtils class
* @author Robert J Davis
* @date 2025-10-24
*
* Defines the StateUtils class, which contains utility functions used in multiple States.
*/
#pragma once
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.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
template <typename T>
class State;
/**
* @brief Checks common alarms (non-fail alarms) and updates the Common Alarm Modbus point.
* @param equipment Pointer to the Equipment instance.
* @return void
*/
void updateAlarms(Equipment<ModbusIP>* equipment);
/**
* @brief Checks Dehumidifier Mode ON from Modscan (Coil 1) and updates the Dehumidifier Status point.
* @param equipment Pointer to the Equipment instance.
* @return void
*/
void updateDehumidifier(Equipment<ModbusIP>* equipment);

View File

@@ -0,0 +1,99 @@
/**
* @file State_Fail.cpp
* @brief Implementation of the FailState class.
* @author Robert J Davis
* @date 2025-10-24
*
* 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_Saw.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "StateUtils.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 ramps Fan Speed, Supply Air Flow, and Fluid Control Valves to 0.
* Return Humidity continues to saw between 0-80 (for sake of Ignition display verification)
* Return and Supply Air Temp is 80 +/- 1
*
*/
template<>
FailState<ModbusIP>::FailState(const std::vector<std::string>& activeAlarms) {
addStrategy("Fan Speed", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Return Humidity", new SawStrategy(0.0f, 80.0f, 5.0f, 1000));
addStrategy("Return Air Temp", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("Supply Air Temp", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("Supply Air Flow", new RampStrategy(0.0f, 1.0f, 1000));
addStrategy("Fluid Control Valve Position 1", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Fluid Control Valve Position 2", new RampStrategy(0.0f, 5.0f, 1000));
}
/**
* @brief Executes the fail state's logic for one update cycle.
*
* My programming logic: ensure System On/Off Control is always set to 0. This will ensure
* that after the fault is cleared, the unit will enter StandbyMode and will then be commanded
* by Operator to starts, rather than automatically restarting. This is my assumption for the sake
* of testing, actual implementation may be different.
*
* The only way to exit FailState is for the Smoke Detect and High Water alarms to be cleared.
* Upon exiting FailState, the unit will enter StandbyState.
*
* @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");
setPointValue(equipment, "System On/Off Control", 0); // my programming logic: when clear fault, should be sent to Standby Mode
updateAlarms(equipment);
updateDehumidifier(equipment); // Dehumidifier mode can be toggled while in FailState (for ease of Ignition HMI verification)
bool smokeDetectState = getPointValue(equipment, "Alarm Smoke Detected");
bool highWaterState = getPointValue(equipment, "Alarm CP High Water");
if (smokeDetectState == false && highWaterState == false){
return new StandbyState<ModbusIP>();
}
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the fail state.
* Sets the Unit Status, Supply Fan Status, Cooling Status, and Free Cooling Status to 0 (off).
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "Unit Status", 0);
setPointValue(equipment, "Supply Fan Status", 0);
setPointValue(equipment, "Cooling Status", 0);
setPointValue(equipment, "Free Cooling Status", 0);
}
/**
* @brief Logic to execute once when exiting the fail 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...");
}

View File

@@ -0,0 +1,134 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Robert J Davis
* @date 2025-10-24
*
* 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 "StateUtils.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.
* Return Air Temp saws between 66 and 110 to cover both low alarm and high alarm states (72 and 100).
* Supply Air Temp saws between 68 and 86 to cover both low alarm and high alarm states (72 and 78).
* Supply Air Flow saws between 7 to 10 (for Ignition HMI verification, no correlation to expected values).
* Fan Speed adjusts via PID on Return Air Temp Setpoint (note: PID parameters are set in base code, can't be adjusted)
* Fluid Control Valve Positions adjust via PID on Supply Air Temp Setpoint.
* Unsure of the difference between FCV 1 and 2, therefore they just match for the sake of testing.
*
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("Return Humidity", new SawStrategy(0.0f, 80.0f, 5.0f, 1000));
addStrategy("Return Air Temp", new SawStrategy(66.0f, 110.0f, 2.0f, 1000));
addStrategy("Supply Air Temp", new SawStrategy(68.0f, 86.0f, 1.0f, 1000));
addStrategy("Supply Air Flow", new SawStrategy(7.0f, 10.0f, 1.0f, 1000));
addStrategy("Fan Speed", new PIDStrategy("Return Air Temp Setpoint", 1000, "Return Air Temp"));
addStrategy("Fluid Control Valve Position 1", new PIDStrategy("Supply Air Temp Setpoint", 1000, "Supply Air Temp"));
addStrategy("Fluid Control Valve Position 2", new PIDStrategy("Supply Air Temp Setpoint", 1000, "Supply Air Temp"));
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. Updates Alarm states
* 2. Updates Dehumidifier mode (for ease of Ignition verification)
* If the Smoke Detected or High Water alarms annunciate, send to FailState.
*
* 3. Check if On/Off Command = 0, then send to Standby State.
*
* 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) {
Serial.println("Running update function");
updateAlarms(equipment);
updateDehumidifier(equipment);
bool smokeDetect = getPointValue(equipment, "Alarm Smoke Detected");
bool highWater = getPointValue(equipment, "Alarm CP High Water");
std::vector<std::string> activeFailAlarms;
if (smokeDetect) activeFailAlarms.push_back("Alarm Smoke Detected");
if (highWater) activeFailAlarms.push_back("Alarm CP High Water");
if (!activeFailAlarms.empty()){
return new FailState<ModbusIP>(activeFailAlarms);
}
int On_Off_Command = getPointValue(equipment, "System On/Off Control");
if (On_Off_Command == 0){
return new StandbyState<ModbusIP>();
}
// Due to 10x scaling of Supply and Return Air Temps, need to adjust for PID strategies
float returnTemp = getPointValue(equipment, "Return Air Temp")/10;
float supplyTemp = getPointValue(equipment, "Supply Air Temp")/10;
float returnTempSP = getPointValue(equipment, "Return Air Temp Setpoint");
float supplyTempSP = getPointValue(equipment, "Supply Air Temp Setpoint");
Strategy_Behavior* FluidControlValve1_strat = getStrategy("Fluid Control Valve Position 1");
Strategy_Behavior* FluidControlValve2_strat = getStrategy("Fluid Control Valve Position 2");
Strategy_Behavior* FanSpeed_strat = getStrategy("Fan Speed");
static_cast<PIDStrategy*>(FluidControlValve1_strat)->setLimits(supplyTempSP, supplyTemp);
static_cast<PIDStrategy*>(FluidControlValve2_strat)->setLimits(supplyTempSP, supplyTemp);
static_cast<PIDStrategy*>(FanSpeed_strat)->setLimits(returnTempSP, returnTemp);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the Unit Status, Supply Fan Status, Cooling Status, and Free Cooling Status 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
setPointValue(equipment, "Unit Status", 1);
setPointValue(equipment, "Supply Fan Status", 1);
setPointValue(equipment, "Cooling Status", 1);
setPointValue(equipment, "Free Cooling Status", 0);
}
/**
* @brief Logic to execute once when exiting the running state.
* All State transitions are executed on enterState function, therefore
* this exitState function is not used.
* @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
}

View File

@@ -0,0 +1,117 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Robert J Davis
* @date 2025-10-24
*
* 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 "StateUtils.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 ramps Fan Speed, Supply Air Flow, and Fluid Control Valves to 0.
* Return Humidity continues to saw between 0-80 (for sake of Ignition display verification)
* Return and Supply Air Temp is 80 +/- 1.
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
addStrategy("Fan Speed", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Return Humidity", new SawStrategy(0.0f, 80.0f, 5.0f, 1000));
addStrategy("Return Air Temp", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("Supply Air Temp", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("Supply Air Flow", new RampStrategy(0.0f, 1.0f, 1000));
addStrategy("Fluid Control Valve Position 1", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Fluid Control Valve Position 2", new RampStrategy(0.0f, 5.0f, 1000));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. Updates Alarm states
* 2. Updates Dehumidifier mode (for ease of Ignition verification)
* If the Smoke Detected or High Water alarms annunciate, send to FailState.
*
* 3. Check if On/Off Command = 1, then send to Running State.
*
* If no transition occurs, it applies the strategies defined for the standby state.
*
* @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");
updateAlarms(equipment);
updateDehumidifier(equipment);
bool smokeDetect = getPointValue(equipment, "Alarm Smoke Detected");
bool highWater = getPointValue(equipment, "Alarm CP High Water");
std::vector<std::string> activeFailAlarms;
if (smokeDetect == true) activeFailAlarms.push_back("Alarm Smoke Detected");
if (highWater == true) activeFailAlarms.push_back("Alarm CP High Water");
if (!activeFailAlarms.empty()){
return new FailState<ModbusIP>(activeFailAlarms);
}
int On_Off_Command = getPointValue(equipment, "System On/Off Control");
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.
* Sets the Unit Status = 2 (standby) and Free Cooling Status to 1 (assume whenever in Standby Mode, runs in Free Cooling)
* Supply Fan Status, Cooling Status, Dehumidifier Status, and On/Off Command set to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Set all Unit Status to 2 (standby), ensure On Off Command also set to 0.
setPointValue(equipment, "Unit Status", 2);
setPointValue(equipment, "Supply Fan Status", 0);
setPointValue(equipment, "Cooling Status", 0);
setPointValue(equipment, "Free Cooling Status", 1);
setPointValue(equipment, "Dehumidifier Status", 0);
setPointValue(equipment, "System OnOff Control", 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,162 @@
/**
* @file config.h
* @brief Main configuration file for the Electrical Gallery CRAH Unit (TCP) emulator - Vertiv Liebert 80 Slab TCP PHX3 DC1
* @author Robert J Davis
* @date 2025-10-24
*
* 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 = "TP-Link_D91A"; /**< @brief The SSID of the WiFi network. */
const char *password = "52761492"; /**< @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[] =
{
{COIL, 0, 0, "Dehumidifier Mode ON"}, // For Arduino testing only
{COIL, 1, 0, "Alarm Smoke Detected ON"}, // For Arduino testing only - will send to FailState
{COIL, 2, 0, "Alarm High Water ON"}, // For Arduino testing only - will send to FailState
{COIL, 3, 0, "Alarm Fan Overload ON"}, // For Arduino testing only
{COIL, 4, 0, "Alarm Loss of Air ON"}, // For Arduino testing only
{COIL, 5, 0, "Alarm Compressor 1A Overload ON"}, // For Arduino testing only
{COIL, 6, 0, "Alarm Compressor 2A Overload ON"}, // For Arduino testing only
{COIL, 7, 0, "Alarm Water Detected ON"}, // For Arduino testing only
{COIL, 8, 0, "Alarm Standby Unit On ON"}, // For Arduino testing only
{COIL, 9, 0, "Alarm Room Sensor Failure ON"}, // For Arduino testing only
{COIL, 10, 0, "Alarm Power Loss ON"}, // For Arduino testing only
{COIL, 11, 0, "Alarm Clogged Filter ON"}, // For Arduino testing only
{COIL, 12, 0, "Alarm Supply Sensor Failure ON"}, // For Arduino testing only
{COIL, 13, 0, "Alarm Unit Network Failure ON"}, // For Arduino testing only
{COIL, 14, 0, "Alarm Compressor 1 Short Cycle ON"}, // For Arduino testing only
{COIL, 15, 0, "Alarm Compressor 2 Short Cycle ON"}, // For Arduino testing only
{COIL, 16, 0, "Alarm Fan Failure ON"}, // For Arduino testing only
{COIL, 17, 0, "Alarm Circuit 1 Low Pressure ON"}, // For Arduino testing only
{COIL, 18, 0, "Alarm Circuit 2 Low Pressure ON"}, // For Arduino testing only
{COIL, 19, 0, "Alarm Circuit 1 High Pressure ON"}, // For Arduino testing only
{COIL, 20, 0, "Alarm Circuit 2 High Pressure ON"}, // For Arduino testing only
{COIL, 21, 0, "Alarm High Return Air Dew Point ON"}, // For Arduino testing only
{COIL, 22, 0, "Alarm Low Return Air Dew Point ON"}, // For Arduino testing only
{COIL, 23, 0, "Common Alarm ON"}, // For Arduino testing only
{COIL, 24, 0, "System On/Off Control"},
{COIL, 25, 0, "Alarm Compressor 1 Over Temp ON"}, // For Arduino testing only
{COIL, 26, 0, "Alarm Compressor 2 Over Temp ON"}, // For Arduino testing only
{COIL, 27, 0, "Alarm Pump Failure ON"}, // For Arduino testing only
{COIL, 28, 0, "Alarm Comm Loss Condenser 1 ON"}, // For Arduino testing only
{COIL, 29, 0, "Alarm Comm Loss Condenser 2 ON"}, // For Arduino testing only
{COIL, 30, 0, "Alarm Compressor 1B Overload ON"}, // For Arduino testing only
{COIL, 31, 0, "Alarm Compressor 2B Overload ON"}, // For Arduino testing only
{DI, 24, 0, "Supply Fan Status"},
{DI, 25, 0, "Cooling Status"},
{DI, 26, 0, "Free Cooling Status"},
{DI, 30, 0, "Dehumidifier Status"},
{DI, 33, 0, "Alarm Fan Overload"},
{DI, 34, 0, "Alarm Loss of Air"},
{DI, 38, 0, "Alarm Compressor 1A Overload"},
{DI, 42, 0, "Alarm Compressor 2A Overload"},
{DI, 46, 0, "Alarm Smoke Detected"},
{DI, 47, 0, "Alarm Water Detected"},
{DI, 50, 0, "Alarm Standby Unit On"},
{DI, 51, 0, "Alarm CP High Water"},
{DI, 52, 0, "Alarm Room Sensor Failure"},
{DI, 60, 0, "Alarm Power Loss"},
{DI, 66, 0, "Alarm High Return Air Temp"}, // 100 set in Ignition
{DI, 67, 0, "Alarm Low Return Air Temp"}, // 72 set in Ignition
{DI, 68, 0, "Alarm High Return Humidity"}, // 60 set in Ignition
{DI, 69, 0, "Alarm Low Return Humidity"}, // 20 set in Ignition
{DI, 75, 0, "Alarm Clogged Filter"},
{DI, 76, 0, "Alarm Supply Sensor Failure"},
{DI, 91, 0, "Alarm Unit Network Failure"},
{DI, 208, 0, "Alarm High Supply Temp"}, // 78 set in Ignition
{DI, 209, 0, "Alarm Low Supply Temp"}, // 72 set in Ignition
{DI, 211, 0, "Alarm Compressor 1 Short Cycle"},
{DI, 212, 0, "Alarm Compressor 2 Short Cycle"},
{DI, 217, 0, "Alarm Fan Failure"},
{DI, 239, 0, "Alarm Circuit 1 Low Pressure"},
{DI, 240, 0, "Alarm Circuit 2 Low Pressure"},
{DI, 241, 0, "Alarm Circuit 1 High Pressure"},
{DI, 242, 0, "Alarm Circuit 2 High Pressure"},
{DI, 344, 0, "Alarm High Return Air Dew Point"},
{DI, 345, 0, "Alarm Low Return Air Dew Point"},
{DI, 348, 0, "Alarm Compressor 1 Over Temp"},
{DI, 349, 0, "Alarm Compressor 2 Over Temp"},
{DI, 350, 0, "Common Alarm"},
{DI, 491, 0, "Alarm Pump Failure"},
{DI, 682, 0, "Alarm Comm Loss Condenser 1"},
{DI, 683, 0, "Alarm Comm Loss Condenser 2"},
{DI, 740, 0, "Alarm Compressor 1B Overload"},
{DI, 741, 0, "Alarm Compressor 2B Overload"},
{IR, 99, 0, "Unit Status"}, // 0:off, 1:on, 2:standby
{IR, 102, 0, "Fan Speed"},
{IR_10x, 129, 0, "Return Humidity"},
{IR_10x, 742, 0, "Return Air Temp"},
{IR_10x, 743, 0, "Supply Air Temp"},
{IR, 1465, 0, "Supply Air Flow"},
{IR, 2050, 0, "Fluid Control Valve Position 1"},
{IR, 2051, 0, "Fluid Control Valve Position 2"},
{HR, 732, 73, "Supply Air Temp Setpoint"},
{HR, 753, 80, "Return Air Temp Setpoint"},
};
//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);
}
}