diff --git a/platformio.ini b/platformio.ini index 259c977..0fe8268 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,10 +11,10 @@ [platformio] -default_envs = PHX3_VFD_ABB_ACH580_RTU ; 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 = COM50 +upload_port = COM9 [common_env_options] framework = arduino @@ -205,4 +205,11 @@ build_src_filter = -<*> + platform = espressif32 board = dfrobot_firebeetle2_esp32e extends = common_env_options -build_src_filter = -<*> + \ No newline at end of file +build_src_filter = -<*> + + +[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 = -<*> + \ No newline at end of file diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/README.md b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/README.md new file mode 100644 index 0000000..db837dc --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/README.md @@ -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 \ No newline at end of file diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/StateUtils.cpp b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/StateUtils.cpp new file mode 100644 index 0000000..b50dc7f --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/StateUtils.cpp @@ -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 +#include +#if defined(USE_MODBUS_IP) + #include +#else + #include +#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* equipment){ + const std::vector 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 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* commandPoint = equipment->getModbus_Point(alarmCommands[i]); + Modbus_Point* 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* equipment){ + Modbus_Point* DehumidifierCommand = equipment->getModbus_Point("Dehumidifier Mode ON"); + Modbus_Point* DehumidifierStatus = equipment->getModbus_Point("Dehumidifier Status"); + if (DehumidifierCommand->getValue() == 1) { + DehumidifierStatus->setValue(1); + } + else { + DehumidifierStatus->setValue(0); + } +} \ No newline at end of file diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/StateUtils.h b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/StateUtils.h new file mode 100644 index 0000000..e2c1196 --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/StateUtils.h @@ -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 +#include + +#if defined(USE_MODBUS_IP) + #include +#else + #include +#endif + +template +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* 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* equipment); \ No newline at end of file diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Fail.cpp b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Fail.cpp new file mode 100644 index 0000000..888d1b4 --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Fail.cpp @@ -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 +#else + #include +#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::FailState(const std::vector& 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* FailState::update(Equipment* 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(); + } + + _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::enterState(Equipment* 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::exitState(Equipment* equipment) { + // Cleanup logic to run when the equipment leaves this state + Serial.println("Exit Fail State..."); +} \ No newline at end of file diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Running.cpp b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Running.cpp new file mode 100644 index 0000000..d0ce726 --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Running.cpp @@ -0,0 +1,120 @@ +/** + * @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 +#include +#if defined(USE_MODBUS_IP) + #include +#else + #include +#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::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* RunningState::update(Equipment* 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 activeFailAlarms; + if (smokeDetect) activeFailAlarms.push_back("Alarm Smoke Detected"); + if (highWater) activeFailAlarms.push_back("Alarm CP High Water"); + if (!activeFailAlarms.empty()){ + return new FailState(activeFailAlarms); + } + + int On_Off_Command = getPointValue(equipment, "System On/Off Control"); + if (On_Off_Command == 0){ + return new StandbyState(); + } + + // 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::enterState(Equipment* 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::exitState(Equipment* equipment) { + // Cleanup logic to run when the equipment leaves this state +} \ No newline at end of file diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Standby.cpp b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Standby.cpp new file mode 100644 index 0000000..5db3e93 --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/State_Standby.cpp @@ -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 +#include +#if defined(USE_MODBUS_IP) + #include +#else + #include +#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::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* StandbyState::update(Equipment* 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 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(activeFailAlarms); + } + + int On_Off_Command = getPointValue(equipment, "System On/Off Control"); + if (On_Off_Command == 1){ + return new RunningState(); + } + + // 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::enterState(Equipment* 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::exitState(Equipment* equipment) { + // Cleanup logic to run when the equipment leaves this state + Serial.println("Exit Standby State..."); +} \ No newline at end of file diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/config.h b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/config.h new file mode 100644 index 0000000..f1040fb --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/config.h @@ -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 + 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 + 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, 129, 0, "Return Humidity"}, + {IR, 742, 0, "Return Air Temp"}, + {IR, 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 diff --git a/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/main.cpp b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/main.cpp new file mode 100644 index 0000000..286a98c --- /dev/null +++ b/src/BMS/CRAH/PHX3_CRAH_LIEBERT_80_SLAB_TCP/main.cpp @@ -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 +#include "config.h" +#include "ModbusPoints/Modbus_PointFactory.h" +#if defined(USE_MODBUS_IP) + #include +#else + #include +#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* 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); + } +}