diff --git a/platformio.ini b/platformio.ini index 34b85c0..2efefee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,10 +10,10 @@ [platformio] -default_envs = CRAH_PETRA_PAHHC_600_C6_TCP ; Select here the name of the configuration you want to download +default_envs = HUM_DriSteem_RTS_RX36_TCP ; Select here the name of the configuration you want to download [env] -upload_port = COM50 +upload_port = COM9 [common_env_options] framework = arduino @@ -211,4 +211,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:HUM_DriSteem_RTS_RX36_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/HUM/HUM_DriSteem_RTS_RX36_TCP/README.md b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/README.md new file mode 100644 index 0000000..59b4aaa --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/README.md @@ -0,0 +1,51 @@ +# Humidifier Dri-Steem RTS RX-36-1 TCP + +## Brief Introduction +This humidifier receives on/off commands and RH Setpoint from the PLC. +The Space RH register is not used, since there will not be a Space RH sensor wired to the HUM unit. +The RH Setpoint will be determined based on dewpoints in the datahall. See QTS SOO for details. + +## List of Equipment +This configuration has been used for these models: +* **RTS RX-36-1**: 10-28-2025 + +## Hardware Prerequisites + +The code is written for an ESP8266/ESP32-style microcontroller with WiFi capabilities. +* **Microcontroller**: [Firebeetle 2 ESP32.](https://www.dfrobot.com/product-2231.html) + +--- + +## States and Strategies +Provide a brief description of what variables and strategies were used in this configuraiton + +### Standby State +Run Mode = 3 (system standby) +Duct RH = 35 +/- 5 +Fill Valve, Drain Valve = 0 +Steam Demand Mass/Pct = 0 +Steam Output Mass/Pct = 0 +If any alarms active or safety interlock = 0 --> FailState +Checks for Run Mode = 1 AND Air Proving Switch = 1 --> RunningState + +### Running State +Run Mode = 1 (auto) +If any alarms active or safety interlock = 0 --> FailState +If Run Mode = 3 or loss of airflow --> StandbyState +Reads RH Setpoint from PLC +DuctRH will dynamically ramp to RH Setpoint +Fill Valve and Drain Valve switch between 0 and 1 (squareStrategy) +Steam Demand Mass between 3-6 (sawStrategy) +Steam Demand Percent between 50-80% (sawStrategy) +Tank Temp = 80 +/- 3 +Steam Output Mass = 4 +/- 1 +Steam Output Percent = 65 +/- 10 +Water Until ADS/Service will ramp down to 0 (initializes at 1500 and 10000) + +### Fail State +Run Mode = 3 (system standby) +Duct RH = 35 +/- 5 +Fill Valve, Drain Valve = 0 +Steam Demand Mass/Pct = 0 +Steam Output Mass/Pct = 0 +When all alarms are cleared and safety interlock = 1 --> StandbyState \ No newline at end of file diff --git a/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/StateUtils.cpp b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/StateUtils.cpp new file mode 100644 index 0000000..424f2c1 --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/StateUtils.cpp @@ -0,0 +1,91 @@ +/** + * @file StateUtils.cpp + * @brief Implementation of the StateUtils class. + * @author Robert J. Davis + * @date 2025-10-28 + * + * 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 bits and Safety Interlock state (based on Safety Interlock ON coil - for testing only) + * If the "Clear All Active Alarms" coil is activate, all alarms will be cleared, the Safety Interlock will be set to 1 (ready to operate), + * and the "Manual Clear Alarm Exists" bit will be set to 1. + * The "Alarms Present" (DI 10) will be set to 1 if any alarm is active (or Safety Interlock = 0). This is a register used for + * testing only, and will be used in Standby and Running States to send to FailState. + * + * This function is used in the update() of the Standby, Running, and Fail States. + * +*/ + +void updateAlarms(Equipment* equipment){ + const std::vector alarmDescriptions = { + "Tank Temp Sensor Fail", "Tank Overtemp", "Input RH Out of Range", "Duct RH Out of Range", + "Water Probe Check", "Water Probe Faulty", "Fill Time Excessive", "Refill Time Excessive", + "Tank Not Draining", "Boil Time Excessive" + }; + + // update Safety Interlock state (note: Safety Interlock = 0 means the equipment cannot run- fail safe) + if (equipment->getModbus_Point("Safety Interlock ON")->getValue() == 1){ + equipment->setModbus_Point("Safety Interlock", 0); + } + else equipment->setModbus_Point("Safety Interlock", 1); + + // if "Clear All Active Alarms" bit is 1 --> clear all alarms as well as safety interlock + // if "Clear All Active Alarms" bit is 0 --> if any alarms present set "Alarms Present" register to 1 + if (equipment->getModbus_Point("Clear All Active Alarms")->getValue() == 1){ + for (int i =0; i < alarmDescriptions.size(); ++i) { + equipment->setModbus_Point(alarmDescriptions[i], 0); + } + equipment->setModbus_Point("Safety Interlock ON", 0); + equipment->setModbus_Point("Safety Interlock", 1); + equipment->setModbus_Point("Alarms Present", 0); + equipment->setModbus_Point("Manual Clear Alarm Exists", 1); // the only way to set this back to 0 is manually via Modscan + } + else { + int numAlarms = 0; + for (int i =0; i < alarmDescriptions.size(); ++i) { + Modbus_Point* alarmPoint = equipment->getModbus_Point(alarmDescriptions[i]); + if (alarmPoint->getValue() == 1) numAlarms++; + } + if (equipment->getModbus_Point("Safety Interlock")->getValue() == 0) numAlarms++; + if (numAlarms >= 1) equipment->setModbus_Point("Alarms Present", 1); + else equipment->setModbus_Point("Alarms Present", 0); + } +} + +/** + * @brief This function is used to update the Airflow Proving Switch state (DI 1) + * based on the Safety Interlock ON coil (Coil 2) - this is used for testing purposes only. + * + * This function is used in the update() of the Standby, Running, and Fail States. + * +*/ + +void updateAirflow(Equipment* equipment){ + if (equipment->getModbus_Point("Airflow ON")->getValue() == 1){ + equipment->setModbus_Point("Airflow Proving Switch", 1); + } + else equipment->setModbus_Point("Airflow Proving Switch", 0); +} \ No newline at end of file diff --git a/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/StateUtils.h b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/StateUtils.h new file mode 100644 index 0000000..e836ae5 --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/StateUtils.h @@ -0,0 +1,43 @@ +/** + * @file config.h + * @brief StateUtils class + * @author Robert J Davis + * @date 2025-10-28 + * + * 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 Updates all alarms, safety interlock, alarms present register. + * @param equipment Pointer to the Equipment instance. + * @return void + */ +void updateAlarms(Equipment* equipment); + +/** + * @brief Updates the Airflow Proving Switch state based on Airflow ON state. + * @param equipment Pointer to the Equipment instance. + * @return void + */ +void updateAirflow(Equipment* equipment); \ No newline at end of file diff --git a/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Fail.cpp b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Fail.cpp new file mode 100644 index 0000000..d9910a0 --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Fail.cpp @@ -0,0 +1,91 @@ +/** + * @file State_Fail.cpp + * @brief Implementation of the FailState class. + * @author Robert J Davis + * @date 2025-10-28 + * + * 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" +#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 will have the Duct RH fluctuate around 35% (for visualization purposes). + * + * @param activeAlarms A vector of strings, where each string is the + * description of a Modbus point to be set as an active alarm. + * This parameter is not used in this implementation of the Fail State. + */ +template<> +FailState::FailState(const std::vector& activeAlarms) { + addStrategy("Duct RH", new SingleValueStrategy(35.0f, 5.0f, 1000)); + addStrategy("Tank Temp", new SingleValueStrategy(80.0f, 1.0f, 3000)); +} + +/** + * @brief Executes the fail state's logic for one update cycle. + * + * Update Alarms states. Stays in FailState until all alarms are cleared --> 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* FailState::update(Equipment* equipment) { + // STATE control, add conditions if change to a different state is needed + Serial.println("Fail update function"); + + updateAlarms(equipment); + + bool alarmsPresent = getPointValue(equipment, "Alarms Present"); + if (!alarmsPresent){ + return new StandbyState(); + } + + _applyStrategies(equipment); + return nullptr; +} + +/** + * @brief Logic to execute once when entering the fail state. + * Sets the Run Mode to 3 (system standby), and appropriate analogs to 0. + * @param equipment Pointer to the Equipment instance. + */ +template<> +void FailState::enterState(Equipment* equipment) { + // Logic to run when the equipment enters this state + Serial.println("Enter Fail State..."); + // Ensure Run Mode set to 3 (standby) + setPointValue(equipment, "Run Mode", 3); + setPointValue(equipment, "Fill Valve", 0); + setPointValue(equipment, "Drain Valve", 0); + setPointValue(equipment, "Steam Demand Mass", 0); + setPointValue(equipment, "Steam Demand Percent", 0); + setPointValue(equipment, "Steam Output Mass", 0); + setPointValue(equipment, "Steam Output Percent", 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/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Running.cpp b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Running.cpp new file mode 100644 index 0000000..bafc992 --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Running.cpp @@ -0,0 +1,123 @@ +/** + * @file State_Running.cpp + * @brief Implementation of the RunningState class. + * @author Robert J Davis + * @date 2025-10-28 + * + * 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 including various analog values. Fill and Drain Valves switch between 0 and 1. + * Water Until ADS/Service will ramp down to 0, initialized at 1500 and 10000, respectively. + */ +template<> +RunningState::RunningState() { + addStrategy("Duct RH", new RampStrategy(40.0f, 1.0f, 2000)); + + addStrategy("Fill Valve", new SquareStrategy(1.0f, 0.0f, 5000)); + addStrategy("Drain Valve", new SquareStrategy(0.0f, 1.0f, 4500)); + + addStrategy("Steam Demand Mass", new SawStrategy(3.0f, 6.0f, 1.0f, 2000)); // these values are semi-random for visualization + addStrategy("Steam Demand Percent", new SawStrategy(50.0f, 80.0f, 5.0f, 1000)); // these values are semi-random for visualization + addStrategy("Tank Temp", new SingleValueStrategy(80.0f, 3.0f, 1000)); // these values are semi-random for visualization + addStrategy("Steam Output Mass", new SingleValueStrategy(4.0f, 1.0f, 1000)); // these values are semi-random for visualization + addStrategy("Steam Output Percent", new SingleValueStrategy(65.0f, 10.0f, 1000)); // these values are semi-random for visualization + addStrategy("Water Until ADS", new RampStrategy(0.0f, 1.0f, 2000)); // ramping down to 0 from 1500 + addStrategy("Water Until Service", new RampStrategy(0.0f, 1.0f, 2000)); // ramping down to 0 from 10000 +} + +/** + * @brief Executes the running state's logic for one update cycle. + * + * This method first checks if there are any active alarms --> FailState. + * Also updates Airflow state according to Airflow ON (Coil 1- for testing use only). + * If no alarms are active, checks for loss of airflow or "Run Mode" = 3 (Modscan, but will be from PLC) + * to transition to the Standby state. If no transition is triggered, it updates the + * rampStrategy targetValue of the Duct RH to dynamically ramp up to the Space RH Setpoint (sent from PLC). + * + * @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) { + // Update alarms states and airflow switch state + updateAlarms(equipment); + updateAirflow(equipment); + + // if Alarms are present (as updated in updateAlarms function) --> FailState + bool alarmsPresent = getPointValue(equipment, "Alarms Present"); + if (alarmsPresent){ + std::vector activeAlarmsDesc = {}; // sending a blank string to FailState, b/c that parameter not used in FailState implementation. + return new FailState(activeAlarmsDesc); + } + + // Check for Run Mode and Airflow. If Run Mode = 3 OR Airflow stopped --> StandbyState + int runMode_Command = getPointValue(equipment, "Run Mode"); // Set by PLC + int airflow = getPointValue(equipment, "Airflow Proving Switch"); + if (runMode_Command == 3 || airflow == 0) { + return new StandbyState(); + } + + // Set the Duct RH ramp target value equal to the Space RH Setpoint + float Space_RH_Setpoint = getPointValue(equipment, "Space RH Setpoint"); + float ductRH = getPointValue(equipment, "Duct RH"); + Strategy_Behavior* DuctRH_strat = getStrategy("Duct RH"); + if (DuctRH_strat){ + static_cast(DuctRH_strat)->setTarget(Space_RH_Setpoint); + } + + // Apply any strategies defined for the standby state + _applyStrategies(equipment); + return nullptr; +} + +/** + * @brief Logic to execute once when entering the running state. + * + * Note: do not need to set Run Mode = 1 (auto) since that is required to + * send the unit to Run Mode in the first place. Run Mode will already = 1. + * + * @param equipment Pointer to the Equipment instance. + */ +template<> +void RunningState::enterState(Equipment* 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. + * @param equipment Pointer to the Equipment instance. + */ +template<> +void RunningState::exitState(Equipment* equipment) { + // Cleanup logic to run when the equipment leaves this state + Serial.println("Exit Running State..."); +} \ No newline at end of file diff --git a/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Standby.cpp b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Standby.cpp new file mode 100644 index 0000000..d47cd2d --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/State_Standby.cpp @@ -0,0 +1,110 @@ +/** + * @file State_Standby.cpp + * @brief Implementation of the StandbyState class. + * @author Robert J Davis + * @date 2025-10-28 + * + * 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 will have + * Duct RH fluctuate around 35% for visualization purposes only. + * + */ +template<> +StandbyState::StandbyState() { + addStrategy("Duct RH", new SingleValueStrategy(35.0f, 5.0f, 1000)); + addStrategy("Tank Temp", new SingleValueStrategy(80.0f, 1.0f, 3000)); +} + +/** + * @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 Airflow Switch state (based on Airflow ON command - used just for simulation purposes) + * If any Alarms are active or Safety Interlock = 0, send to FailState. + * + * 3. Check if Run Mode = 1 and Airflow Switch = 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); + updateAirflow(equipment); + + // if Alarms are present (as updated in updateAlarms function) --> FailState + bool alarmsPresent = getPointValue(equipment, "Alarms Present"); + if (alarmsPresent){ + std::vector activeAlarmsDesc = {}; // sending a blank string to FailState, b/c that parameter not used in FailState implementation. + return new FailState(activeAlarmsDesc); + } + + // Check for Run Mode and Airflow Proving Switch. If Run Mode = 1 and there is Airflow --> RunningState + int runMode_Command = getPointValue(equipment, "Run Mode"); // Set by PLC + int airflow = getPointValue(equipment, "Airflow Proving Switch"); + if (airflow == 1 && runMode_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. + * @param equipment Pointer to the Equipment instance. + */ +template<> +void StandbyState::enterState(Equipment* equipment) { + Serial.println("Enter Standby State..."); + // Set Run Mode to 3 (standby), just in case entered Standby on loss of airflow + setPointValue(equipment, "Run Mode", 3); + setPointValue(equipment, "Fill Valve", 0); + setPointValue(equipment, "Drain Valve", 0); + setPointValue(equipment, "Steam Demand Mass", 0); + setPointValue(equipment, "Steam Demand Percent", 0); + setPointValue(equipment, "Steam Output Mass", 0); + setPointValue(equipment, "Steam Output Percent", 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/HUM/HUM_DriSteem_RTS_RX36_TCP/config.h b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/config.h new file mode 100644 index 0000000..41b252c --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/config.h @@ -0,0 +1,108 @@ +/** + * @file config.h + * @brief Main configuration file for the DriSteem Humidifier (TCP) emulator. + * @author Robert J Davis + * @date 2025-10-27 + * + * 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, "Airflow ON"}, // Used for Modscan testing only to set Airflow Proving Switch + {COIL, 1, 0, "Safety Interlock ON"}, // Used for Modscan testing only to trip Safety Interlock + {COIL, 2, 0, "Manual Clear Alarm Exists"}, + {COIL, 3, 0, "Clear All Active Alarms"}, // OCmd_Reset + {COIL, 4, 0, "Tank Temp Sensor Fail"}, + {COIL, 5, 0, "Tank Overtemp"}, + {COIL, 6, 0, "Input RH Out of Range"}, + {COIL, 7, 0, "Duct RH Out of Range"}, + {COIL, 9, 0, "Water Probe Check"}, + {COIL, 10, 0, "Water Probe Faulty"}, + {COIL, 11, 0, "Fill Time Excessive"}, + {COIL, 12, 0, "Refill Time Excessive"}, + {COIL, 13, 0, "Tank Not Draining"}, + {COIL, 14, 0, "Boil Time Excessive"}, + + {DI, 0, 0, "Airflow Proving Switch"}, // 0:open, 1:closed + {DI, 2, 1, "Safety Interlock"}, // 0:open, 1:closed + {DI, 7, 0, "Fill Valve"}, // 0:closed, 1:open + {DI, 8, 0, "Drain Valve"}, // 0:not draining, 1:draining + {DI, 9, 0, "Alarms Present"}, // Used for Modscan testing only - not part of vendor Modbus table + + {IR, 0, 0, "Space RH"}, // Relative_Humidity --> NOT USED, sensor not connected to HUM + {IR, 2, 0, "Duct RH"}, // OSet_CV + {IR, 3, 0, "Steam Demand Mass"}, + {IR, 4, 0, "Steam Demand Percent"}, + {IR, 6, 0, "Tank Temp"}, + {IR, 7, 0, "Steam Output Mass"}, + {IR, 8, 0, "Steam Output Percent"}, + {IR_10x, 9, 1500, "Water Until ADS"}, // 1 = 100 lbs (I know this is 10x function only) + {IR_10x, 10, 10000, "Water Until Service"}, // 1 = 100 lbs (I know this is 10x function only) + + {HR, 0, 3, "Run Mode"}, // Operation_Mode, 1:auto, 2:local standby, 3:system standby, 4:manual drain + {HR, 1, 0, "Space RH Setpoint"}, // Relative_Humidity_SP + {HR, 3, 85, "Duct High Limit 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 \ No newline at end of file diff --git a/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/main.cpp b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/main.cpp new file mode 100644 index 0000000..a38c2d0 --- /dev/null +++ b/src/BMS/HUM/HUM_DriSteem_RTS_RX36_TCP/main.cpp @@ -0,0 +1,86 @@ +/** + * @file main.cpp + * @brief Main execution program for the DriSteem Humidifier (TCP) Emulator. + * @author Emmanuel Hernandez Cruz, Robert J Davis + * @date 2025-09-02 + * + * @details This file contains the main execution program for an Arduino-based emulator of the DriSteem Humidifier 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); + } +}