Merge branch 'develop' into rdavis/York_Chiller_RTU

This commit is contained in:
Emmanuel HC
2026-05-21 09:18:02 -05:00
committed by GitHub
105 changed files with 6888 additions and 1286 deletions

View File

@@ -87,6 +87,15 @@ protected:
* @param strategy A pointer to the Strategy_Behavior object. The State takes ownership.
*/
void addStrategy(const std::string& pointDescription, Strategy_Behavior* strategy);
/**
* @brief Modify specifyc bits of a Modbus point in this state.
* @param equipment Pointer to the Equipment instance.
* @param pointName The description key of the Modbus point to write to.
* @param bitPosition The position to which the function will write to.
* @param state The new state of the selected bit.
*/
void setBitValue(Equipment<T>* equipment, const std::string& pointName, int bitPosition, bool state);
/**
* @brief returns a behavior strategy for a specific Modbus point in this state.
* @param pointDescription The description of the Modbus point your need to get.
@@ -218,4 +227,44 @@ void State<T>::_applyStrategies(Equipment<T>* equipment) {
}
}
/**
* @brief Controls a specific bit within an integer Modbus word (like a Holding or Input Register).
*
* This function bypasses the standard float logic to perform direct bit manipulation.
*
* @param equipment Pointer to the Equipment instance.
* @param pointName The description key of the Modbus point to modify.
* @param bitPosition The 0-based index of the bit to set/clear (0-15 for a 16-bit word).
* @param state If true, the bit is set (to 1); if false, the bit is cleared (to 0).
*/
template<typename T>
void State<T>::setBitValue(Equipment<T>* equipment, const std::string& pointName, int bitPosition, bool state) {
Modbus_Point<T>* point = equipment->getModbus_Point(pointName);
// Safety check: Ensure the point exists and isn't a decorated multi-word type (Float or Long)
// Note: Standard 16-bit Hreg/Ireg will return PointType::GENERIC.
if (!point || point->getType() != PointType::GENERIC || bitPosition < 0 || bitPosition > 15) {
// You can add an error logging statement here if needed, like Serial.printf(...)
return;
}
// 1. Get the current integer value directly from the point
int currentValue = point->getValue();
// 2. Create the bit mask
// '1 << bitPosition' shifts a 1 to the position we want to affect
int mask = 1 << bitPosition;
if (state) {
// 3. Set the bit (make it 1): Use the bitwise OR operator
currentValue |= mask;
} else {
// 3. Clear the bit (make it 0): Use the bitwise AND operator with the NOT (inverse) of the mask
currentValue &= ~mask;
}
// 4. Write the new integer value back
point->setValue(currentValue);
}
#endif

View File

@@ -43,7 +43,7 @@ float SingleValueStrategy::execute(float currentValue) {
}
int noiseInt = rand() % 201;
noiseInt -= 100;
float noise = (static_cast<float>(noiseInt) / 100) * _noiseMagnitude;
float noise = (static_cast<float>(noiseInt) / 100.0f) * _noiseMagnitude;
Serial.printf("Single value strategy with noise. %f \n", noise);
return _setpoint + noise;
}

View File

@@ -1,5 +1,4 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
@@ -10,8 +9,7 @@
[platformio]
default_envs = CH_York_YVAA_RTU ; Select here the name of the configuration you want to download
default_envs = RPP_Cortex_TCP ; Select here the name of the configuration you want to download
[env]
upload_port = COM11
@@ -37,6 +35,20 @@ extends = common_env_options
build_flags = -D USE_MODBUS_IP ;Importat configuration, this flags is used to configure the program
build_src_filter = -<*> +<Base_TCP> ;Add the specific folder path here
;----------------------------------------------------------------------------------------------------
[env:CRAH_HTS_PLC_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<BMS/CRAH/CRAH_HTS_PLC_TCP>
[env:CRAH_PETRA_PAHHC_600_C6_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<BMS/CRAH/CRAH_PETRA_PAHHC_600_C6_TCP>
[env:POD_MBB_Power_Meter_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
@@ -199,4 +211,73 @@ build_src_filter = -<*> +<EPMS/UPS/UPS_Vertiv_APM2_TCP>
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_src_filter = -<*> +<BMS/CHILLER/CH_York_YVAA_RTU>
build_src_filter = -<*> +<BMS/CHILLER/CH_York_YVAA_RTU>
[env:PHX3_VFD_ABB_ACH580_RTU]
platform = espressif32
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>
[env:CRAH_UMAS_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<BMS/CRAH/CRAH_UMAS_TCP>
[env:GEN_HSE]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<EPMS/GEN/GEN_HSE>
[env:SEL_2440_MVG]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<Base_TCP>
[env:MVG_SC_EC_M505_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP,
build_src_filter = -<*> +<EPMS/MVG/MVG_SC_EC_M505_TCP>
[env:GEN_CAT_GCCP_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP,
build_src_filter = -<*> +<EPMS/GEN/GEN_CAT_GCCP_TCP>
[env:CDU_CoolIT_Oracle_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<BMS/CDU/CDU_CoolIT_Oracle_TCP>
[env:RPP_Cortex_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<EPMS/RPP/RPP_Cortex_TCP>

View File

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

View File

@@ -0,0 +1,136 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Totalizer.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("TT01", new SingleValueStrategy(870.0F, 10.0f, 1000));
addStrategy("TT02", new SingleValueStrategy(880.0F, 10.0f, 1000));
addStrategy("TT31", new SingleValueStrategy(670.0F, 10.0f, 1000));
addStrategy("TT41", new SingleValueStrategy(660.0F, 10.0f, 1000));
addStrategy("PT01", new SingleValueStrategy(350.0F, 10.0f, 1000));
addStrategy("PT02", new SingleValueStrategy(380.0F, 10.0f, 1000));
addStrategy("PT31", new SingleValueStrategy(340.0F, 10.0f, 1000));
addStrategy("PT41", new SingleValueStrategy(370.0F, 10.0f, 1000));
addStrategy("PT32", new SingleValueStrategy(380.0F, 10.0f, 1000));
addStrategy("PT42", new SingleValueStrategy(350.0F, 10.0f, 1000));
addStrategy("PT21", new SingleValueStrategy(370.0F, 10.0f, 1000));
addStrategy("PT11", new SingleValueStrategy(390.0F, 10.0f, 1000));
addStrategy("AirTemp", new SingleValueStrategy(660.0F, 1.0f, 1000));
addStrategy("DP31", new SingleValueStrategy(150.0F, 10.0f, 1000));
addStrategy("DP41", new SingleValueStrategy(180.0F, 10.0f, 1000));
addStrategy("DP", new SingleValueStrategy(160.0F, 10.0f, 1000));
addStrategy("FL01", new SingleValueStrategy(7420.0F, 10.0f, 1000));
addStrategy("P31_Speed", new SingleValueStrategy(300.0F, 10.0f, 1000));
addStrategy("P41_Speed", new SingleValueStrategy(410.0F, 10.0f, 1000));
addStrategy("F1_Speed", new SingleValueStrategy(180.0F, 10.0f, 1000));
addStrategy("F2_Speed", new SingleValueStrategy(190.0F, 10.0f, 1000));
addStrategy("F3_Speed", new SingleValueStrategy(170.0F, 10.0f, 1000));
addStrategy("F4_Speed", new SingleValueStrategy(200.0F, 10.0f, 1000));
addStrategy("F5_Speed", new SingleValueStrategy(250.0F, 10.0f, 1000));
addStrategy("F6_Speed", new SingleValueStrategy(210.0F, 10.0f, 1000));
addStrategy("F7_Speed", new SingleValueStrategy(200.0F, 10.0f, 1000));
addStrategy("F8_Speed", new SingleValueStrategy(180.0F, 10.0f, 1000));
addStrategy("AirTemp", new SingleValueStrategy(680.0f, 100.0f, 5000));
addStrategy("Group_Flow", new SingleValueStrategy(7510.0F, 10.0f, 1000));
addStrategy("Group_DP", new SingleValueStrategy(200.0F, 10.0f, 1000));
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. It reads the "ON/OFF Command By BMS" point. If it's 0, it transitions to StandbyState.
* 2. It reads the "Fault Code" point. If it's non-zero, it transitions to FailState,
* passing the corresponding alarm description.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float State_Ctrl = getPointValue(equipment, "Remote_Start");
if (State_Ctrl == 0){
return new StandbyState<ModbusIP>();
}
float TT01 = getPointValue(equipment, "TT01");
float TT02 = getPointValue(equipment, "TT02");
float TT31 = getPointValue(equipment, "TT31");
float TT41 = getPointValue(equipment, "TT41");
float PT01 = getPointValue(equipment, "PT01");
float PT02 = getPointValue(equipment, "PT02");
float PT31 = getPointValue(equipment, "PT31");
float PT41 = getPointValue(equipment, "PT41");
setPointValue(equipment, "TT01_TT02", (TT01 + TT02)/2.0f);
setPointValue(equipment, "TT31_TT41", (TT31 + TT41)/2.0f);
setPointValue(equipment, "PT01_PT02", (PT01 + PT02)/2.0f);
setPointValue(equipment, "PT31_PT41", (PT31 + PT41)/2.0f);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Run Status" for all EC fans to 1 to indicate they are active.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "Status", 1);
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}

View File

@@ -0,0 +1,130 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the StandbyState, which defines
* the behavior of the equipment when it is in an idle or standby mode.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new StandbyState object.
*
* In this state, the equipment is idle. This constructor initializes strategies
* to bring the system to a safe, idle condition. It sets a stable value for
* the SAT reading and creates ramp strategies to bring the CW valve and all
* EC fan speeds down to zero.
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
addStrategy("TT01", new SingleValueStrategy(870.0F, 10.0f, 1000));
addStrategy("TT02", new SingleValueStrategy(870.0F, 10.0f, 1000));
addStrategy("TT31", new SingleValueStrategy(870.0F, 10.0f, 1000));
addStrategy("TT41", new SingleValueStrategy(870.0F, 10.0f, 1000));
addStrategy("PT01", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("PT02", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("PT31", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("PT41", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("PT32", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("PT42", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("PT21", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("PT11", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("DP31", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("DP41", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("DP", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("FL01", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("AirTemp", new SingleValueStrategy(870.0F, 10.0f, 1000));
addStrategy("P31_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("P41_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F1_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F2_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F3_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F4_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F5_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F6_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F7_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("F8_Speed", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("Group_Flow", new SingleValueStrategy(1.0F, 1.0f, 1000));
addStrategy("Group_DP", new SingleValueStrategy(1.0F, 1.0f, 1000));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method applies the strategies defined for the standby state (e.g.,
* ramping values to zero).
*
* @warning This method currently does not check for a command to transition to the
* Running state. This logic needs to be added to allow the unit to start.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
float State_Ctrl = getPointValue(equipment, "Remote_Start");
if (State_Ctrl == 1){
return new RunningState<ModbusIP>();
}
float TT01 = getPointValue(equipment, "TT01");
float TT02 = getPointValue(equipment, "TT02");
float TT31 = getPointValue(equipment, "TT31");
float TT41 = getPointValue(equipment, "TT41");
float PT01 = getPointValue(equipment, "PT01");
float PT02 = getPointValue(equipment, "PT02");
float PT31 = getPointValue(equipment, "PT31");
float PT41 = getPointValue(equipment, "PT41");
setPointValue(equipment, "TT01_TT02", (TT01 + TT02)/2.0f);
setPointValue(equipment, "TT31_TT41", (TT31 + TT41)/2.0f);
setPointValue(equipment, "PT01_PT02", (PT01 + PT02)/2.0f);
setPointValue(equipment, "PT31_PT41", (PT31 + PT41)/2.0f);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* This method performs cleanup by setting all alarm points and all EC fan
* run status points to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Status", 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,129 @@
/**
* @file config.h
* @brief Main configuration file for the CRAH Unit (TCP) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "Oracle_SA"; /**< @brief The SSID of the WiFi network. */
const char *password = "Prime!123"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 38, 23); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 38, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 0, 0, "Status"},
{HR, 1, 0, "Group"},
{HR, 2, 0, "TT01"},
{HR, 3, 0, "TT02"},
{HR, 4, 0, "TT31"},
{HR, 5, 0, "TT41"},
{HR, 6, 0, "PT01"},
{HR, 7, 0, "PT02"},
{HR, 8, 0, "PT31"},
{HR, 9, 0, "PT41"},
{HR, 10, 0, "PT32"},
{HR, 11, 0, "PT42"},
{HR, 12, 0, "PT21"},
{HR, 13, 0, "PT11"},
{HR, 14, 0, "TT01_TT02"},
{HR, 15, 0, "TT31_TT41"},
{HR, 16, 0, "PT01_PT02"},
{HR, 17, 0, "PT31_PT41"},
{HR, 18, 0, "DP31"},
{HR, 19, 0, "DP41"},
{HR, 20, 0, "DP"},
{HR, 21, 0, "FL01"},
{HR, 22, 0, "P31_Speed"},
{HR, 23, 0, "P41_Speed"},
{HR, 24, 0, "F1_Speed"},
{HR, 25, 0, "F2_Speed"},
{HR, 26, 0, "F3_Speed"},
{HR, 27, 0, "F4_Speed"},
{HR, 28, 0, "F5_Speed"},
{HR, 29, 0, "F6_Speed"},
{HR, 30, 0, "F7_Speed"},
{HR, 31, 0, "F8_Speed"},
{HR, 33, 0, "AirTemp"},
{HR_FLOAT, 40, 0, "Group_Flow"},
{HR_FLOAT, 42, 0, "Group_DP"},
{HR, 44, 0, "Version"},
{HR, 200, 0, "Temp_SP"},
{HR, 201, 0, "DP_SP"},
{HR, 202, 0, "Flow_SP"},
{COIL, 0, 0, "Alarm"},
{COIL, 1, 0, "Alarm_Ack"},
{COIL, 5, 0, "OvrPressure"},
{COIL, 14, 0, "Ntwk_Fault"},
{COIL, 15, 0, "Unit_Available"},
{COIL, 33, 0, "OvrTemp"},
{COIL, 86, 0, "LD01"},
{COIL, 87, 0, "StpBtn"},
{COIL, 131, 0, "Critical_Fault"},
{COIL, 132, 0, "Power_Fault"},
{COIL, 133, 0, "PLC_Fault"},
{COIL, 200, 0, "Remote_Start"},
};
//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

@@ -22,11 +22,11 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 32, 35); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else

View File

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

View File

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

View File

@@ -0,0 +1,134 @@
/**
* @file config.h
* @brief Main configuration file for the CRAH Unit (TCP) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "QTS_ATL_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "Fayetteville123"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 25, 123); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 25, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 49, 0, "Total Ariflow"},
{HR_FLOAT, 31, 0, "Airflow Effectiveness"},
{HR_FLOAT, 33, 0, "Return Humidity"},
{HR_FLOAT, 35, 0, "Return Air Temp"},
{HR_FLOAT, 37, 0, "Return Dew Point"},
{HR_FLOAT, 55, 0, "Supply Air Temp"},
{HR_FLOAT, 27, 0, "Cooling Valve Output"},
{HR_FLOAT, 29, 0, "Feedback Differential"},
{HR, 23, 0, "Airflow Used"},
{HR, 24, 0, "Available Airflow"},
{HR_FLOAT, 47, 0, "Cooling Capacity"},
{HR_FLOAT, 45, 0, "Net Sensible Cooling Capacity"},
{HR_FLOAT, 43, 0, "Fan Time in Hrs"},
{HR_FLOAT, 51, 0, "Differential Air Temp"},
{HR_FLOAT, 39, 0, "Fan Speed"},
{HR, 20, 0, "Heartbeat"},
{HR_FLOAT, 41, 0, "Air Temp Setpoint"},
{HR_FLOAT, 25, 0, "Fan Speed Setpoint"},
{HR, 17, 0, "Pump Run Status"},
{HR, 18, 0, "Pump Health"},
{HR, 22, 0, "Pump High Float"},
{DI, 2, 0, "Common Alarm"},
{DI, 12, 0, "Smoke Detected"},
{DI, 13, 0, "Water Under Foot"},
{DI, 14, 0, "Check Air Filter"},
{DI, 15, 0, "Fan Issue"},
{DI, 16, 0, "Alternate Power Source"},
{DI, 8, 0, "Unit Status"},
{DI, 7, 0, "Loss of Air Flow"},
{DI, 8, 0, "Cooling State Input"},
{DI, 6, 0, "Unit Local"},
{HR, 4, 0, "Alarm Acknowledged"},
//{DI, 2, 0, "Operator Status (Input)"},
//{COIL, 2, 0, "Operator Status (Output)"},
//{DI, 2, 0, "Program Status (Input)"},
//{COIL, 2, 0, "Program Status (Output)"},
//{DI, 2, 0, "Running Status"},
//{DI, 2, 0, "Not Ready Status"},
//{DI, 2, 0, "Start Command (Input)"},
//{COIL, 2, 0, "Start Command (Output)"},
//{DI, 2, 0, "Stop Command (Input)"},
//{COIL, 2, 0, "Stop Command (Output)"},
//{DI, 2, 0, "Reset (Input)"},
//{COIL, 2, 0, "Reset (Output)"},
//{DI, 2, 0, "Start Command (Input)"},
//{DI, 2, 0, "Stopped Status"},
//{DI, 2, 0, "Error Status"},
//{DI, 2, 0, "Not Ready Fail"},
//{DI, 2, 0, "Starting Status"},
//{DI, 2, 0, "Stopping Status"},
//
//{HR_FLOAT, 2, 0, "Air Temp Setpoint (Output)"},
//{HR_FLOAT, 2, 0, "Fan Speed Setpoint (Output)"},
};
//Size of modbus map used in FOR cycles, automatically calculated.
/**
* @brief The total number of entries in the `mb_map` array.
* This is calculated at compile time and used for iterating over the map.
*/
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
/** @brief The main loop update interval in milliseconds. */
int interval = 250;
/** @} */ // End of ModbusMapConfig group
#endif // CONFIG_H

View File

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

View File

@@ -38,16 +38,26 @@
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("CW Valve Position", new PIDStrategy("RAT Setpoint", 1000, "RAT"));
addStrategy("Operating Hours EC Fan #1", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #2", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #3", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #4", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #5", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #6", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #7", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #8", new TotalizerStrategy(10000));
addStrategy("Operating Hours EC Fan #9", new TotalizerStrategy(10000));
addStrategy("CW Valve Position", new PIDStrategy("SAT Setpoint", 2000, "SAT Reading"));
addStrategy("SAT Reading", new SingleValueStrategy(0.0f, 3.0f, 1000));
addStrategy("Speed EC Fan #1", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #2", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #3", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #4", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #5", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #6", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #7", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #8", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Speed EC Fan #9", new RampStrategy(0.0f, 500.0f, 1000));
addStrategy("Operating Hours EC Fan #1", new TotalizerStrategy(1100));
addStrategy("Operating Hours EC Fan #2", new TotalizerStrategy(1200));
addStrategy("Operating Hours EC Fan #3", new TotalizerStrategy(1300));
addStrategy("Operating Hours EC Fan #4", new TotalizerStrategy(1250));
addStrategy("Operating Hours EC Fan #5", new TotalizerStrategy(1350));
addStrategy("Operating Hours EC Fan #6", new TotalizerStrategy(1450));
addStrategy("Operating Hours EC Fan #7", new TotalizerStrategy(1150));
addStrategy("Operating Hours EC Fan #8", new TotalizerStrategy(1180));
addStrategy("Operating Hours EC Fan #9", new TotalizerStrategy(1340));
}
/**
@@ -74,63 +84,36 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
return new StandbyState<ModbusIP>();
}
Modbus_Point<ModbusIP>* faultCode = equipment->getModbus_Point("Fault Code");
int faultCodeValue = faultCode ? faultCode->getValue() : 0;
switch (faultCodeValue){
case 1:
return new FailState<ModbusIP>({"Alarm SAT Sensor Fault"});
case 2:
return new FailState<ModbusIP>({"Alarm RAH Sensor Fault"});
case 3:
return new FailState<ModbusIP>({"Alarm RAT Sensor Fault"});
case 4:
return new FailState<ModbusIP>({"Alarm Filter DP Sensor Fault"});
case 5:
return new FailState<ModbusIP>({"Alarm Flooding"});
case 6:
return new FailState<ModbusIP>({"Alarm Dirty Filter"});
case 7:
return new FailState<ModbusIP>({"Alarm High RAT"});
case 8:
return new FailState<ModbusIP>({"Alarm Low RAT"});
case 9:
return new FailState<ModbusIP>({"Alarm High SAT"});
case 10:
return new FailState<ModbusIP>({"Alarm Low SAT"});
case 11:
return new FailState<ModbusIP>({"Alarm High RAH"});
case 12:
return new FailState<ModbusIP>({"Alarm Low RAH"});
case 13:
return new FailState<ModbusIP>({"Alarm Phase Failure"});
case 14:
return new FailState<ModbusIP>({"Alarm Condensate Pump"});
case 15:
return new FailState<ModbusIP>({"Alarm Smoke"});
case 16:
return new FailState<ModbusIP>({"Alarm Fire"});
case 17:
return new FailState<ModbusIP>({"Alarm EC Fan #1"});
case 18:
return new FailState<ModbusIP>({"Alarm EC Fan #2"});
case 19:
return new FailState<ModbusIP>({"Alarm EC Fan #3"});
case 20:
return new FailState<ModbusIP>({"Alarm EC Fan #4"});
case 21:
return new FailState<ModbusIP>({"Alarm EC Fan #5"});
case 22:
return new FailState<ModbusIP>({"Alarm EC Fan #6"});
case 23:
return new FailState<ModbusIP>({"Alarm EC Fan #7"});
case 24:
return new FailState<ModbusIP>({"Alarm EC Fan #8"});
case 25:
return new FailState<ModbusIP>({"Alarm EC Fan #9"});
default:
break;
}
float rat = getPointValue(equipment, "RAT");
setPointValue(equipment, "RAT Reading", rat);
float speed = getPointValue(equipment, "Setting EC Fan Speed");
Strategy_Behavior* fan1_rs = getStrategy("Speed EC Fan #1");
static_cast<RampStrategy*>(fan1_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan2_rs = getStrategy("Speed EC Fan #2");
static_cast<RampStrategy*>(fan2_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan3_rs = getStrategy("Speed EC Fan #3");
static_cast<RampStrategy*>(fan3_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan4_rs = getStrategy("Speed EC Fan #4");
static_cast<RampStrategy*>(fan4_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan5_rs = getStrategy("Speed EC Fan #5");
static_cast<RampStrategy*>(fan5_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan6_rs = getStrategy("Speed EC Fan #6");
static_cast<RampStrategy*>(fan6_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan7_rs = getStrategy("Speed EC Fan #7");
static_cast<RampStrategy*>(fan7_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan8_rs = getStrategy("Speed EC Fan #8");
static_cast<RampStrategy*>(fan8_rs)->setTarget(4200.0f * (speed /100.0f));
Strategy_Behavior* fan9_rs = getStrategy("Speed EC Fan #9");
static_cast<RampStrategy*>(fan9_rs)->setTarget(4200.0f * (speed /100.0f));
float value = getPointValue(equipment, "CW Valve Position");
Serial.printf("CW Valve Position: %0.2f\n", value);
float sat_setpoint = getPointValue(equipment, "SAT Setpoint");
Strategy_Behavior* sat_svs = getStrategy("SAT Reading");
static_cast<SingleValueStrategy*>(sat_svs)->setSetpoint(sat_setpoint);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 33, 11); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
@@ -60,81 +60,82 @@
*/
modbusMap mb_map[] =
{
{HR, 15, 0, "State Control"}, //Internal to control from Modscan
{HR, 16, 0, "Fault Code"},
{HR_FLOAT, 18, 0, "RAT"}, //Internal Fault code from Modscan
{HR_FLOAT, 1, 0, "SAT Setpoint"},
{HR_FLOAT, 681, 0, "RAT Setpoint"},
{HR_FLOAT, 111, 0, "High RAT Limit"},
{HR_FLOAT, 114, 0, "Low RAT Limit"},
{HR_FLOAT, 118, 0, "High SAT Limit"},
{HR_FLOAT, 122, 0, "Low SAT Limit"},
{HR_FLOAT, 685, 0, "High RAH Limit"},
{HR_FLOAT, 689, 0, "Low RAH Limit"},
{HR, 5, 0, "Setting the EC Fan Max Speed"},
{HR, 695, 0, "Setting the EC Fan Min Speed"},
{HR_FLOAT, 693, 0, "Setting Room Temp"},
{HR, 691, 0, "Setting EC Fan Speed "},
{DI, 146, 0, "Alarm SAT Sensor Fault"},
{DI, 1246, 0, "Alarm RAH Sensor Fault"},
{DI, 1245, 0, "Alarm RAT Sensor Fault"},
{DI, 1250, 0, "Alarm Filter DP Sensor Fault"},
{DI, 51, 0, "Alarm Flooding"},
{DI, 1096, 0, "Alarm Dirty Filter"},
{DI, 1367, 0, "Alarm High RAT"},
{DI, 1099, 0, "Alarm Low RAT"},
{DI, 118, 0, "Alarm High SAT"},
{DI, 122, 0, "Alarm Low SAT"},
{DI, 1307, 0, "Alarm High RAH"},
{DI, 1308, 0, "Alarm Low RAH"},
{DI, 1342, 0, "Alarm Common"},
{DI, 148, 0, "Alarm Phase Failure"},
{DI, 1370, 0, "Alarm Condensate Pump"},
{DI, 1368, 0, "Alarm Smoke"},
{DI, 1369, 0, "Alarm Fire"},
{DI, 131, 0, "Alarm EC Fan #1"},
{DI, 132, 0, "Alarm EC Fan #2"},
{DI, 133, 0, "Alarm EC Fan #3"},
{DI, 134, 0, "Alarm EC Fan #4"},
{DI, 135, 0, "Alarm EC Fan #5"},
{DI, 136, 0, "Alarm EC Fan #6"},
{DI, 1360, 0, "Alarm EC Fan #7"},
{DI, 1361, 0, "Alarm EC Fan #8"},
{DI, 1362, 0, "Alarm EC Fan #9"},
{DI, 138, 0, "Run Status EC Fan #1"},
{DI, 139, 0, "Run Status EC Fan #2"},
{DI, 140, 0, "Run Status EC Fan #3"},
{DI, 141, 0, "Run Status EC Fan #4"},
{DI, 142, 0, "Run Status EC Fan #5"},
{DI, 143, 0, "Run Status EC Fan #6"},
{DI, 1363, 0, "Run Status EC Fan #7"},
{DI, 1364, 0, "Run Status EC Fan #8"},
{DI, 1365, 0, "Run Status EC Fan #9"},
{IR_FLOAT, 99, 0, "SAT Reading"},
{IR_FLOAT, 70, 0, "RAH Reading"},
{IR_FLOAT, 101, 0, "RAT Reading"},
{IR_FLOAT, 106, 0, "Filter DP Reading"},
{IR_FLOAT, 496, 0, "CW Valve Position"},
{IR, 53, 0, "Speed EC Fan #1"},
{IR, 228, 0, "Speed EC Fan #2"},
{IR, 229, 0, "Speed EC Fan #3"},
{IR, 230, 0, "Speed EC Fan #4"},
{IR, 231, 0, "Speed EC Fan #5"},
{IR, 232, 0, "Speed EC Fan #6"},
{IR, 678, 0, "Speed EC Fan #7"},
{IR, 679, 0, "Speed EC Fan #8"},
{IR, 680, 0, "Speed EC Fan #9"},
{IR, 274, 0, "Operating Hours EC Fan #1"},
{IR, 233, 0, "Operating Hours EC Fan #2"},
{IR, 244, 0, "Operating Hours EC Fan #3"},
{IR, 235, 0, "Operating Hours EC Fan #4"},
{IR, 236, 0, "Operating Hours EC Fan #5"},
{IR, 245, 0, "Operating Hours EC Fan #6"},
{IR, 486, 0, "Operating Hours EC Fan #7"},
{IR, 487, 0, "Operating Hours EC Fan #8"},
{IR, 488, 0, "Operating Hours EC Fan #9"},
{COIL, 301, 0, "ON/OFF Command By BMS"},
{COIL, 302, 0, "Enable Off By Supervisory"},
{HR, 13, 0, "Delta"},
{HR, 14, 0, "State Control"}, //Internal to control from Modscan
{HR, 15, 0, "Fault Code"},
{HR_FLOAT, 17, 0, "RAT"}, //Internal Fault code from Modscan
{HR_FLOAT, 0, 0, "SAT Setpoint"},
{HR_FLOAT, 680, 0, "RAT Setpoint"},
{HR_FLOAT, 110, 0, "High RAT Limit"},
{HR_FLOAT, 113, 0, "Low RAT Limit"},
{HR_FLOAT, 117, 0, "High SAT Limit"},
{HR_FLOAT, 121, 0, "Low SAT Limit"},
{HR_FLOAT, 684, 0, "High RAH Limit"},
{HR_FLOAT, 688, 0, "Low RAH Limit"},
{HR, 4, 0, "Setting the EC Fan Max Speed"},
{HR, 694, 0, "Setting the EC Fan Min Speed"},
{HR_FLOAT, 692, 0, "Setting Room Temp"},
{HR_FLOAT, 690, 0, "Setting EC Fan Speed"},
{DI, 145, 0, "Alarm SAT Sensor Fault"},
{DI, 1245, 0, "Alarm RAH Sensor Fault"},
{DI, 1244, 0, "Alarm RAT Sensor Fault"},
{DI, 1249, 0, "Alarm Filter DP Sensor Fault"},
{DI, 50, 0, "Alarm Flooding"},
{DI, 1095, 0, "Alarm Dirty Filter"},
{DI, 1366, 0, "Alarm High RAT"},
{DI, 1098, 0, "Alarm Low RAT"},
{DI, 117, 0, "Alarm High SAT"},
{DI, 121, 0, "Alarm Low SAT"},
{DI, 1306, 0, "Alarm High RAH"},
{DI, 1307, 0, "Alarm Low RAH"},
{DI, 1341, 0, "Alarm Common"},
{DI, 147, 0, "Alarm Phase Failure"},
{DI, 1369, 0, "Alarm Condensate Pump"},
{DI, 1367, 0, "Alarm Smoke"},
{DI, 1368, 0, "Alarm Fire"},
{DI, 130, 0, "Alarm EC Fan #1"},
{DI, 131, 0, "Alarm EC Fan #2"},
{DI, 132, 0, "Alarm EC Fan #3"},
{DI, 133, 0, "Alarm EC Fan #4"},
{DI, 134, 0, "Alarm EC Fan #5"},
{DI, 135, 0, "Alarm EC Fan #6"},
{DI, 1359, 0, "Alarm EC Fan #7"},
{DI, 1360, 0, "Alarm EC Fan #8"},
{DI, 1361, 0, "Alarm EC Fan #9"},
{DI, 137, 0, "Run Status EC Fan #1"},
{DI, 138, 0, "Run Status EC Fan #2"},
{DI, 139, 0, "Run Status EC Fan #3"},
{DI, 140, 0, "Run Status EC Fan #4"},
{DI, 141, 0, "Run Status EC Fan #5"},
{DI, 142, 0, "Run Status EC Fan #6"},
{DI, 1362, 0, "Run Status EC Fan #7"},
{DI, 1363, 0, "Run Status EC Fan #8"},
{DI, 1364, 0, "Run Status EC Fan #9"},
{IR_FLOAT, 98, 0, "SAT Reading"},
{IR_FLOAT, 69, 0, "RAH Reading"},
{IR_FLOAT, 100, 0, "RAT Reading"},
{IR_FLOAT, 105, 0, "Filter DP Reading"},
{IR_FLOAT, 495, 0, "CW Valve Position"},
{IR, 52, 0, "Speed EC Fan #1"},
{IR, 227, 0, "Speed EC Fan #2"},
{IR, 228, 0, "Speed EC Fan #3"},
{IR, 229, 0, "Speed EC Fan #4"},
{IR, 230, 0, "Speed EC Fan #5"},
{IR, 231, 0, "Speed EC Fan #6"},
{IR, 677, 0, "Speed EC Fan #7"},
{IR, 678, 0, "Speed EC Fan #8"},
{IR, 679, 0, "Speed EC Fan #9"},
{IR, 273, 0, "Operating Hours EC Fan #1"},
{IR, 232, 0, "Operating Hours EC Fan #2"},
{IR, 243, 0, "Operating Hours EC Fan #3"},
{IR, 234, 0, "Operating Hours EC Fan #4"},
{IR, 235, 0, "Operating Hours EC Fan #5"},
{IR, 244, 0, "Operating Hours EC Fan #6"},
{IR, 485, 0, "Operating Hours EC Fan #7"},
{IR, 486, 0, "Operating Hours EC Fan #8"},
{IR, 487, 0, "Operating Hours EC Fan #9"},
{COIL, 300, 0, "ON/OFF Command By BMS"},
{COIL, 301, 0, "Enable Off By Supervisory"},
{COIL, 264, 0, "Alarm Reset"}
};
//Size of modbus map used in FOR cycles, automatically calculated.

View File

@@ -75,6 +75,8 @@ void updateAlarms(Equipment<ModbusIP>* equipment){
"Alarm Condensate Pump ON", "Alarm Fire ON", "Alarm Smoke ON"
};
// NOTE: Per UMAS hardwire signals, alarm opened in case of normal operation, closed in case of alarm condition
// 0: no alarm, 1: alarm
int numAlarms = 0;
for (int i =0; i< alarmCommands.size() && i < alarmDescriptions.size(); ++i) {
Modbus_Point<ModbusIP>* commandPoint = equipment->getModbus_Point(alarmCommands[i]);
@@ -110,7 +112,6 @@ void updateAnalogs(Equipment<ModbusIP>* equipment){
else if (equipment->getModbus_Point("RA Temp High Alarm ON")->getValue()==1){
equipment->setModbus_Point("Return Air Temp", 104.0f);
}
else equipment->setModbus_Point("Return Air Temp", 74.0f);
if (equipment->getModbus_Point("RA Humidity Low Alarm ON")->getValue()==1){
equipment->setModbus_Point("Return Air Humidity", 15.0f);
@@ -118,5 +119,4 @@ void updateAnalogs(Equipment<ModbusIP>* equipment){
else if (equipment->getModbus_Point("RA Humidity High Alarm ON")->getValue()==1){
equipment->setModbus_Point("Return Air Humidity", 65.0f);
}
else equipment->setModbus_Point("Return Air Humidity", 35.0f);
}

View File

@@ -16,6 +16,7 @@
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Saw.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
@@ -39,6 +40,9 @@ FailState<ModbusIP>::FailState(const std::vector<std::string>& activeAlarms) {
// Fan speed --> 0, Run Status --> 0, Amps --> 0
addStrategy("CW Valve Position", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Supply Air Temp", new SingleValueStrategy(74.0f, 1.0f, 1000));
addStrategy("Return Air Temp", new SingleValueStrategy(86.0f, 1.0f, 1000));
addStrategy("Return Air Humidity", new SingleValueStrategy(35.0f, 2.0f, 1000));
addStrategy("Speed Fan 1", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 2", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 3", new RampStrategy(0.0f, 10.0f, 1000));
@@ -57,7 +61,7 @@ FailState<ModbusIP>::FailState(const std::vector<std::string>& activeAlarms) {
addStrategy("Amps Fan 7", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 8", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 9", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("CRAH Heartbeat", new SawStrategy(0.0f, 60.0f, 1.0f, 1000));
}
/**
@@ -82,8 +86,8 @@ State<ModbusIP>* FailState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "ON/OFF Command By BMS", 0);
// The only way to exit the Fail State is for Leak Detect Alarm to turn off, then enter Standby State.
bool leakDetected = equipment->getModbus_Point("Alarm Leak Detect");
if (leakDetected == 0){
bool leakDetected = getPointValue(equipment, "Alarm Leak Detect");
if (!leakDetected){
return new StandbyState<ModbusIP>();
}
@@ -93,7 +97,7 @@ State<ModbusIP>* FailState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
/**
* @brief Logic to execute once when entering the fail state.
* When entering failed state, turn all fans off (fan status --> 0) and set BMS Command --> 0
* When entering failed state, turn all fans off (fan status --> 1) and set BMS Command --> 0
* @param equipment Pointer to the Equipment instance.
*/
template<>
@@ -107,7 +111,8 @@ void FailState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 0
// Per UMAS submittal Hardwired Run Status signals- 0: fans running, 1: fans stopped
// Loop through and set all motor statuses to 1
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {

View File

@@ -71,11 +71,13 @@ RunningState<ModbusIP>::RunningState() {
addStrategy("Operating Hours Fan 7", new TotalizerStrategy(1000));
addStrategy("Operating Hours Fan 8", new TotalizerStrategy(1000));
addStrategy("Operating Hours Fan 9", new TotalizerStrategy(1000));
addStrategy("Supply Air Temp", new SawStrategy(60.0f, 100.0f, 2.0f, 1000)); // Won't initialize at lower bound; always initializes at 0 b/c FLOAT; initialize manually via Modscan
addStrategy("Return Air Temp", new SawStrategy(70.0f, 80.0f, 1.0f, 1000));
addStrategy("Supply Air Temp", new SawStrategy(60.0f, 100.0f, 2.2f, 1000)); // Won't initialize at lower bound; always initializes at 0 b/c FLOAT; initialize manually via Modscan
addStrategy("Return Air Humidity", new SawStrategy(25.0f, 40.0f, 1.1f, 1000));
addStrategy("Return Air Temp", new SawStrategy(70.0f, 80.0f, 0.8f, 1000));
addStrategy("Filter Differential Pressure", new SawStrategy(0.0f, 5.0f, 0.2f, 1000));
addStrategy("CW Valve Position", new PIDStrategy("Supply Air Temp Setpoint", 1000, "Supply Air Temp")); // SAT must be greater than SAT Setpoint for this PID to work.
addStrategy("CRAH Heartbeat", new SawStrategy(0.0f, 60.0f, 1.0f, 1000));
addStrategy("Fan Speed Feedback", new RampStrategy(0.0f, 1.0f, 200));
}
/**
@@ -114,7 +116,6 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
// Check to see if BMS Command set to OFF --> Place unit in Standby
// Removed logic of placing unit on standby if BMS_Enable_Source != 2 for ease in testing Mode Feedback.
if (On_Off_Command == 0){
setPointValue(equipment, "ON/OFF Command By BMS", 0);
return new StandbyState<ModbusIP>();
}
@@ -142,6 +143,12 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
}
}
// Fan Speed Feedback dynamically ramp to Fan Speed Setpoint sent to Arduino
Strategy_Behavior* speedFeedback = getStrategy("Fan Speed Feedback");
if (speedFeedback){
static_cast<RampStrategy*>(speedFeedback)->setTarget(BMS_Speed_Setpoint);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -164,7 +171,8 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 1
// Per UMAS submittal Hardwired Run Status signals- 0: fans running, 1: fans stopped
// Loop through and set all motor statuses to 0
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
@@ -175,7 +183,7 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* Sets the "Run Status" for all EC fans to 1 (stopped) before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
@@ -188,7 +196,8 @@ void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 0
// Per UMAS submittal Hardwired Run Status signals- 0: fans running, 1: fans stopped
// Loop through and set all motor statuses to 1
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {

View File

@@ -41,8 +41,9 @@ StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed.
// These strategies are applied at the end of the update function.
addStrategy("CW Valve Position", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Supply Air Temp", new RampStrategy(74.0f, 1.0f, 1000));
addStrategy("Return Air Temp", new RampStrategy(86.0f, 1.0f, 1000));
addStrategy("Supply Air Temp", new SingleValueStrategy(74.0f, 1.0f, 1000));
addStrategy("Return Air Temp", new SingleValueStrategy(86.0f, 1.0f, 1000));
addStrategy("Return Air Humidity", new SingleValueStrategy(35.0f, 2.0f, 1000));
addStrategy("Speed Fan 1", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 2", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 3", new RampStrategy(0.0f, 10.0f, 1000));
@@ -61,7 +62,7 @@ StandbyState<ModbusIP>::StandbyState() {
addStrategy("Amps Fan 7", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 8", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 9", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("CRAH Heartbeat", new SawStrategy(0.0f, 60.0f, 1.0f, 1000));
}
/**
@@ -105,7 +106,7 @@ State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
/**
* @brief Logic to execute once when entering the standby state.
* This method performs cleanup by setting all EC fan run status points to 0.
* This method performs cleanup by setting all EC fan run status points to 1 (stopped).
* The BMS Command is also set to OFF.
* @param equipment Pointer to the Equipment instance.
*/
@@ -120,7 +121,8 @@ void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 0
// Per UMAS submittal Hardwired Run Status signals- 0: fans running, 1: fans stopped
// Loop through and set all motor statuses to 1
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {

View File

@@ -21,10 +21,10 @@
* @{
*/
#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. */
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 32, 62); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
@@ -126,7 +126,7 @@ modbusMap mb_map[] =
{IR, 52, 0, "Operating Hours Fan 7"},
{IR, 56, 0, "Operating Hours Fan 8"},
{IR, 60, 0, "Operating Hours Fan 9"},
{IR, 61, 0, "Control Mode Selected"},
{IR, 61, 0, "Control Mode Selected"}, // 0: BMS+Speed, 1: BMS+Room Temp, 2: Return Temp
{IR_FLOAT, 63, 0, "Amps Fan 1"},
{IR_FLOAT, 65, 0, "Amps Fan 2"},
{IR_FLOAT, 67, 0, "Amps Fan 3"},
@@ -135,14 +135,17 @@ modbusMap mb_map[] =
{IR_FLOAT, 73, 0, "Amps Fan 6"},
{IR_FLOAT, 75, 0, "Amps Fan 7"},
{IR_FLOAT, 77, 0, "Amps Fan 8"},
{IR_FLOAT, 79, 0, "Amps Fan 9"},
{IR_FLOAT, 79, 0, "Amps Fan 9"},
{IR_FLOAT, 99, 0, "CRAH Heartbeat"},
{HR_FLOAT, 13, 0, "Fan Speed Setpoint"}, // Receive signal from PLC
{HR_FLOAT, 17, 0, "Supply Air Temp Setpoint"}, // Receive signal from PLC
{HR_FLOAT, 21, 0, "Fan Min Speed"}, // Send to PLC
{HR_FLOAT, 23, 0, "Fan Max Speed"}, // Send to PLC
{HR, 25, 0, "BMS Control Source"}, // Receive signal from PLC
{HR, 26, 0, "BMS Enable Source"}, // Receive signal from PLC
{HR, 25, 0, "BMS Control Source"}, // Receive signal from PLC 0:Speed, 1:Room Temp
{HR, 26, 2, "BMS Enable Source"}, // Receive signal from PLC 0:Keypad, 1:DI, 2:BMS
{HR_FLOAT, 28, 0, "Fan Speed Feedback"},
{HR_FLOAT, 99, 0, "PLC Heartbeat"}, // This will be seconds from PLC - if doesn't change for 15 seconds set BMS Enable Source to Local (0)
};
//Size of modbus map used in FOR cycles, automatically calculated.

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,227 @@
/**
* @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
/** The purpose of this function is to change the RA Temp, SA Temp, RA Humidity values for the purpose of testing alarms in Ignition.
* There are LOW and HIGH coils, which when activated will set the analog to a low/high range.
* The NORMAL coil will send a one-shot to set the associated analog value back into a normal range.
* If in a RunningState, the analog value will follow it's normal function (typically a sawStrategy).
*/
void updateAnalogs(Equipment<ModbusIP>* equipment){
if (equipment->getModbus_Point("RA Temp NORMAL")->getValue()==1){
equipment->setModbus_Point("Return Air Temp", 84.0f);
equipment->setModbus_Point("RA Temp Low Alarm ON", 0);
equipment->setModbus_Point("RA Temp High Alarm ON", 0);
equipment->setModbus_Point("RA Temp NORMAL", 0);
}
else if (equipment->getModbus_Point("RA Temp Low Alarm ON")->getValue() ==1){
equipment->setModbus_Point("Return Air Temp", 60.0f);
equipment->setModbus_Point("RA Temp High Alarm ON", 0);
}
else if (equipment->getModbus_Point("RA Temp High Alarm ON")->getValue()==1){
equipment->setModbus_Point("Return Air Temp", 110.0f);
equipment->setModbus_Point("RA Temp Low Alarm ON", 0);
}
if (equipment->getModbus_Point("RA Humidity NORMAL")->getValue()==1){
equipment->setModbus_Point("Return Humidity", 25.0f);
equipment->setModbus_Point("RA Humidity Low Alarm ON", 0);
equipment->setModbus_Point("RA Humidity High Alarm ON", 0);
equipment->setModbus_Point("RA Humidity NORMAL", 0);
}
else if (equipment->getModbus_Point("RA Humidity Low Alarm ON")->getValue()==1){
equipment->setModbus_Point("Return Humidity", 5.0f);
equipment->setModbus_Point("RA Humidity High Alarm ON", 0);
}
else if (equipment->getModbus_Point("RA Humidity High Alarm ON")->getValue()==1){
equipment->setModbus_Point("Return Humidity", 80.0f);
equipment->setModbus_Point("RA Humidity Low Alarm ON", 0);
}
if (equipment->getModbus_Point("SA Temp NORMAL")->getValue()==1){
equipment->setModbus_Point("Supply Air Temp", 76.0f);
equipment->setModbus_Point("SA Temp Low Alarm ON", 0);
equipment->setModbus_Point("SA Temp High Alarm ON", 0);
equipment->setModbus_Point("SA Temp NORMAL", 0);
}
else if (equipment->getModbus_Point("SA Temp Low Alarm ON")->getValue() ==1){
equipment->setModbus_Point("Supply Air Temp", 60.0f);
equipment->setModbus_Point("SA Temp High Alarm ON", 0);
}
else if (equipment->getModbus_Point("SA Temp High Alarm ON")->getValue()==1){
equipment->setModbus_Point("Supply Air Temp", 90.0f);
equipment->setModbus_Point("SA Temp Low Alarm ON", 0);
}
}
//This function updates the Alarm bit for the Return Air Temp
void updateReturnAirTempAlarms(Equipment<ModbusIP>* equipment){
Modbus_Point<ModbusIP>* returnAirTemp = equipment -> getModbus_Point("Return Air Temp");
Modbus_Point<ModbusIP>* returnTempHighAlarmSP = equipment->getModbus_Point("Return Air Temp Alarm High SP");
Modbus_Point<ModbusIP>* returnTempLowAlarmSP = equipment->getModbus_Point("Return Air Temp Alarm Low SP");
if (returnAirTemp->getValue() < returnTempLowAlarmSP->getValue()) {
equipment->setModbus_Point("Alarm Low Return Air Temp", 1);
equipment->setModbus_Point("Alarm High Return Air Temp", 0);
}
else if (returnAirTemp->getValue() > returnTempHighAlarmSP->getValue()) {
equipment->setModbus_Point("Alarm High Return Air Temp", 1);
equipment->setModbus_Point("Alarm Low Return Air Temp", 0);
}
else{
equipment->setModbus_Point("Alarm High Return Air Temp", 0);
equipment->setModbus_Point("Alarm Low Return Air Temp", 0);
}
}
// This function updates the Alarm bit for the Supply Air Temp
void updateSupplyAirTempAlarms(Equipment<ModbusIP>* equipment){
Modbus_Point<ModbusIP>* supplyAirTemp = equipment -> getModbus_Point("Supply Air Temp");
Modbus_Point<ModbusIP>* supplyTempHighAlarmSP = equipment->getModbus_Point("Supply Air Temp Alarm High SP");
Modbus_Point<ModbusIP>* supplyTempLowAlarmSP = equipment->getModbus_Point("Supply Air Temp Alarm Low SP");
if (supplyAirTemp->getValue() > supplyTempHighAlarmSP->getValue()) {
equipment->setModbus_Point("Alarm High Supply Temp", 1);
equipment->setModbus_Point("Alarm Low Supply Temp", 0);
}
else if (supplyAirTemp->getValue() < supplyTempLowAlarmSP->getValue()) {
equipment->setModbus_Point("Alarm Low Supply Temp", 1);
equipment->setModbus_Point("Alarm High Supply Temp", 0);
}
else{
equipment->setModbus_Point("Alarm High Supply Temp", 0);
equipment->setModbus_Point("Alarm Low Supply Temp", 0);
}
}
//This function updates the Alarm bit for the Return Humidity
void updateReturnHumidityAlarms(Equipment<ModbusIP>* equipment){
Modbus_Point<ModbusIP>* returnHumidity = equipment -> getModbus_Point("Return Humidity");
Modbus_Point<ModbusIP>* returnHumHighAlarmSP = equipment->getModbus_Point("Return Humidity Alarm High SP");
Modbus_Point<ModbusIP>* returnHumLowAlarmSP = equipment->getModbus_Point("Return Humidity Alarm Low SP");
if (returnHumidity->getValue() > returnHumHighAlarmSP->getValue()) {
equipment->setModbus_Point("Alarm High Return Humidity", 1);
equipment->setModbus_Point("Alarm Low Return Humidity", 0);
}
else if (returnHumidity->getValue() < returnHumLowAlarmSP->getValue()) {
equipment->setModbus_Point("Alarm Low Return Humidity", 1);
equipment->setModbus_Point("Alarm High Return Humidity", 0);
}
else{
equipment->setModbus_Point("Alarm High Return Humidity", 0);
equipment->setModbus_Point("Alarm Low Return Humidity", 0);
}
}
/**
* @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"
};
const std::vector<std::string> alarmAnalogs = {
"Alarm High Return Air Temp", "Alarm Low Return Air Temp", "Alarm High Return Humidity", "Alarm Low Return Humidity",
"Alarm High Supply Temp", "Alarm Low Supply Temp"
};
// If any Alarm Commands = 1, set the appropriate Alarm = 1 and increment counter for number of active alarms
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 any of the analog alarms = 1, increment counter for number of active alarms
for (int j = 0; j<alarmAnalogs.size(); ++j){
Modbus_Point<ModbusIP>* alarmAnalogPoint = equipment->getModbus_Point(alarmAnalogs[j]);
if (alarmAnalogPoint) {
if (alarmAnalogPoint->getValue() == 1) numAlarms++;
}
}
// If any alarms are active, set the Common Alarm = 1, else Common Alarm = 0.
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,48 @@
/**
* @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;
void updateAnalogs(Equipment<ModbusIP>* equipment);
void updateReturnAirTempAlarms(Equipment<ModbusIP>* equipment);
void updateSupplyAirTempAlarms(Equipment<ModbusIP>* equipment);
void updateReturnHumidityAlarms(Equipment<ModbusIP>* equipment);
/**
* @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,103 @@
/**
* @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(25.0f, 35.0f, 1.0f, 2000));
addStrategy("Return Air Temp", new SingleValueStrategy(80.0f, 1.0f, 3000));
addStrategy("Supply Air Temp", new SingleValueStrategy(76.0f, 1.0f, 3000));
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
updateAnalogs(equipment);
updateReturnAirTempAlarms(equipment);
updateSupplyAirTempAlarms(equipment);
updateReturnHumidityAlarms(equipment);
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,140 @@
/**
* @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(25.0f, 35.0f, 1.0f, 2000));
addStrategy("Return Air Temp", new SawStrategy(78.0f, 88.0f, 1.0f, 3000));
addStrategy("Supply Air Temp", new SawStrategy(73.0f, 77.0f, 1.0f, 5000));
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");
updateAnalogs(equipment);
updateReturnAirTempAlarms(equipment);
updateSupplyAirTempAlarms(equipment);
updateReturnHumidityAlarms(equipment);
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,122 @@
/**
* @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(25.0f, 35.0f, 1.0f, 2000));
addStrategy("Return Air Temp", new SingleValueStrategy(80.0f, 1.0f, 3000));
addStrategy("Supply Air Temp", new SingleValueStrategy(76.0f, 1.0f, 3000));
addStrategy("Supply Air Flow", new RampStrategy(0.0f, 1.0f, 5000));
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");
updateAnalogs(equipment);
updateReturnAirTempAlarms(equipment);
updateSupplyAirTempAlarms(equipment);
updateReturnHumidityAlarms(equipment);
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,195 @@
/**
* @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 = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 32, 66); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 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
{COIL, 32, 0, "RA Temp Low Alarm ON"}, // For Arduino testing only - sets RA Temp = 60
{COIL, 33, 0, "RA Temp NORMAL"}, // For Arduino testing only - sets RA Temp = 84
{COIL, 34, 0, "RA Temp High Alarm ON"}, // For Arduino testing only - sets RA Temp = 110
{COIL, 35, 0, "RA Humidity Low Alarm ON"}, // For Arduino testing only - sets RA Humidity = 5
{COIL, 36, 0, "RA Humidity NORMAL"}, // For Arduino testing only - sets RA Humidity = 25
{COIL, 37, 0, "RA Humidity High Alarm ON"}, // For Arduino testing only - sets RA Humidity = 80
{COIL, 38, 0, "SA Temp Low Alarm ON"}, // For Arduino testing only - sets SA Temp = 60
{COIL, 39, 0, "SA Temp NORMAL"}, // For Arduino testing only - sets SA Temp = 76
{COIL, 40, 0, "SA Temp High Alarm ON"}, // For Arduino testing only - sets SA Temp = 90
{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, 250, "Return Humidity"},
{IR_10x, 742, 840, "Return Air Temp"},
{IR_10x, 743, 760, "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_10x, 53, 600, "Return Humidity Alarm High SP"},
{HR_10x, 54, 200, "Return Humidity Alarm Low SP"},
{HR, 56, 0, "RAHum Value"},
{HR, 57, 0, "RAHumHighAlm Value"},
{HR, 58, 0, "RAHumLowAlm Value"},
{HR, 732, 73, "Supply Air Temp Setpoint"},
{HR_10x, 738, 1000, "Return Air Temp Alarm High SP"},
{HR_10x, 739, 720, "Return Air Temp Alarm Low SP"},
{HR, 741, 0, "RATemp Value"},
{HR, 742, 0, "RATempHighAlm Value"},
{HR, 743, 0, "RATempLowAlm Value"},
{HR, 753, 80, "Return Air Temp Setpoint"},
{HR_10x, 754, 780, "Supply Air Temp Alarm High SP"},
{HR_10x, 755, 720, "Supply Air Temp Alarm Low SP"},
{HR, 757, 0, "SATemp Value"},
{HR, 758, 0, "SATempHighAlm Value"},
{HR, 759, 0, "SATempLowAlm Value"},
};
//Size of modbus map used in FOR cycles, automatically calculated.
/**
* @brief The total number of entries in the `mb_map` array.
* This is calculated at compile time and used for iterating over the map.
*/
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
/** @brief The main loop update interval in milliseconds. */
int interval = 250;
/** @} */ // End of ModbusMapConfig group
#endif // CONFIG_H

View File

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

View File

@@ -0,0 +1,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

View File

@@ -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 <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#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<ModbusIP>* equipment){
const std::vector<std::string> 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<ModbusIP>* 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<ModbusIP>* equipment){
if (equipment->getModbus_Point("Airflow ON")->getValue() == 1){
equipment->setModbus_Point("Airflow Proving Switch", 1);
}
else equipment->setModbus_Point("Airflow Proving Switch", 0);
}

View File

@@ -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 <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
template <typename T>
class State;
/**
* @brief Updates all alarms, safety interlock, alarms present register.
* @param equipment Pointer to the Equipment instance.
* @return void
*/
void updateAlarms(Equipment<ModbusIP>* 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<ModbusIP>* equipment);

View File

@@ -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 <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#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<ModbusIP>::FailState(const std::vector<std::string>& 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<ModbusIP>* FailState<ModbusIP>::update(Equipment<ModbusIP>* 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<ModbusIP>();
}
_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<ModbusIP>::enterState(Equipment<ModbusIP>* 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<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,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 <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 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<ModbusIP>::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<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* 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<std::string> activeAlarmsDesc = {}; // sending a blank string to FailState, b/c that parameter not used in FailState implementation.
return new FailState<ModbusIP>(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<ModbusIP>();
}
// 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<RampStrategy*>(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<ModbusIP>::enterState(Equipment<ModbusIP>* 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<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}

View File

@@ -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 <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 will have
* Duct RH fluctuate around 35% for visualization purposes only.
*
*/
template<>
StandbyState<ModbusIP>::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<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);
updateAirflow(equipment);
// if Alarms are present (as updated in updateAlarms function) --> FailState
bool alarmsPresent = getPointValue(equipment, "Alarms Present");
if (alarmsPresent){
std::vector<std::string> activeAlarmsDesc = {}; // sending a blank string to FailState, b/c that parameter not used in FailState implementation.
return new FailState<ModbusIP>(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<ModbusIP>();
}
// 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<ModbusIP>::enterState(Equipment<ModbusIP>* 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<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,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 <ModbusIP_ESP8266.h>
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 32, 68); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 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, "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

View File

@@ -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 <WiFi.h>
#include "config.h"
#include "ModbusPoints/Modbus_PointFactory.h"
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
//=================================================================================================================================
/**
* @brief Initializes the application.
* @details This function runs once at startup. It configures the serial communication,
* Wi-Fi, and the Modbus server. It also creates and initializes all the Modbus points
* based on the `mb_map` array in `config.h`.
*/
void setup() {
Serial.begin(115200); //Serial comm start
WiFi.config(local_IP, gateway, subnet); // Wifi service start
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("Connected!!");
mb.server(); //Modbus server start
Serial.println("Server Created");
Serial.println(map_size);
for(int i = 0; i < map_size; i++){
Modbus_Point<ModbusIP>* point = createModbus_Point(&mb, mb_map[i].category, mb_map[i].address, mb_map[i].value, mb_map[i].description);
if (point) {
point->addToModbusServer();
EquipmentInstance.addModbus_Point(mb_map[i].description, point);
}
}
Serial.println("All modbus Points created");
Serial.println("Setup function ended");
}
//=================================================================================================================================
/**
* @brief The main application loop.
* @details This function runs repeatedly after setup() has completed. It performs two main actions:
* 1. It continuously services the Modbus server by calling `mb.task()` to handle
* incoming requests from a Modbus master.
* 2. At a fixed interval (defined in `config.h`), it calls `EquipmentInstance.update()`
* to run the emulator's internal state machine and behavior logic.
*/
void loop() {
mb.task();
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
unsigned long startTime = millis();
EquipmentInstance.update();
unsigned long endTime = millis();
unsigned long elapsedTime = endTime - startTime;
Serial.printf("Control Execution time: %d ms\n", elapsedTime);
}
}

View File

@@ -0,0 +1,40 @@
# VFD ABB ACH580 RTU
## Brief Introduction
This is based on a 50hp motor, 480V, 65 FLA, 60Hz, 1800 rpm (PHX3 DC1/2).
Modbus addresses are based on 32-bit registers.
## List of Equipment
This configuration has been used for these models:
* **ACH580**: 10-23-2025 (PHX3)
## Hardware Prerequisites
The code is written for an ESP8266/ESP32-style microcontroller with WiFi capabilities and at least one hardware serial port for RS485 communication.
* **Microcontroller**: [Firebeetle 2 ESP32.](https://www.dfrobot.com/product-2231.html)
* **RS485 Transceiver**: [RS485 Shield for Arduino.](https://www.dfrobot.com/product-1024.html)
---
## States and Strategies
The hardwire IO signals to/from VFD/PLC are Start Cmd, Stop Cmd, Speed Command, Speed Feedback, Run Status, Fault Status.
User needs to set the speed command (HR 150) in RPM from the PLC
User needs to set the Start command (HR 151) from the PLC
It appears these hard IO registers were arbitrarily chosen for the purpose of this Arduino simulation.
The registers selected are based on FS Config file from CDR project. Run Status and Fault Status registers were added for simulation.
### Standby State
* **Equipment**: Equipment parameters go back to 0
### Running State
* **Ramp Strategy**: The following regisers will dynamically ramp based upon the Speed Cmd:
* Motor Speed Used, Motor Speed estimated, Output Frequency, Motor Current, Motor Torque, DC Voltage, Output Voltage, Output Power
* The logic is based on Affinity laws and nominal motor values stated in the Introduction section.
* **Square Strategy**: Inverter Temperature switches between 40 and 80 based on inherited code.
* **Totalizers Strategy**: Inverter kWh cnt, Hours Run
### Fail State
* Enters Fail State if Fault Status is set to 0.
While in Fail State, the Start/Stop command is reset to 0.
The only way to exit Fail State is if Fault Status = 1 --> Standby State.

View File

@@ -0,0 +1,97 @@
/**
* @file State_Fail.cpp
* @brief Implementation of the FailState class.
* @author Robert J Davis
* @date 2025-10-30
*
* This file contains the implementation for the FailState, which defines
* the behavior of the equipment when it has entered a fault condition.
*
*/
#include "States/State_Standby.h"
#include "States/State_Fail.h"
#include "ModbusPoints/Modbus_Point.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_PID.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new FailState object.
*
* This constructor sets the associated analog signals to the same values as Standby.
*/
template<>
FailState<ModbusRTU>::FailState(const std::vector<std::string>& activeAlarms) {
addStrategy("Speed Feedback", new RampStrategy(0.0f, 200.0f, 1000 ));
addStrategy("Motor Current", new RampStrategy(0.0f, 20.0f, 1000 ));
addStrategy("Motor Torque", new RampStrategy(0.0f, 20.0f, 1000 ));
addStrategy("Inverter Temperature", new RampStrategy(0.0f, 1.0f, 1000 ));
addStrategy("Output Frequency", new SingleValueStrategy(0.1f, 0.2f, 1000 ));
addStrategy("Output Voltage", new SingleValueStrategy(0.1f, 0.1f, 1000 ));
addStrategy("DC Voltage", new SingleValueStrategy(0.1f, 0.1f, 1000 ));
addStrategy("Motor Shaft Power", new SingleValueStrategy(0.1f, 0.1f, 1000 ));
}
/**
* @brief Executes the fail state's logic for one update cycle.
*
* While in FailState, the Unit cannot be started and the Start/Stop command is reset to 0.
* When the fault is 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<ModbusRTU>* FailState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Fail update function");
int faultNotPresent = getPointValue(equipment, "Fault Status");
if(faultNotPresent == 1){
return new StandbyState<ModbusRTU>();
}
setPointValue(equipment, "Start/Stop", 0);
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the fail state. Sets the Run Status to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Fail State...");
setPointValue(equipment, "Speed Scaling", 1800);
setPointValue(equipment, "Frequency Scaling", 60);
setPointValue(equipment, "Nominal Current", 65);
setPointValue(equipment, "Nominal Voltage", 480);
setPointValue(equipment, "Nominal Frequency", 60);
setPointValue(equipment, "Nominal Speed", 1800);
setPointValue(equipment, "Nominal Power", 50);
setPointValue(equipment, "Run Status", 0);
}
/**
* @brief Logic to execute once when exiting the fail state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Fail State...");
}

View File

@@ -0,0 +1,165 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Robert J Davis
* @date 2025-10-22
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "Strategies/Strategy_Behavior.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Totalizer.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_Square.h"
#include "Equipment/Equipment.h"
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state, such as speed feedback, current, torque, hours run, etc.
* These values are based on a 50hp motor, 480V, 65 FLA, 60Hz, 1800 rpm (PHX3 DC1/2)
*/
template<>
RunningState<ModbusRTU>::RunningState() {
addStrategy("Speed Feedback", new RampStrategy(1800.0f, 100.0f, 1000));
addStrategy("Motor Current", new RampStrategy(65.0f, 7.0f, 1000));
addStrategy("Motor Torque", new RampStrategy(90.0f, 10.0f, 1000));
addStrategy("Inverter Temperature", new SquareStrategy(40.0f, 80.0f, 1000));
addStrategy("Output Frequency", new RampStrategy(60.0f, 3.0f, 1000 ));
addStrategy("Output Voltage", new RampStrategy(480.0f, 15.0f, 1000 ));
addStrategy("DC Voltage", new RampStrategy(678.0f, 20.0f, 1000 ));
addStrategy("Motor Shaft Power", new RampStrategy(36.7f, 2.0f, 1000 ));
addStrategy("Inverter MWh counter", new TotalizerStrategy(1000));
addStrategy("Inverter kWh counter", new TotalizerStrategy(1000));
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. If Fault is 0 (there is a fault present) --> FailState
* 2. It reads the "Stop/Start" command point (from PLC). If it's 0, it transitions to StandbyState.
*
* If no transition occurs, it updates values according to speed 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<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
// If Fault Status = 0, there is a fault --> FailState
int faultNotPresent = getPointValue(equipment, "Fault Status");
if(faultNotPresent == 0){
return new FailState<ModbusRTU>({"Fault Status"});
}
int VFD_Start_Stop = getPointValue(equipment, "Start/Stop");
if (VFD_Start_Stop == 0){
return new StandbyState<ModbusRTU>();
}
float speed_pct = getPointValue(equipment, "Speed Cmd") / 1800.0f;
// Based on Affinity Laws. Motor: 65 FLA, 480V, 60Hz, 1800 rpm, 50hp
float voltage_update = speed_pct * 480;
float dc_voltage_update = speed_pct * 678;
float current_update = speed_pct * speed_pct * 65;
float torque_update = speed_pct * speed_pct * 100; // This is a % of nominal motor torque
float freq_update = speed_pct * 60;
float power_update = speed_pct * speed_pct * speed_pct * 36.77f; // 50 hp ~ 36.77kW
float currentSP = getPointValue(equipment, "Speed Cmd");
Strategy_Behavior* speedFeedback = getStrategy("Speed Feedback");
if (speedFeedback) {
static_cast<RampStrategy*>(speedFeedback)->setTarget(currentSP);
}
Strategy_Behavior* frequencystrategy = getStrategy("Output Frequency");
if (frequencystrategy) {
static_cast<RampStrategy*>(frequencystrategy)->setTarget(freq_update);
}
Strategy_Behavior* currentstrategy = getStrategy("Motor Current");
if (currentstrategy) {
static_cast<RampStrategy*>(currentstrategy)->setTarget(current_update);
}
Strategy_Behavior* torquestrategy = getStrategy("Motor Torque");
if (torquestrategy) {
static_cast<RampStrategy*>(torquestrategy)->setTarget(torque_update);
}
Strategy_Behavior* dcvoltagestrategy = getStrategy("DC Voltage");
if (dcvoltagestrategy) {
static_cast<RampStrategy*>(dcvoltagestrategy)->setTarget(dc_voltage_update);
}
Strategy_Behavior* voltagestrategy = getStrategy("Output Voltage");
if (voltagestrategy) {
static_cast<RampStrategy*>(voltagestrategy)->setTarget(voltage_update);
}
Strategy_Behavior* powerstrategy = getStrategy("Motor Shaft Power");
if (powerstrategy) {
static_cast<RampStrategy*>(powerstrategy)->setTarget(power_update);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the Run Status" point to indicate the unit is running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
setPointValue(equipment, "Speed Scaling", 1800);
setPointValue(equipment, "Frequency Scaling", 60);
setPointValue(equipment, "Nominal Current", 65);
setPointValue(equipment, "Nominal Voltage", 480);
setPointValue(equipment, "Nominal Frequency", 60);
setPointValue(equipment, "Nominal Speed", 1800);
setPointValue(equipment, "Nominal Power", 50);
setPointValue(equipment, "Run Status", 1);
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Output Frequency" to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
setPointValue(equipment, "Output Frequency", 0.0f);
}

View File

@@ -0,0 +1,103 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Robert J Davis
* @date 2025-10-23
*
* This file contains the implementation for the StandbyState, which defines
* the behavior of the equipment when it is in an idle or standby mode.
*/
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_SingleValue.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new StandbyState object.
*
* In this state, the equipment is idle. This constructor initializes several
* strategies to simulate a live but non-operational unit. Most values are ramped down to 0.
*/
template<>
StandbyState<ModbusRTU>::StandbyState() {
addStrategy("Speed Feedback", new RampStrategy(0.0f, 200.0f, 1000 ));
addStrategy("Motor Current", new RampStrategy(0.0f, 20.0f, 1000 ));
addStrategy("Motor Torque", new RampStrategy(0.0f, 20.0f, 1000 ));
addStrategy("Inverter Temperature", new RampStrategy(0.0f, 1.0f, 1000 ));
addStrategy("Output Frequency", new SingleValueStrategy(0.1f, 0.2f, 1000 ));
addStrategy("Output Voltage", new SingleValueStrategy(0.1f, 0.1f, 1000 ));
addStrategy("DC Voltage", new SingleValueStrategy(0.1f, 0.1f, 1000 ));
addStrategy("Motor Shaft Power", new SingleValueStrategy(0.1f, 0.1f, 1000 ));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. If Fault is 0 (there is a fault present) --> FailState
* 2. It reads the "Start/Stop" point (from PLC). If it's 1 --> RunningState
*
* If no transition is requested, it applies the strategies defined for the standby state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* StandbyState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
// If Fault Status = 0, there is a fault --> FailState
int faultNotPresent = getPointValue(equipment, "Fault Status");
if(faultNotPresent == 0){
return new FailState<ModbusRTU>({"Fault Status"});
}
int VFD_Start_Stop = getPointValue(equipment, "Start/Stop");
if (VFD_Start_Stop == 1){
return new RunningState<ModbusRTU>();
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* Sets the "Run Status" point to indicate the unit is not running.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Speed Scaling", 1800);
setPointValue(equipment, "Frequency Scaling", 60);
setPointValue(equipment, "Nominal Current", 65);
setPointValue(equipment, "Nominal Voltage", 480);
setPointValue(equipment, "Nominal Frequency", 60);
setPointValue(equipment, "Nominal Speed", 1800);
setPointValue(equipment, "Nominal Power", 50);
setPointValue(equipment, "Run Status", 0);
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
}

View File

@@ -0,0 +1,104 @@
/**
* @file config.h
* @brief Main configuration file for the ABB ACH580 VFD (RTU) emulator.
* @author Robert J Davis
* @date 2025-10-22
*
* This file contains important configurations for the Modbus RTU communication
* and the specific register map for the emulated device.
* These are 32-bit modbus registers.
* Added "Run Status" and "Fault Status" to simulated hard IO points and send feedback to PLC during simulation.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <ModbusRTU.h>
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 149, 1650, "Speed Cmd"}, // arbitrary register number - receive signal from PLC (hardwire IO in field); expecting rpm (1800 rpm max)
{HR, 151, 1, "Start/Stop"}, // arbitrary register number - receive signal from PLC (hardwire IO in practice)
{HR, 152, 0, "HOA Command"}, // arbitrary register number - not used in program
{HR, 154, 0, "Run Status"}, // arbitrary register number - 0:off, 1:on (simulated hardwire IO) sending feedback to PLC during simulation.
{HR, 155, 1, "Fault Status"}, // arbitrary register number - 0:faulted, 1:not faulted (simulated hardwire IO). When = 0, will turn off VFD.
// {HR, 156, 0, "Speed Feedback"}, // arbitrary register number - send signal to PLC (simulated hardwire IO). Will be equal to Motor Speed Used register
{HR, 100, 0, "Speed Feedback"}, // 1800 rpm max
{HR, 105, 0, "Output Frequency"}, // 60 Hz @100% speed
{HR, 106, 0, "Motor Current"}, // 65 FLA
{HR_10x, 109, 0, "Motor Torque"}, // % of nominal torque
{HR_10x, 110, 0, "DC Voltage"}, // approx 678 VDC @100% speed
{HR, 112, 0, "Output Voltage"}, // 480 VAC
{HR_10x, 116, 0, "Motor Shaft Power"}, // 50 hp ~ 36.77 kW
{HR, 118, 0, "Inverter MWh counter"},
{HR_10x, 119, 0, "Inverter kWh counter"},
{HR, 510, 0, "Inverter Temperature"}, // RJD: Changed from HR_10x to HR, % of fault limit
{HR, 519, 0, "Diagnostic Word"}, // not used in program. Bit 9:Drive Over-Temp Alarm
{HR, 1000, 0, "DI Status"}, // not used in program.
{HR, 1211, 0, "AI1 Scaled"}, // not used in program.
{HR, 1221, 0, "AI2 Scaled"}, // not used in program.
{HR, 1310, 0, "AO1 Actual"}, // not used in program.
{HR, 1910, 0, "External Control Location"}, // not used in program.
{HR, 4600, 0, "Speed Scaling"}, // ADD: 1800 rpm
{HR, 4601, 0, "Frequency Scaling"}, // ADD: 60 Hz
{HR, 9905, 0, "Nominal Current"}, // ADD: 65 A
{HR_10x, 9906, 0, "Nominal Voltage"}, // ADD: 480 V
{HR_10x, 9907, 0, "Nominal Frequency"}, // ADD: 60 Hz
{HR, 9908, 0, "Nominal Speed"}, // ADD: 1800 rpm
{HR_10x, 9909, 0, "Nominal Power"}, // ADD: 50 hp
};
//Size of modbus map used in FOR cycles, automatically calculated.
/**
* @brief The total number of entries in the `mb_map` array.
* This is calculated at compile time and used for iterating over the map.
*/
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
/**
* @brief The main loop update interval in milliseconds.
*/
int interval = 250;
#endif // CONFIG_H

View File

@@ -0,0 +1,78 @@
/**
* @file main.cpp
* @brief Main execution program for the ABB ACH580 VFD (RTU) 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 a ABB ACH580 VFD unit. The program communicates via the
* Modbus RTU protocol over a serial connection.
*
* The setup() function initializes the following:
* - Serial communication for debugging.
* - A Modbus RTU server with parameters from config.h.
* - Modbus points (Coils, Holding Registers, etc.) based on a predefined map in config.h.
*
* The loop() function continuously:
* - Services the Modbus RTU server to handle incoming requests.
* - Periodically calls the main update loop for the emulated equipment, which
* manages state transitions and behavior strategies.
*
* @see config.h for Modbus RTU and register map configuration.
* @see Equipment.h for the main equipment logic.
* @see State.h for different equipment states.
* @see Strategies/Strategy_Behavior.h for value generation strategies.
* @see Modbus_Point.h for the base class for all Modbus points.
*/
//=================================================================================================================================
//Libraries and declaration of variables.
#include <Arduino.h>
#include "config.h"
#include "ModbusPoints/Modbus_PointFactory.h"
//=================================================================================================================================
/**
* @brief Initializes the application.
* @details This function runs once at startup. It configures the serial communication
* for debugging and the Modbus RTU server. It then creates and initializes all
* the Modbus points based on the `mb_map` array in `config.h`.
*/
const int rtsPin = 4;
void setup() {
Serial.begin(115200);
Serial.println("Setup function started");
Serial2.begin(BAUDRATE, SERIAL_8N1, RX_PIN, TX_PIN);
mb.begin(&Serial2, RST_PIN); // Start the server
mb.slave(MODBUS_ID); // Set the slave ID
for(int i = 0; i < map_size; i++){
Modbus_Point<ModbusRTU>* point = createModbus_Point(&mb, mb_map[i].category, mb_map[i].address, mb_map[i].value, mb_map[i].description);
if (point) {
point->addToModbusServer();
EquipmentInstance.addModbus_Point(mb_map[i].description, point);
}
}
Serial.println("Setup function ended");
}
//=================================================================================================================================
/**
* @brief The main application loop.
* @details This function runs repeatedly after setup() has completed. It performs two main actions:
* 1. It continuously services the Modbus server by calling `mb.task()` to handle
* incoming requests from a Modbus master.
* 2. At a fixed interval (defined in `config.h`), it calls `EquipmentInstance.update()`
* to run the emulator's internal state machine and behavior logic.
*/
void loop() {
mb.task();
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
unsigned long startTime = millis();
EquipmentInstance.update();
unsigned long endTime = millis();
unsigned long elapsedTime = endTime - startTime;
Serial.printf("Control Execution time: %d ms\n", elapsedTime);
}
}

View File

@@ -1,8 +1,8 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
* @author Emmanuel Hernandez Cruz, Robert J Davis
* @date 2025-10-22
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
@@ -41,8 +41,11 @@
*/
template<>
RunningState<ModbusRTU>::RunningState() {
addStrategy("Inverter Temperature", new SquareStrategy(40.0f, 80.0f,1000));
addStrategy("Inverter Temperature", new SquareStrategy(40.0f, 80.0f, 1000));
addStrategy("Motor Speed Used", new RampStrategy(100.0f, 10.0f, 1000));
addStrategy("Motor Speed Estimated", new RampStrategy(80.0f, 10.0f, 1000));
addStrategy("Motor Current", new RampStrategy(65.0f, 2.0f, 1000));
addStrategy("Motor Torque", new RampStrategy(200.0f, 10.0f, 1000));
}
/**

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
const char *ssid = "QTS_ATL_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "Fayetteville123"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 25, 115); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 25, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;

View File

@@ -39,20 +39,19 @@
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("Source 1 Volts AB", new SingleValueStrategy(0.0F, 0.0f, 1000));
addStrategy("Source 1 Volts BC", new SingleValueStrategy(0.0F, 0.0f, 1000));
addStrategy("Source 2 Volts CA", new SingleValueStrategy(0.0F, 0.0f, 1000));
addStrategy("Source 2 Volts AB", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 2 Volts BC", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 2 Volts CA", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 1 Frequency", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Source 2 Frequency", new SingleValueStrategy(60.0f, 1.0f, 1000));
addStrategy("Power Factor", new SingleValueStrategy(90.0f, 2.0f, 1000));
addStrategy("Amps A", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("Amps B", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("Amps C", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("Volts AB", new SingleValueStrategy(480.0f, 2.0f, 1000));
addStrategy("Votls BC", new SingleValueStrategy(480.0f, 2.0f, 1000));
addStrategy("Volts CA", new SingleValueStrategy(480.0f, 2.0f, 1000));
addStrategy("Amps A", new SingleValueStrategy(1.0f, 3.0f, 1000));
addStrategy("Amps B", new SingleValueStrategy(1.0f, 3.0f, 1000));
addStrategy("Amps C", new SingleValueStrategy(1.0f, 3.0f, 1000));
addStrategy("Total Active Power", new SingleValueStrategy(6.0f, 5.0f, 1000));
addStrategy("Total Apparent Power", new SingleValueStrategy(6.0f, 5.0f, 1000));
}
@@ -78,20 +77,40 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
return new StandbyState<ModbusIP>();
}
int I_load = getPointValue(equipment, "ATS_Load");
int I_rating = getPointValue(equipment, "ATS_Rating");
float load = static_cast<float>(I_load);
float rating = static_cast<float>(I_rating);
float real_load = rating * (load/100.0f);
float load = getPointValue(equipment, "ATS_Load");
float rating = getPointValue(equipment, "ATS_Rating");
float sim_load = rating * (load/100.0f);
Strategy_Behavior* ampsA_svs = getStrategy("Amps A");
Strategy_Behavior* ampsB_svs = getStrategy("Amps B");
Strategy_Behavior* ampsC_svs = getStrategy("Amps C");
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(sim_load);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(sim_load);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(sim_load);
// Apply any strategies defined for the standby state
float v_ab = getPointValue(equipment, "Source 2 Volts AB");
float v_bc = getPointValue(equipment, "Source 2 Volts BC");
float v_ca = getPointValue(equipment, "Source 2 Volts CA");
float i_a = getPointValue(equipment, "Amps A");
float i_b = getPointValue(equipment, "Amps B");
float i_c = getPointValue(equipment, "Ampc C");
float pwr = ((v_ab * i_a) + (v_bc * i_b) + (v_ca * i_c));
setPointValue(equipment, "Total Active Power", pwr*1000.0f);
float pf = getPointValue(equipment, "Power Factor");
float a_pwr = pwr * (pf/100.0f);
setPointValue(equipment, "Total Apparent Power", a_pwr*1000.0f);
float preferred = getPointValue(equipment, "ATS_Preferred");
if (preferred == 1.0f){
setPointValue(equipment, "Source 1 Preferred", 1.0f);
setPointValue(equipment, "Source 2 Preferred", 0.0f);
}
if (preferred == 2.0f){
setPointValue(equipment, "Source 1 Preferred", 0.0f);
setPointValue(equipment, "Source 2 Preferred", 1.0f);
}
_applyStrategies(equipment);
return nullptr;
}
@@ -113,6 +132,11 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "Source 1 Preferred", 1);
setPointValue(equipment, "Source 2 Preferred", 0);
setPointValue(equipment, "Source 1 Volts AB", 0.0f);
setPointValue(equipment, "Source 1 Volts BC", 0.0f);
setPointValue(equipment, "Source 1 Volts CA", 0.0f);
setPointValue(equipment, "Source 1 Frequency", 0.0f);
int transferQty = getPointValue(equipment, "Number of Transfers");
setPointValue(equipment, "Number of Transfers", transferQty + 1);
}

View File

@@ -37,21 +37,21 @@
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
addStrategy("Source 1 Volts AB", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 1 Volts BC", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 1 Volts CA", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 2 Volts AB", new SingleValueStrategy(0.0F, 0.0f, 1000));
addStrategy("Source 2 Volts BC", new SingleValueStrategy(0.0F, 0.0f, 1000));
addStrategy("Source 2 Volts CA", new SingleValueStrategy(0.0F, 0.0f, 1000));
addStrategy("Source 1 Frequency", new SingleValueStrategy(60.0f, 1.0f, 1000));
addStrategy("Source 2 Frequency", new SingleValueStrategy(0.0f, 0.0f, 1000));
// You can add initialization code here if needed
addStrategy("Source 1 Volts AB", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 1 Volts BC", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 1 Volts CA", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("Source 1 Frequency", new SingleValueStrategy(60.0f, 1.0f, 1000));
addStrategy("Power Factor", new SingleValueStrategy(90.0f, 2.0f, 1000));
addStrategy("Volts AB", new SingleValueStrategy(480.0f, 2.0f, 1000));
addStrategy("Votls BC", new SingleValueStrategy(480.0f, 2.0f, 1000));
addStrategy("Volts CA", new SingleValueStrategy(480.0f, 2.0f, 1000));
addStrategy("Amps A", new SingleValueStrategy(1.0f, 3.0f, 1000));
addStrategy("Amps B", new SingleValueStrategy(1.0f, 3.0f, 1000));
addStrategy("Amps C", new SingleValueStrategy(1.0f, 3.0f, 1000));
addStrategy("Total Active Power", new SingleValueStrategy(6.0f, 5.0f, 1000));
addStrategy("Total Apparent Power", new SingleValueStrategy(6.0f, 5.0f, 1000));
addStrategy("Power Factor", new SingleValueStrategy(90.0f, 2.0f, 1000));
addStrategy("Amps A", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("Amps B", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("Amps C", new SingleValueStrategy(1.0f, 5.0f, 1000));
}
/**
@@ -73,19 +73,42 @@ State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
return new RunningState<ModbusIP>();
}
int I_load = getPointValue(equipment, "ATS_Load");
int I_rating = getPointValue(equipment, "ATS_Rating");
float load = static_cast<float>(I_load);
float rating = static_cast<float>(I_rating);
float real_load = rating * (load/100.0f);
float load = getPointValue(equipment, "ATS_Load");
float rating = getPointValue(equipment, "ATS_Rating");
float sim_load = rating * (load/100.0f);
Strategy_Behavior* ampsA_svs = getStrategy("Amps A");
Strategy_Behavior* ampsB_svs = getStrategy("Amps B");
Strategy_Behavior* ampsC_svs = getStrategy("Amps C");
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(sim_load);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(sim_load);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(sim_load);
// Apply any strategies defined for the standby state
float v_ab = getPointValue(equipment, "Source 1 Volts AB");
float v_bc = getPointValue(equipment, "Source 1 Volts BC");
float v_ca = getPointValue(equipment, "Source 1 Volts CA");
float i_a = getPointValue(equipment, "Amps A");
float i_b = getPointValue(equipment, "Amps B");
float i_c = getPointValue(equipment, "Ampc C");
float pwr = ((v_ab * i_a) + (v_bc * i_b) + (v_ca * i_c));
setPointValue(equipment, "Total Active Power", pwr*1000.0f);
float pf = getPointValue(equipment, "Power Factor");
float a_pwr = pwr * (pf/100.0f);
setPointValue(equipment, "Total Apparent Power", a_pwr*1000.0f);
/*setPointValue(equipment, "S2 kW", kw);
setPointValue(equipment, "S2 MWh", mwh);*/
float preferred = getPointValue(equipment, "ATS_Preferred");
if (preferred == 1.0f){
setPointValue(equipment, "Source 1 Preferred", 1.0f);
setPointValue(equipment, "Source 2 Preferred", 0.0f);
}
if (preferred == 2.0f){
setPointValue(equipment, "Source 1 Preferred", 0.0f);
setPointValue(equipment, "Source 2 Preferred", 1.0f);
}
_applyStrategies(equipment);
return nullptr;
}
@@ -107,6 +130,11 @@ void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "Source 1 Preferred", 0);
setPointValue(equipment, "Source 2 Preferred", 1);
setPointValue(equipment, "Source 2 Volts AB", 0.0f);
setPointValue(equipment, "Source 2 Volts BC", 0.0f);
setPointValue(equipment, "Source 2 Volts CA", 0.0f);
setPointValue(equipment, "Source 2 Frequency", 0.0f);
int transferQty = getPointValue(equipment, "Number of Transfers");
setPointValue(equipment, "Number of Transfers", transferQty + 1);
}

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_ssid"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 15); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 33, 172); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
@@ -60,6 +60,7 @@
*/
modbusMap mb_map[] =
{
{HR, 8, 0, "ATS_Preferred"}, //Internal to control from Modscan
{HR, 9, 0, "ATS_Source"}, //Internal to control from Modscan
{HR, 10, 0, "ATS_Load"}, //Internal Fault code from Modscan
{HR, 11, 0, "ATS_Rating"}, //Internal Fault code from Modscan
@@ -86,9 +87,9 @@ modbusMap mb_map[] =
{IR, 6158, 0, "Amps A"},
{IR, 6159, 0, "Amps B"},
{IR, 6160, 0, "Amps C"},
{IR_LONG, 6165, 0, "Total Active Power"},
{IR_LONG, 6169, 0, "Total Apparent Power"},
{IR, 6171, 0, "Power Factor"},
{IR_LONG, 6165, 0, "Total Active Power"}, //0.001
{IR_LONG, 6169, 0, "Total Apparent Power"}, //0.001
{IR, 6171, 0, "Power Factor"}, //0.001
{IR, 6263, 0, "Number of Transfers"},
{IR, 6297, 0, "Alarm Status Bits"},

View File

@@ -39,15 +39,15 @@
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("S2 Volts AB", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("S2 Volts BC", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("S2 Volts CA", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("S2 Volts AB", new SingleValueStrategy(4800.0F, 10.0f, 1000));
addStrategy("S2 Volts BC", new SingleValueStrategy(4800.0F, 10.0f, 1000));
addStrategy("S2 Volts CA", new SingleValueStrategy(4800.0F, 10.0f, 1000));
addStrategy("PF", new SingleValueStrategy(90.0f, 2.0f, 1000));
addStrategy("PF", new SingleValueStrategy(910.0f, 20.0f, 1000));
addStrategy("S2 Amps A", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("S2 Amps B", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("S2 Amps C", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("S2 Amps A", new SingleValueStrategy(200.0f, 100.0f, 1000));
addStrategy("S2 Amps B", new SingleValueStrategy(200.0f, 100.0f, 1000));
addStrategy("S2 Amps C", new SingleValueStrategy(200.0f, 100.0f, 1000));
}
@@ -74,34 +74,47 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
}
float volts_AB = getPointValue(equipment, "S2 Volts AB");
float volts_BC = getPointValue(equipment, "S2 Volts BC");
float volts_AC = getPointValue(equipment, "S2 Volts CA");
float volts_CA = getPointValue(equipment, "S2 Volts CA");
setPointValue(equipment, "S2 Volts AN", volts_AB/1.732);
setPointValue(equipment, "S2 Volts BN", volts_BC/1.732);
setPointValue(equipment, "S2 Volts CN", volts_AC/1.732);
setPointValue(equipment, "S2 Volts CN", volts_CA/1.732);
int I_load = getPointValue(equipment, "ATS_Load");
int I_rating = getPointValue(equipment, "ATS_Rating");
float load = static_cast<float>(I_load);
float rating = static_cast<float>(I_rating);
float real_load = rating * (load/100.0f);
float sim_load = rating * (load/100.0f);
Strategy_Behavior* ampsA_svs = getStrategy("S2 Amps A");
Strategy_Behavior* ampsB_svs = getStrategy("S2 Amps B");
Strategy_Behavior* ampsC_svs = getStrategy("S2 Amps C");
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(sim_load*1000.0f);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(sim_load*1000.0f);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(sim_load*1000.0f);
float pf = getPointValue(equipment, "PF");
float get_pf = getPointValue(equipment, "PF");
float pf = get_pf/1000.0f;
float kw = (1.732f * ((volts_AB + volts_BC + volts_AC)/3.0f) * real_load * pf)/1000;
float kva = (1.732f * ((volts_AB + volts_BC + volts_AC)/3.0f) * real_load)/1000;
float real_v_AB = volts_AB/1000.0f;
float real_v_BC = volts_BC/1000.0f;
float real_v_CA = volts_CA/1000.0f;
float kw = (1.732f * ((real_v_AB + real_v_BC + real_v_CA)/3.0f) * sim_load * pf)*10;
float mwh = kw *600.0f;
setPointValue(equipment, "S2 kW", kw);
setPointValue(equipment, "S2 kVA", kva);
setPointValue(equipment, "S2 MWh", mwh);
float preferred = getPointValue(equipment, "ATS_Preferred");
if (preferred == 1.0f){
setBitValue(equipment, "Source Preferred", 9, true);
setBitValue(equipment, "Source Preferred", 8, false);
}
if (preferred == 2.0f){
setBitValue(equipment, "Source Preferred", 9, false);
setBitValue(equipment, "Source Preferred", 8, true);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -117,9 +130,6 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "Source Active", 32);
setPointValue(equipment, "Source Preferred", 512);
setPointValue(equipment, "S1 Volts AB", 0.0f);
setPointValue(equipment, "S1 Volts BC", 0.0f);
setPointValue(equipment, "S1 Volts CA", 0.0f);
@@ -129,6 +139,14 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "S1 Amps A", 0.0f);
setPointValue(equipment, "S1 Amps B", 0.0f);
setPointValue(equipment, "S1 Amps C", 0.0f);
setPointValue(equipment, "S1 kW", 0.0f);
setPointValue(equipment, "S1 MWh", 0.0f);
setBitValue(equipment, "Source Active", 4, false);
setBitValue(equipment, "Source Active", 3, true);
setBitValue(equipment, "Source Preferred", 8, false);
setBitValue(equipment, "Source Preferred", 9, true);
}
/**

View File

@@ -38,15 +38,15 @@
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
addStrategy("S1 Volts AB", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("S1 Volts BC", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("S1 Volts CA", new SingleValueStrategy(480.0F, 2.0f, 1000));
addStrategy("S1 Volts AB", new SingleValueStrategy(4800.0F, 10.0f, 1000));
addStrategy("S1 Volts BC", new SingleValueStrategy(4800.0F, 10.0f, 1000));
addStrategy("S1 Volts CA", new SingleValueStrategy(4800.0F, 10.0f, 1000));
addStrategy("PF", new SingleValueStrategy(90.0f, 2.0f, 1000));
addStrategy("PF", new SingleValueStrategy(910.0f, 20.0f, 1000));
addStrategy("S1 Amps A", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("S1 Amps B", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("S1 Amps C", new SingleValueStrategy(1.0f, 5.0f, 1000));
addStrategy("S1 Amps A", new SingleValueStrategy(200.0f, 100.0f, 1000));
addStrategy("S1 Amps B", new SingleValueStrategy(200.0f, 100.0f, 1000));
addStrategy("S1 Amps C", new SingleValueStrategy(200.0f, 100.0f, 1000));
}
/**
@@ -68,33 +68,46 @@ State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
}
float volts_AB = getPointValue(equipment, "S1 Volts AB");
float volts_BC = getPointValue(equipment, "S1 Volts BC");
float volts_AC = getPointValue(equipment, "S1 Volts CA");
float volts_CA = getPointValue(equipment, "S1 Volts CA");
setPointValue(equipment, "S1 Volts AN", volts_AB/1.732);
setPointValue(equipment, "S1 Volts BN", volts_BC/1.732);
setPointValue(equipment, "S1 Volts CN", volts_AC/1.732);
setPointValue(equipment, "S1 Volts CN", volts_CA/1.732);
int I_load = getPointValue(equipment, "ATS_Load");
int I_rating = getPointValue(equipment, "ATS_Rating");
float load = static_cast<float>(I_load);
float rating = static_cast<float>(I_rating);
float real_load = rating * (load/100.0f);
float sim_load = rating * (load/100.0f);
Strategy_Behavior* ampsA_svs = getStrategy("S1 Amps A");
Strategy_Behavior* ampsB_svs = getStrategy("S1 Amps B");
Strategy_Behavior* ampsC_svs = getStrategy("S1 Amps C");
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(real_load);
static_cast<SingleValueStrategy*>(ampsA_svs)->setSetpoint(sim_load*1000.0f);
static_cast<SingleValueStrategy*>(ampsB_svs)->setSetpoint(sim_load*1000.0f);
static_cast<SingleValueStrategy*>(ampsC_svs)->setSetpoint(sim_load*1000.0f);
float pf = getPointValue(equipment, "PF");
float get_pf = getPointValue(equipment, "PF");
float pf = get_pf/1000.0f;
float real_v_AB = volts_AB/1000.0f;
float real_v_BC = volts_BC/1000.0f;
float real_v_CA = volts_CA/1000.0f;
float kw = (1.732f * ((real_v_AB + real_v_BC + real_v_CA)/3.0f) * sim_load * pf)*10;
float mwh = kw *600.0f;
float kw = (1.732f * ((volts_AB + volts_BC + volts_AC)/3.0f) * real_load * pf)/1000;
float kva = (1.732f * ((volts_AB + volts_BC + volts_AC)/3.0f) * real_load)/1000;
float preferred = getPointValue(equipment, "ATS_Preferred");
if (preferred == 1.0f){
setBitValue(equipment, "Source Preferred", 9, true);
setBitValue(equipment, "Source Preferred", 8, false);
}
if (preferred == 2.0f){
setBitValue(equipment, "Source Preferred", 9, false);
setBitValue(equipment, "Source Preferred", 8, true);
}
setPointValue(equipment, "S1 kW", kw);
setPointValue(equipment, "S1 kVA", kva);
setPointValue(equipment, "S1 MWh", mwh);
_applyStrategies(equipment);
return nullptr;
}
@@ -109,8 +122,6 @@ template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Source Active", 64);
setPointValue(equipment, "Source Preferred", 1024);
setPointValue(equipment, "S2 Volts AB", 0.0f);
setPointValue(equipment, "S2 Volts BC", 0.0f);
@@ -121,6 +132,14 @@ void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "S2 Amps A", 0.0f);
setPointValue(equipment, "S2 Amps B", 0.0f);
setPointValue(equipment, "S2 Amps C", 0.0f);
setPointValue(equipment, "S2 kW", 0.0f);
setPointValue(equipment, "S2 MWh", 0.0f);
setBitValue(equipment, "Source Active", 3, false);
setBitValue(equipment, "Source Active", 4, true);
setBitValue(equipment, "Source Preferred", 8, false);
setBitValue(equipment, "Source Preferred", 9, true);
}
/**

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 33, 241); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress local_IP(172, 17, 32, 82); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
@@ -60,35 +60,37 @@
*/
modbusMap mb_map[] =
{
{HR, 8, 0, "ATS_Preferred"}, //Internal to control from Modscan
{HR, 9, 0, "ATS_Source"}, //Internal to control from Modscan
{HR, 10, 0, "ATS_Load"},
{HR, 11, 0, "ATS_Rating"}, //Internal Fault code from Modscan
{HR, 50009, 0, "PF"},
{HR_LONG, 50001, 0, "S2 Volts AB"},
{HR_LONG, 50004, 0, "S2 Volts AN"},
{HR_LONG, 50007, 0, "S2 Volts BC"},
{HR_LONG, 50010, 0, "S2 Volts BN"},
{HR_LONG, 50013, 0, "S2 Volts CA"},
{HR_LONG, 50016, 0, "S2 Volts CN"},
{HR_LONG, 50019, 0, "S1 Volts AB"},
{HR_LONG, 50022, 0, "S1 Volts AN"},
{HR_LONG, 50025, 0, "S1 Volts BC"},
{HR_LONG, 50028, 0, "S1 Volts BN"},
{HR_LONG, 50031, 0, "S1 Volts CA"},
{HR_LONG, 50034, 0, "S1 Volts CN"},
{HR_LONG, 50037, 0, "S2 Amps A"},
{HR_LONG, 50040, 0, "S2 Amps B"},
{HR_LONG, 50043, 0, "S2 Amps C"},
{HR_LONG, 50060, 0, "S2 kW"},
{HR_LONG, 50064, 0, "S2 MWh"},
{HR, 50078, 0, "Source Preferred"}, // bit 8 and bit 9
{HR, 50082, 0, "Source Active"}, //bit2 and bit 3
{HR_LONG, 50091, 0, "S1 Amps A"},
{HR, 50093, 0, "S1 kW"},
{HR_LONG, 50094, 0, "S1 Amps B"},
{HR_LONG, 50097, 0, "S1 Amps C"},
{HR_LONG, 50100, 0, "S1 MWh"},
{HR, 50009, 0, "PF"}, //0.001x
{HR_LONG, 50001, 0, "S2 Volts AB"}, //0.1x
{HR_LONG, 50004, 0, "S2 Volts AN"}, //0.1x
{HR_LONG, 50007, 0, "S2 Volts BC"}, //0.1x
{HR_LONG, 50010, 0, "S2 Volts BN"}, //0.1x
{HR_LONG, 50013, 0, "S2 Volts CA"}, //0.1x
{HR_LONG, 50016, 0, "S2 Volts CN"}, //0.1x
{HR_LONG, 50019, 0, "S1 Volts AB"}, //0.1x
{HR_LONG, 50022, 0, "S1 Volts AN"}, //0.1x
{HR_LONG, 50025, 0, "S1 Volts BC"}, //0.1x
{HR_LONG, 50028, 0, "S1 Volts BN"}, //0.1x
{HR_LONG, 50031, 0, "S1 Volts CA"}, //0.1x
{HR_LONG, 50034, 0, "S1 Volts CN"}, //0.1x
{HR_LONG, 50037, 0, "S2 Amps A"}, //0.001x
{HR_LONG, 50040, 0, "S2 Amps B"}, //0.001x
{HR_LONG, 50043, 0, "S2 Amps C"}, //0.001x
{HR_LONG, 50060, 0, "S2 kW"},
{HR_LONG, 50064, 0, "S2 MWh"}, //0.01x
{HR, 50078, 0, "Source Preferred"}, //bit9 source1 bit8 source 2
{HR, 50082, 0, "Source Active"}, //bit4 source1 bit3 source 2
{HR_LONG, 50091, 0, "S1 Amps A"}, //.001x
{HR, 50093, 0, "S1 kW"}, //.1x
{HR_LONG, 50094, 0, "S1 Amps B"}, //.001x
{HR_LONG, 50097, 0, "S1 Amps C"}, //.001x
{HR_LONG, 50100, 0, "S1 MWh"}, //.01x
};
//Size of modbus map used in FOR cycles, automatically calculated.

View File

@@ -38,6 +38,13 @@
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("V_AB", new SingleValueStrategy(4800.0F, 5.0f, 1000));
addStrategy("V_BC", new SingleValueStrategy(4800.0F, 5.0f, 1000));
addStrategy("V_CA", new SingleValueStrategy(4800.0F, 5.0f, 1000));
addStrategy("Amps A", new SingleValueStrategy(1.0f, 10.0f, 1000));
addStrategy("Amps B", new SingleValueStrategy(1.0f, 10.0f, 1000));
addStrategy("Amps C", new SingleValueStrategy(1.0f, 10.0f, 1000));
}
/**
@@ -57,7 +64,48 @@ template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float State_Ctrl = getPointValue(equipment, "PxControl");
if (State_Ctrl == 0.0f){
return new StandbyState<ModbusIP>();
}
if (State_Ctrl == 1.0f){
setBitValue(equipment, "CB_Position", 0, true);
setBitValue(equipment, "CB_Position", 12, false);
}
if (State_Ctrl == 2.0f){
return new StandbyState<ModbusIP>();
}
float volts_AB = getPointValue(equipment, "V_AB");
float volts_BC = getPointValue(equipment, "V_BC");
float volts_AC = getPointValue(equipment, "V_CA");
setPointValue(equipment, "V_AN", volts_AB/1.732f);
setPointValue(equipment, "V_BN", volts_BC/1.732f);
setPointValue(equipment, "V_CN", volts_AC/1.732f);
int I_load = getPointValue(equipment, "PxLoad");
int I_rating = getPointValue(equipment, "PxRating");
float load = static_cast<float>(I_load);
float rating = static_cast<float>(I_rating);
float real_load = rating * (load/100.0f);
setPointValue(equipment, "Amps A", real_load * 10.0f);
setPointValue(equipment, "Amps B", real_load * 10.0f);
setPointValue(equipment, "Amps C", real_load * 10.0f);
setPointValue(equipment, "Amps G", volts_AB * 0.037f);
setPointValue(equipment, "Amps N", volts_BC * 0.034f);
float kva = (1.732f * ((volts_AB + volts_BC + volts_AC)/4.0f) * real_load * (0.92f))/100000.0f;
float kw = (1.732f * ((volts_AB + volts_BC + volts_AC)/4.0f) * real_load )/10000.0f;
setPointValue(equipment, "kW", kw);
setPointValue(equipment, "kVA", kva);
setPointValue(equipment, "kVA2", kva);
setPointValue(equipment, "kWh", 1724.0f);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;

View File

@@ -56,7 +56,18 @@ 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");
float State_Ctrl = getPointValue(equipment, "PxControl");
if (State_Ctrl == 1.0f){
return new RunningState<ModbusIP>();
}
if (State_Ctrl == 0.0f){
setBitValue(equipment, "CB_Position", 0, false);
setBitValue(equipment, "CB_Position", 12, false);
}
if (State_Ctrl == 2.0f){
setBitValue(equipment, "CB_Position", 0, false);
setBitValue(equipment, "CB_Position", 12, true);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -72,6 +83,21 @@ template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "V_AB", 0.0f);
setPointValue(equipment, "V_BC", 0.0f);
setPointValue(equipment, "V_CA", 0.0f);
setPointValue(equipment, "V_AN", 0.0f);
setPointValue(equipment, "V_BN", 0.0f);
setPointValue(equipment, "V_CN", 0.0f);
setPointValue(equipment, "Amps A", 0.0f);
setPointValue(equipment, "Amps B", 0.0f);
setPointValue(equipment, "Amps C", 0.0f);
setPointValue(equipment, "Amps G", 0.0f);
setPointValue(equipment, "Amps N", 0.0f);
setPointValue(equipment, "kW", 0.0f);
setPointValue(equipment, "k_VA", 0.0f);
setPointValue(equipment, "k_VA2", 0.0f);
setPointValue(equipment, "kWh", 0.0f);
}
/**

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 22, 152); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 22, 254); /**< @brief The gateway IP address. */
const char *ssid = "wifi"; /**< @brief The SSID of the WiFi network. */
const char *password = "password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 170); /**< @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;
@@ -63,23 +63,25 @@ modbusMap mb_map[] = {
// Convert from ESP8266 to traditional Modbus addressing: subtract 30001/40001.
// Input Registers (3x) - Floating Point (MSB & LSB)
{IR, 40, 0, "CB_Position" }, // 300041.0 - Circuit Breaker Position
{IR, 40, 0, "CB_Trip" }, // 300041.12 - Circuit Breaker Tripped
{IR_LONG, 100, 0, "Amps_A" }, //DWORD
{IR_LONG, 102, 0, "Amps_B" }, //DWORD
{IR_LONG, 104, 0, "Amps_C" }, //DWORD
{IR_LONG, 106, 0, "Amps_N" }, //DWORD
{IR_LONG, 108, 0, "Amps_G" }, //DWORD
{IR, 150, 0, "V_AN" }, //WORD
{IR, 151, 0, "V_BN" }, //WORD
{IR, 152, 0, "V_CN" }, //WORD
{IR, 154, 0, "V_AB" }, //WORD
{IR, 155, 0, "V_BC" }, //WORD
{IR, 156, 0, "V_CA" }, //WORD
{IR_LONG, 222, 0, "k_VA" }, //LONG
{IR_LONG, 206, 0, "kW" }, //LONG
{IR_LONG, 304, 0, "kWh" }, //LONG
{IR, 253, 0, "k_VA" }, //SHORT
{HR, 9, 0, "PxControl"}, //Open-Close Cmd
{HR, 10, 0, "PxLoad"}, //Adjustble Load
{HR, 11, 0, "PxRating"}, //Max amp to calculate kw, kVA, etc
{IR, 39, 0, "CB_Position" }, // 300041.0 - Circuit Breaker Position || 300041.12 - Circuit Breaker Tripped
{IR_LONG, 99, 0, "Amps A" }, //DWORD
{IR_LONG, 101, 0, "Amps B" }, //DWORD
{IR_LONG, 103, 0, "Amps C" }, //DWORD
{IR_LONG, 105, 0, "Amps N" }, //DWORD
{IR_LONG, 107, 0, "Amps G" }, //DWORD
{IR, 149, 0, "V_AN" }, //WORD
{IR, 150, 0, "V_BN" }, //WORD
{IR, 151, 0, "V_CN" }, //WORD
{IR, 153, 0, "V_AB" }, //WORD
{IR, 154, 0, "V_BC" }, //WORD
{IR, 155, 0, "V_CA" }, //WORD
{IR_LONG, 221, 0, "k_VA" }, //LONG
{IR_LONG, 205, 0, "kW" }, //LONG
{IR_LONG, 303, 0, "kWh" }, //LONG
{IR, 252, 0, "k_VA2" }, //SHORT
};
//Size of modbus map used in FOR cycles, automatically calculated.

View File

@@ -38,11 +38,11 @@
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("Volts AB", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("Volts BC", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("Volts CA", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("Volts AB", new SingleValueStrategy(4800.0F, 5.0f, 1000));
addStrategy("Volts BC", new SingleValueStrategy(4800.0F, 5.0f, 1000));
addStrategy("Volts CA", new SingleValueStrategy(4800.0F, 5.0f, 1000));
addStrategy("PF", new SingleValueStrategy(90.0f, 1.0f, 1000));
addStrategy("PF", new SingleValueStrategy(900.0f, 1.0f, 1000));
addStrategy("Amps A", new SingleValueStrategy(1.0f, 10.0f, 1000));
addStrategy("Amps B", new SingleValueStrategy(1.0f, 10.0f, 1000));
@@ -66,9 +66,11 @@ template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
Serial.println("Running update function");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 1){
if (State_Ctrl == 0.0f){
return new StandbyState<ModbusIP>();
}
if (State_Ctrl == 2.0f){
return new StandbyState<ModbusIP>();
}
// Apply any strategies defined for the standby state
@@ -76,9 +78,9 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
float volts_BC = getPointValue(equipment, "Volts BC");
float volts_AC = getPointValue(equipment, "Volts CA");
setPointValue(equipment, "Volts AN", volts_AB/1.732);
setPointValue(equipment, "Volts BN", volts_BC/1.732);
setPointValue(equipment, "Volts CN", volts_AC/1.732);
setPointValue(equipment, "Volts AN", volts_AB/1.732f);
setPointValue(equipment, "Volts BN", volts_BC/1.732f);
setPointValue(equipment, "Volts CN", volts_AC/1.732f);
int I_load = getPointValue(equipment, "Load");
@@ -86,18 +88,21 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
float load = static_cast<float>(I_load);
float rating = static_cast<float>(I_rating);
float real_load = rating * (load/100.0f);
setPointValue(equipment, "Amps A", real_load);
setPointValue(equipment, "Amps B", real_load);
setPointValue(equipment, "Amps C", real_load);
setPointValue(equipment, "Amps A", real_load * 10.0f);
setPointValue(equipment, "Amps B", real_load * 10.0f);
setPointValue(equipment, "Amps C", real_load * 10.0f);
setPointValue(equipment, "Amps G", volts_AB * 0.037f);
setPointValue(equipment, "Amps N", volts_BC * 0.034f);
float pf = getPointValue(equipment, "PF");
float kw = (1.732f * ((volts_AB + volts_BC + volts_AC)/3.0f) * real_load * (pf/100))/100;
float kva = (1.732f * ((volts_AB + volts_BC + volts_AC)/3.0f) * real_load)/100;
float kva = (1.732f * ((volts_AB + volts_BC + volts_AC)/4.0f) * real_load * (pf/100.0f))/100000.0f;
float kw = (1.732f * ((volts_AB + volts_BC + volts_AC)/4.0f) * real_load )/10000.0f;
setPointValue(equipment, "kW", kw);
setPointValue(equipment, "kVA", kva);
setPointValue(equipment, "kWh", 1724.0f);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -113,7 +118,8 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "CB Position", 1);
setBitValue(equipment, "CB Position", 0, true);
setBitValue(equipment, "CB Position", 12, false);
}
/**

View File

@@ -57,9 +57,16 @@ 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");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 2){
if (State_Ctrl == 1.0f){
return new RunningState<ModbusIP>();
}
if (State_Ctrl == 0.0f){
setBitValue(equipment, "CB Position", 12, false);
}
if (State_Ctrl == 2.0f){
setBitValue(equipment, "CB Position", 12, true);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -75,7 +82,7 @@ template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "CB Position", 0);
setBitValue(equipment, "CB Position", 0, false);
setPointValue(equipment, "Volts AB", 0.0f);
setPointValue(equipment, "Volts BC", 0.0f);
setPointValue(equipment, "Volts CA", 0.0f);
@@ -86,8 +93,12 @@ void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "Amps A", 0.0f);
setPointValue(equipment, "Amps B", 0.0f);
setPointValue(equipment, "Amps C", 0.0f);
setPointValue(equipment, "Amps G", 0.0f);
setPointValue(equipment, "Amps N", 0.0f);
setPointValue(equipment, "kW", 0.0f);
setPointValue(equipment, "kVA", 0.0f);
setPointValue(equipment, "kWh", 0.0f);
setBitValue(equipment, "Alarm General", 0, false);
}
/**

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 30, 241); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 30, 1); /**< @brief The gateway IP address. */
IPAddress local_IP(172, 17, 32, 102); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
@@ -63,7 +63,7 @@ modbusMap mb_map[] =
{HR, 9, 0, "State Control"}, //Open-Close Cmd
{HR, 10, 0, "Load"}, //Adjustble Load
{HR, 11, 0, "Rating"}, //Max amp to calculate kw, kVA, etc
{IR_LONG, 41, 0, "CB Position"},
{IR, 41, 0, "CB Position"}, //b0 close open b12 tripped
{IR_LONG, 101, 0, "Amps A"},
{IR_LONG, 103, 0, "Amps B"},
{IR_LONG, 105, 0, "Amps C"},

View File

@@ -68,7 +68,10 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 1){
if (State_Ctrl == 0){
return new StandbyState<ModbusIP>();
}
if (State_Ctrl == 2){
return new StandbyState<ModbusIP>();
}
// Apply any strategies defined for the standby state
@@ -118,6 +121,7 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "CB Position", 2048);
setPointValue(equipment, "CB Trip", 0.0f);
}

View File

@@ -57,9 +57,16 @@ 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");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 2){
if (State_Ctrl == 1){
return new RunningState<ModbusIP>();
}
if (State_Ctrl == 0){
setPointValue(equipment, "CB Trip", 0.0f);
}
if (State_Ctrl == 2){
setPointValue(equipment, "CB Trip", 1.0f);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 33, 167); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;

View File

@@ -61,7 +61,10 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 1){
if (State_Ctrl == 0.0f){
return new StandbyState<ModbusIP>();
}
if (State_Ctrl == 2.0f){
return new StandbyState<ModbusIP>();
}
@@ -92,7 +95,9 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "Status", 4);
setBitValue(equipment, "Status", 2, true);
setBitValue(equipment, "Tripped", 0, false);
}

View File

@@ -57,9 +57,16 @@ 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");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 2){
if (State_Ctrl == 1.0f){
return new RunningState<ModbusIP>();
}
if (State_Ctrl == 0.0f){
setBitValue(equipment, "Tripped", 0, false);
}
if (State_Ctrl == 2.0f){
setBitValue(equipment, "Tripped", 0, true);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -75,11 +82,13 @@ template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Status", 0);
setPointValue(equipment, "Amps A", 0.0f);
setPointValue(equipment, "Amps B", 0.0f);
setPointValue(equipment, "Amps C", 0.0f);
setPointValue(equipment, "Amps N", 0.0f);
setBitValue(equipment, "Status", 2, false);
}
/**

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 238); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 138, 1, 1); /**< @brief The gateway IP address. */
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 33, 149); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
@@ -64,11 +64,12 @@ modbusMap mb_map[] =
{HR, 10, 0, "Load"},
{HR, 11, 0, "Rating"},
{IR, 2, 0, "Status"},
{IR, 2, 0, "Status"}, //bit 2 open-close,
{IR, 3, 0, "Amps A"},
{IR, 5, 0, "Amps B"},
{IR, 7, 0, "Amps C"},
{IR, 9, 0, "Amps N"},
{IR, 13, 0, "Tripped"}, //bit 0 tripped
};
//Size of modbus map used in FOR cycles, automatically calculated.

View File

@@ -68,7 +68,10 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 1){
if (State_Ctrl == 0.0f){
return new StandbyState<ModbusIP>();
}
if (State_Ctrl == 2.0f){
return new StandbyState<ModbusIP>();
}
// Apply any strategies defined for the standby state
@@ -116,7 +119,9 @@ void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "CB Position", 4096);
setBitValue(equipment, "CB Position", 12, true);
setBitValue(equipment, "CB Position", 9, false);
}

View File

@@ -57,9 +57,17 @@ 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");
float State_Ctrl = getPointValue(equipment, "State Control");
if (State_Ctrl == 2){
if (State_Ctrl == 1.0f){
return new RunningState<ModbusIP>();
}
if (State_Ctrl == 0.0f){
setBitValue(equipment, "CB Position", 9, false);
}
if (State_Ctrl == 2.0f){
setBitValue(equipment, "CB Position", 9, true);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -86,6 +94,7 @@ void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "Amps A", 0.0f);
setPointValue(equipment, "Amps B", 0.0f);
setPointValue(equipment, "Amps C", 0.0f);
setBitValue(equipment, "CB Position", 12, false);
}
/**

View File

@@ -23,7 +23,7 @@
#include <ModbusIP_ESP8266.h>
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 33, 132); /**< @brief The static IP address for the device. */
IPAddress local_IP(172, 17, 33, 141); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
@@ -64,7 +64,7 @@ modbusMap mb_map[] =
{HR, 10, 0, "Load"},
{HR, 11, 0, "Rating"},
{IR, 207, 0, "CB Position"},
{IR, 207, 0, "CB Position"}, //bit 9 trip bit 12 open closed
{IR_FLOAT, 215, 0, "Amps A"},
{IR_FLOAT, 217, 0, "Amps B"},
{IR_FLOAT, 219, 0, "Amps C"},

View File

@@ -60,6 +60,7 @@
*/
modbusMap mb_map[] = {
// ESP8266 Modbus server uses 0-based addressing, while Modbus Poll uses 1-based addressing.
{HR, 48899, 0, "Alm01" }, // 448900
{HR, 48898, 0, "Alm02" }, // 448899
{HR, 48905, 0, "Alm03" }, // 448906

View File

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

View File

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

View File

@@ -0,0 +1,92 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Totalizer.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
RunningState<ModbusIP>::RunningState() {
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. It reads the "ON/OFF Command By BMS" point. If it's 0, it transitions to StandbyState.
* 2. It reads the "Fault Code" point. If it's non-zero, it transitions to FailState,
* passing the corresponding alarm description.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float Mode = getPointValue(equipment, "PxMode");
if (static_cast<int>(Mode) == 2 ){
return new StandbyState<ModbusIP>();
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Run Status" for all EC fans to 1 to indicate they are active.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}

View File

@@ -0,0 +1,89 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the StandbyState, which defines
* the behavior of the equipment when it is in an idle or standby mode.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new StandbyState object.
*
* In this state, the equipment is idle. This constructor initializes strategies
* to bring the system to a safe, idle condition. It sets a stable value for
* the SAT reading and creates ramp strategies to bring the CW valve and all
* EC fan speeds down to zero.
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method applies the strategies defined for the standby state (e.g.,
* ramping values to zero).
*
* @warning This method currently does not check for a command to transition to the
* Running state. This logic needs to be added to allow the unit to start.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
float Mode = getPointValue(equipment, "PxMode");
if (static_cast<int>(Mode) == 2 ){
return new RunningState<ModbusIP>();
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* This method performs cleanup by setting all alarm points and all EC fan
* run status points to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<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,133 @@
/**
* @file config.h
* @brief Main configuration file for the GEN CAT EMCP4
* @author Zach Gutierrez
* @date 2025-09-02
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] = {
// ESP8266 Modbus server uses 0-based addressing, while Modbus Poll uses 1-based addressing.
{HR, 9, 0, "PxMode" }, // 448900 High_Coolant_Temp_Warning
{HR, 10, 0, "Px01" }, // 448899 Low_Coolant_Temp
{HR, 11, 0, "Px02" }, // 448906 Unexpected_Engine_Shutdown
{HR, 48900, 0, "Alm01" }, // 448900 High_Coolant_Temp_Warning
{HR, 48899, 0, "Alm02" }, // 448899 Low_Coolant_Temp
{HR, 48906, 0, "Alm03" }, // 448906 Unexpected_Engine_Shutdown
{HR, 48897, 0, "Alm04" }, // 448897 Emergency_Stop
{HR, 48901, 0, "Alm05" }, // 448901 High_Coolant_Temp_Alarm
{HR, 48904, 0, "Alm06" }, // 448904 Engine_Overspeed
{HR, 48902, 0, "Alm07" }, // 448902 Low_Oil_Pressure_Warning
{HR, 48903, 0, "Alm08" }, // 448903 Low_Oil_Pressure_Alarm
{HR, 48908, 0, "Fuel_LoLo" }, // 448908 Fuel_LoLo
{HR, 48913, 0, "Alm10" }, // 448913 Low_Battery_Voltage
{HR, 48898, 0, "Alm11" }, // 448898 Engine_Overcrank
{HR, 48915, 0, "Fuel_Hi" }, // 448915 Fuel_Hi
{HR, 48907, 0, "Fuel_Lo" }, // 448907 Fuel_Lo
{HR, 48912, 0, "Alm14" }, // 448912 High_Battery_Voltage
{HR, 48905, 0, "Common_Alarm" }, // 448905 Common_Alarm
{HR, 48914, 0, "Batt_Charge_Fail" }, // 448914 Battery_Charger_Failure
{HR, 48916, 0, "EPS_Supp_Load" }, // 448916 EPS_Supplying_Load
{HR, 8655, 0, "Bkr_State" }, // 48655 Gen_Breaker_State
{HR, 1025, 0, "Oil Pressure" }, // 41025 Engine_Oil_Pressure
{HR, 1026, 0, "Coolant Temp" }, // 41026 Coolant_Temperature_degC
{HR, 1027, 0, "Oil_Temp_degC" }, // 41027 Oil_Temperature_degC
{HR, 1030, 0, "Battery_Voltage" }, // 41030 Battery_Voltage
{HR, 1031, 0, "Engine_Speed" }, // 41031 Engine_Speed
{HR, 1032, 0, "Freq" }, // 41032 Freq
{HR, 1033, 0, "Volts_AN" }, // 41033 Volts_AN
{HR, 1035, 0, "Volts_BN" }, // 41035 Volts_BN
{HR, 1037, 0, "Volts_CN" }, // 41037 Volts_CN
{HR, 1039, 0, "Volts_AB" }, // 41039 Volts_AB
{HR, 1041, 0, "Volts_BC" }, // 41041 Volts_BC
{HR, 1043, 0, "Volts_CA" }, // 41043 Volts_CA
{HR, 1045, 0, "Amps_A" }, // 41045 Amps_A
{HR, 1047, 0, "Amps_B" }, // 41047 Amps_B
{HR, 1049, 0, "Amps_C" }, // 41049 Amps_C
{HR, 1053, 0, "kW_A" }, // 41053 kW_A
{HR, 1055, 0, "kW_B" }, // 41055 kW_B
{HR, 1057, 0, "kW_C" }, // 41057 kW_C
{HR, 1289, 0, "L_Exhaust_degC" }, // 41289 Left_Exhaust_Temp_degC
{HR, 1290, 0, "R_Exhaust_degC" }, // 41290 Right_Exhaust_Temp_degC
{HR, 1355, 0, "Percent_Load" }, // 41355 Percent_Load
{HR, 1537, 0, "kW_Tot" }, // 41537 kW
{HR, 1539, 0, "kVA_A" }, // 41539 kVA_A
{HR, 1541, 0, "kVA_B" }, // 41541 kVA_B
{HR, 1543, 0, "kVA_C" }, // 41543 kVA_C
{HR, 1545, 0, "kVA_Tot" }, // 41545 kVA
{HR, 1553, 0, "kVAR_Tot" }, // 41553 kVAR
{HR, 1558, 0, "PF_Tot" }, // 41558 PF
{HR, 1799, 0, "TTL_Run_Hours" }, // 41799 TTL_Run_Hours
{HR, 1801, 0, "kWh_Tot" }, // 41801 kWh
{HR, 1809, 0, "TTL_Starts" }, // 41809 TTL_Engine_Starts
{HR, 48909, 0, "Auto_Mode" }, // 448909 Auto_Mode
{HR, 48910, 0, "Stop_Mode" }, // 448910 Stop_Mode
{HR, 48911, 0, "Manual_Mode" }, // 448911 Manual_Mode
{HR, 772, 0, "Gen_Sts" } // 400772 Generator Status
};
//Size of modbus map used in FOR cycles, automatically calculated.
/**
* @brief The total number of entries in the `mb_map` array.
* This is calculated at compile time and used for iterating over the map.
*/
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
/** @brief The main loop update interval in milliseconds. */
int interval = 250;
/** @} */ // End of ModbusMapConfig group
#endif // CONFIG_H

View File

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

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 32, 81); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;

View File

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

View File

@@ -0,0 +1,271 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Totalizer.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
RunningState<ModbusIP>::RunningState() {
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. It reads the "ON/OFF Command By BMS" point. If it's 0, it transitions to StandbyState.
* 2. It reads the "Fault Code" point. If it's non-zero, it transitions to FailState,
* passing the corresponding alarm description.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float StateCtrl = getPointValue(equipment, "Px_Mode");
if (StateCtrl == 1.0f) {
return new StandbyState<ModbusIP>();
}
float W1 = getPointValue(equipment, "Px_W1");
float W2 = getPointValue(equipment, "Px_W2");
float W3 = getPointValue(equipment, "Px_W3");
float W4 = getPointValue(equipment, "Px_W4");
float W5 = getPointValue(equipment, "Px_W5");
float W6 = getPointValue(equipment, "Px_W6");
// Apply any strategies defined for the standby state
switch (static_cast<int>(W1)){
case 0:
setBitValue(equipment, "MVG_STS_01", 0, false);
setBitValue(equipment, "MVG_STS_01", 1, false);
setBitValue(equipment, "MVG_STS_01", 2, false);
break;
case 1:
setBitValue(equipment, "MVG_STS_01", 0, true);
setBitValue(equipment, "MVG_STS_01", 1, false);
setBitValue(equipment, "MVG_STS_01", 2, false);
break;
case 2:
setBitValue(equipment, "MVG_STS_01", 0, false);
setBitValue(equipment, "MVG_STS_01", 1, true);
setBitValue(equipment, "MVG_STS_01", 2, false);
break;
case 3:
setBitValue(equipment, "MVG_STS_01", 0, true);
setBitValue(equipment, "MVG_STS_01", 1, false);
setBitValue(equipment, "MVG_STS_01", 2, true);
break;
default:
setBitValue(equipment, "MVG_STS_01", 0, false);
setBitValue(equipment, "MVG_STS_01", 1, false);
setBitValue(equipment, "MVG_STS_01", 2, false);
break;
}
switch (static_cast<int>(W2)){
case 0:
setBitValue(equipment, "MVG_STS_01", 3, false);
setBitValue(equipment, "MVG_STS_01", 4, false);
setBitValue(equipment, "MVG_STS_01", 5, false);
break;
case 1:
setBitValue(equipment, "MVG_STS_01", 3, true);
setBitValue(equipment, "MVG_STS_01", 4, false);
setBitValue(equipment, "MVG_STS_01", 5, false);
break;
case 2:
setBitValue(equipment, "MVG_STS_01", 3, false);
setBitValue(equipment, "MVG_STS_01", 4, true);
setBitValue(equipment, "MVG_STS_01", 5, false);
break;
case 3:
setBitValue(equipment, "MVG_STS_01", 3, true);
setBitValue(equipment, "MVG_STS_01", 4, false);
setBitValue(equipment, "MVG_STS_01", 5, true);
break;
default:
setBitValue(equipment, "MVG_STS_01", 3, false);
setBitValue(equipment, "MVG_STS_01", 4, false);
setBitValue(equipment, "MVG_STS_01", 5, false);
break;
}
switch (static_cast<int>(W3)){
case 0:
setBitValue(equipment, "MVG_STS_01", 6, false);
setBitValue(equipment, "MVG_STS_01", 7, false);
setBitValue(equipment, "MVG_STS_02", 0, false);
break;
case 1:
setBitValue(equipment, "MVG_STS_01", 6, true);
setBitValue(equipment, "MVG_STS_01", 7, false);
setBitValue(equipment, "MVG_STS_02", 0, false);
break;
case 2:
setBitValue(equipment, "MVG_STS_01", 6, false);
setBitValue(equipment, "MVG_STS_01", 7, true);
setBitValue(equipment, "MVG_STS_02", 0, false);
break;
case 3:
setBitValue(equipment, "MVG_STS_01", 6, true);
setBitValue(equipment, "MVG_STS_01", 7, false);
setBitValue(equipment, "MVG_STS_02", 0, true);
break;
default:
setBitValue(equipment, "MVG_STS_01", 6, false);
setBitValue(equipment, "MVG_STS_01", 7, false);
setBitValue(equipment, "MVG_STS_02", 0, false);
break;
}
switch (static_cast<int>(W4)){
case 0:
setBitValue(equipment, "MVG_STS_02", 1, false);
setBitValue(equipment, "MVG_STS_02", 2, false);
setBitValue(equipment, "MVG_STS_02", 3, false);
break;
case 1:
setBitValue(equipment, "MVG_STS_02", 1, true);
setBitValue(equipment, "MVG_STS_02", 2, false);
setBitValue(equipment, "MVG_STS_02", 3, false);
break;
case 2:
setBitValue(equipment, "MVG_STS_02", 1, false);
setBitValue(equipment, "MVG_STS_02", 2, true);
setBitValue(equipment, "MVG_STS_02", 3, false);
break;
case 3:
setBitValue(equipment, "MVG_STS_02", 1, true);
setBitValue(equipment, "MVG_STS_02", 2, false);
setBitValue(equipment, "MVG_STS_02", 3, true);
break;
default:
setBitValue(equipment, "MVG_STS_02", 1, false);
setBitValue(equipment, "MVG_STS_02", 2, false);
setBitValue(equipment, "MVG_STS_02", 3, false);
break;
}
switch (static_cast<int>(W5)){
case 0:
setBitValue(equipment, "MVG_STS_02", 4, false);
setBitValue(equipment, "MVG_STS_02", 5, false);
setBitValue(equipment, "MVG_STS_02", 6, false);
break;
case 1:
setBitValue(equipment, "MVG_STS_02", 4, true);
setBitValue(equipment, "MVG_STS_02", 5, false);
setBitValue(equipment, "MVG_STS_02", 6, false);
break;
case 2:
setBitValue(equipment, "MVG_STS_02", 4, false);
setBitValue(equipment, "MVG_STS_02", 5, true);
setBitValue(equipment, "MVG_STS_02", 6, false);
break;
case 3:
setBitValue(equipment, "MVG_STS_02", 4, true);
setBitValue(equipment, "MVG_STS_02", 5, false);
setBitValue(equipment, "MVG_STS_02", 6, true);
break;
default:
setBitValue(equipment, "MVG_STS_02", 4, false);
setBitValue(equipment, "MVG_STS_02", 5, false);
setBitValue(equipment, "MVG_STS_02", 6, false);
break;
}
switch (static_cast<int>(W6)){
case 0:
setBitValue(equipment, "MVG_STS_02", 7, false);
setBitValue(equipment, "MVG_STS_03", 0, false);
setBitValue(equipment, "MVG_STS_03", 1, false);
break;
case 1:
setBitValue(equipment, "MVG_STS_02", 7, true);
setBitValue(equipment, "MVG_STS_03", 0, false);
setBitValue(equipment, "MVG_STS_03", 1, false);
break;
case 2:
setBitValue(equipment, "MVG_STS_02", 7, false);
setBitValue(equipment, "MVG_STS_03", 0, true);
setBitValue(equipment, "MVG_STS_03", 1, false);
break;
case 3:
setBitValue(equipment, "MVG_STS_02", 7, true);
setBitValue(equipment, "MVG_STS_03", 0, false);
setBitValue(equipment, "MVG_STS_03", 1, true);
break;
default:
setBitValue(equipment, "MVG_STS_02", 7, false);
setBitValue(equipment, "MVG_STS_03", 0, false);
setBitValue(equipment, "MVG_STS_03", 1, false);
break;
}
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Run Status" for all EC fans to 1 to indicate they are active.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
setPointValue(equipment, "MVG_STS_01", 0);
setPointValue(equipment, "MVG_STS_02", 0);
setPointValue(equipment, "MVG_STS_03", 0);
}

View File

@@ -0,0 +1,88 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the StandbyState, which defines
* the behavior of the equipment when it is in an idle or standby mode.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new StandbyState object.
*
* In this state, the equipment is idle. This constructor initializes strategies
* to bring the system to a safe, idle condition. It sets a stable value for
* the SAT reading and creates ramp strategies to bring the CW valve and all
* EC fan speeds down to zero.
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method applies the strategies defined for the standby state (e.g.,
* ramping values to zero).
*
* @warning This method currently does not check for a command to transition to the
* Running state. This logic needs to be added to allow the unit to start.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
float StateCtrl = getPointValue(equipment, "Px_Mode");
if (StateCtrl == 2.0f) {
return new RunningState<ModbusIP>();
}
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* This method performs cleanup by setting all alarm points and all EC fan
* run status points to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<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,86 @@
/**
* @file config.h
* @brief Main configuration file for the CRAH Unit (TCP) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 32, 82); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 9, 0, "Px_Mode"},
{HR, 10, 0, "Px_W1"},
{HR, 11, 0, "Px_W2"},
{HR, 12, 0, "Px_W3"},
{HR, 13, 0, "Px_W4"},
{HR, 14, 0, "Px_W5"},
{HR, 15, 0, "Px_W6"},
{HR, 1049, 0, "MVG_STS_01"},
{HR, 1050, 0, "MVG_STS_02"},
{HR, 1051, 0, "MVG_STS_03"},
};
//Size of modbus map used in FOR cycles, automatically calculated.
/**
* @brief The total number of entries in the `mb_map` array.
* This is calculated at compile time and used for iterating over the map.
*/
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
/** @brief The main loop update interval in milliseconds. */
int interval = 250;
/** @} */ // End of ModbusMapConfig group
#endif // CONFIG_H

View File

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

View File

@@ -0,0 +1,48 @@
# Daikin Chiller (RTU) Emulator
This project is an Arduino-based emulator for a Daikin Chiller unit, communicating over Modbus RTU. It is designed to be a flexible template that can be adapted to simulate different types of chillers by modifying the configuration and state logic.
The emulator operates on a state machine with three core states:
* **Standby**: The chiller is idle but ready.
* **Running**: The chiller is active and operational.
* **Fail**: The chiller has encountered a fault condition.
## Features
* **Modbus RTU Communication**: Emulates a Modbus slave device.
* **State Machine Logic**: Simulates different operational states (Standby, Running, Fail).
* **Dynamic Value Simulation**: Uses "Strategies" (e.g., PID, Ramp) to generate realistic, changing values for Modbus points.
* **Configurable Modbus Map**: The entire Modbus register map is defined in a single, easy-to-modify file (`config.h`).
* **Extensible Design**: The structure allows for the addition of new states and behaviors.
## Hardware Prerequisites
The code is written for an ESP8266/ESP32-style microcontroller with WiFi capabilities and at least one hardware serial port for RS485 communication.
* **Microcontroller**: ESP8266, ESP32, or similar.
* **RS485 Transceiver**: A module like the MAX485 to interface with the Modbus RTU bus.
## Software Dependencies
This project relies on a Modbus library. Ensure you have the correct library installed in your Arduino IDE.
* **Modbus Library**: The code uses a library that provides `ModbusRTU.h` and optionally `ModbusIP_ESP8266.h`.
---
## How to Customize for a New Chiller
To adapt this template for a new chiller, follow these steps.
### 1. Configure Device-Specific Parameters (`config.h`)
Open `CH_Daikin_AWV026B_RTU/config.h`. This is the main file for device-specific settings.
#### Modbus RTU Settings
Update the following constants for your device's serial communication setup.
```c++
const int BAUDRATE = 19200; // The serial communication speed
const int RX_PIN = 17; // The GPIO pin for receiving data (RX)
const int TX_PIN = 16; // The GPIO pin for transmitting data (TX)
const int RST_PIN = 4; // The GPIO pin for RS485 direction control
const int MODBUS_ID = 1; // The unique slave ID for this device

View File

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

View File

@@ -0,0 +1,89 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Totalizer.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
RunningState<ModbusIP>::RunningState() {
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. It reads the "ON/OFF Command By BMS" point. If it's 0, it transitions to StandbyState.
* 2. It reads the "Fault Code" point. If it's non-zero, it transitions to FailState,
* passing the corresponding alarm description.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Run Status" for all EC fans to 1 to indicate they are active.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}

View File

@@ -0,0 +1,85 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the StandbyState, which defines
* the behavior of the equipment when it is in an idle or standby mode.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new StandbyState object.
*
* In this state, the equipment is idle. This constructor initializes strategies
* to bring the system to a safe, idle condition. It sets a stable value for
* the SAT reading and creates ramp strategies to bring the CW valve and all
* EC fan speeds down to zero.
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method applies the strategies defined for the standby state (e.g.,
* ramping values to zero).
*
* @warning This method currently does not check for a command to transition to the
* Running state. This logic needs to be added to allow the unit to start.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* This method performs cleanup by setting all alarm points and all EC fan
* run status points to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Standby State...");
}

View File

@@ -21,10 +21,10 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
const char *ssid = "ArduinoWifiB"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 32, 88); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 32, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;

View File

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

View File

@@ -36,79 +36,90 @@
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
std::string cbs[] = {"CB0", "CB1", "CB2", "CB3", "CB4", "CB5", "CB6", "CB7", "CB8", };
std::string cbs[] = {"CB1", "CB2", "CB3", "CB4", "CB5", "CB6", "CB7", "CB8"};
template<>
RunningState<ModbusIP>::RunningState() {
//Example
addStrategy("System Input RMS C-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Input RMS Current Phase A", new RampStrategy(10.0F, 5.0f, 1000));
//
addStrategy("Input_I1", new SingleValueStrategy(40.0f, 30.0f, 1000));
addStrategy("Input_I2", new SingleValueStrategy(40.0f, 30.0f, 1000));
addStrategy("Input_I3", new SingleValueStrategy(40.0f, 30.0f, 1000));
addStrategy("Input_kVA", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Input_kVAR", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Input_kW", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Input_kWh", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_PF", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Input_V_AB", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_V_AN", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_V_BC", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_V_BN", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_V_CA", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_V_CN", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_LL_Avg", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Input_LN_Avg", new SingleValueStrategy(3.0f, 2.0f, 1000));
for (const std::string& cb : cbs) {
std::string tag = "";
tag = cb + "_V1N";
addStrategy(tag, new SingleValueStrategy(0.0f, 2.0f, 1000));
tag = "";
tag = cb + "_V2N";
addStrategy(tag, new SingleValueStrategy(0.0f, 2.0f, 1000));
tag = cb + "_I1";
addStrategy(tag, new SingleValueStrategy(40.0f, 30.0f, 1000));
tag = "";
tag = cb + "_V3N";
addStrategy(tag, new SingleValueStrategy(0.0f, 2.0f, 1000));
tag = cb + "_I2";
addStrategy(tag, new SingleValueStrategy(40.0f, 30.0f, 1000));
tag = "";
tag = cb + "_L1PF";
addStrategy(tag, new SingleValueStrategy(93.0f, 2.0f, 1000));
tag = cb + "_I3";
addStrategy(tag, new SingleValueStrategy(40.0f, 30.0f, 1000));
tag = "";
tag = cb + "_L2PF";
addStrategy(tag, new SingleValueStrategy(93.0f, 2.0f, 1000));
tag = cb + "_kVA";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_L3PF";
addStrategy(tag, new SingleValueStrategy(93.0f, 2.0f, 1000));
tag = cb + "_kVA1";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_V1THD";
addStrategy(tag, new SingleValueStrategy(2.0f, 2.0f, 1000));
tag = cb + "_kVA2";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_V2THD";
addStrategy(tag, new SingleValueStrategy(2.0f, 2.0f, 1000));
tag = cb + "_kVA3";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_V3THD";
addStrategy(tag, new SingleValueStrategy(2.0f, 2.0f, 1000));
tag = "";
tag = cb + "_I1THD";
addStrategy(tag, new SingleValueStrategy(10.0f, 2.0f, 1000));
tag = cb + "_kVAR";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_I2THD";
addStrategy(tag, new SingleValueStrategy(10.0f, 2.0f, 1000));
tag = cb + "_kW";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_I3THD";
addStrategy(tag, new SingleValueStrategy(10.0f, 2.0f, 1000));
tag = cb + "_kW1";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_I1Kfactor";
addStrategy(tag, new SingleValueStrategy(3.0f, 2.0f, 1000));
tag = cb + "_kW2";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_I2Kfactor";
addStrategy(tag, new SingleValueStrategy(3.0f, 2.0f, 1000));
tag = cb + "_kW3";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
tag = "";
tag = cb + "_I3Kfactor";
addStrategy(tag, new SingleValueStrategy(3.0f, 2.0f, 1000));
tag = cb + "_kWh";
addStrategy(tag, new SingleValueStrategy(0.1f, 100.0f, 1000));
tag = "";
tag = cb + "_I1TDD";
addStrategy(tag, new SingleValueStrategy(5.0f, 2.0f, 1000));
tag = "";
tag = cb + "_I2TDD";
addStrategy(tag, new SingleValueStrategy(5.0f, 2.0f, 1000));
tag = "";
tag = cb + "_I3TDD";
addStrategy(tag, new SingleValueStrategy(5.0f, 2.0f, 1000));
tag = "";
tag = cb + "_V12";
addStrategy(tag, new SingleValueStrategy(0.0f, 2.0f, 1000));
tag = "";
tag = cb + "_V23";
addStrategy(tag, new SingleValueStrategy(0.0f, 2.0f, 1000));
tag = "";
tag = cb + "_V31";
addStrategy(tag, new SingleValueStrategy(0.0f, 2.0f, 1000));
tag = cb + "_PF";
addStrategy(tag, new SingleValueStrategy(150.0f, 100.0f, 1000));
}
addStrategy("Output_I1", new SingleValueStrategy(30.0f, 20.0f, 1000));
addStrategy("Output_I2", new SingleValueStrategy(30.0f, 30.0f, 1000));
addStrategy("Output_I3", new SingleValueStrategy(30.0f, 20.0f, 1000));
addStrategy("Output_IG", new SingleValueStrategy(30.0f, 30.0f, 1000));
addStrategy("Output_IN", new SingleValueStrategy(30.0f, 20.0f, 1000));
addStrategy("Output_kVA1", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Output_kVA2", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Output_kVA3", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Output_kWh", new SingleValueStrategy(3.0f, 2.0f, 1000));
addStrategy("Output_PF", new SingleValueStrategy(150.0f, 100.0f, 1000));
addStrategy("Output_V_AB", new SingleValueStrategy(30.0f, 20.0f, 1000));
addStrategy("Output_V_AN", new SingleValueStrategy(30.0f, 30.0f, 1000));
addStrategy("Output_V_BC", new SingleValueStrategy(30.0f, 20.0f, 1000));
addStrategy("Output_V_BN", new SingleValueStrategy(30.0f, 30.0f, 1000));
addStrategy("Output_V_CA", new SingleValueStrategy(30.0f, 20.0f, 1000));
addStrategy("Output_V_CN", new SingleValueStrategy(30.0f, 30.0f, 1000));
}
/**
@@ -139,14 +150,14 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
float cb_count = 0.0f;
for (const std::string& cb :cbs){
std::string tag = "";
tag = "Px " + cb;
if (cb == "CB0") continue;
tag = "Px_" + cb;
float cb_status = getPointValue(equipment, tag);
if (static_cast<int>(cb_status)){
if (cb_status == 1.0f){
cb_count += 1.0f;
}
}
Serial.printf("CB_ CLosed = %f \n", cb_count);
int cb_num = 1;
for (const std::string& cb : cbs) {
std::string tag = "";
Strategy_Behavior* strategy = nullptr;
@@ -155,126 +166,224 @@ State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment)
float cb_status = getPointValue(equipment, tag);
float percent_load = getPointValue(equipment, "Px Load");
float Rating = getPointValue(equipment, "Px Rating");
float total_load = Rating * (percent_load /100.0f);
float cb_load = total_load / cb_count;
if (static_cast<bool>(cb_status) == 1){
tag = "";
tag = cb + "_V1N";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(270.0f);
tag = "";
tag = cb + "_V2N";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(270.0f);
tag = "";
tag = cb + "_V3N";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(270.0f);
tag = "";
tag = cb + "_V12";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(480.0f);
tag = "";
tag = cb + "_V23";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(480.0f);
tag = "";
tag = cb + "_V31";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(480.0f);
float cb_load = 400.0f * (percent_load /1000.0f);
if (cb_status == 1.0f){
setBitValue(equipment, "CB_Status", cb_num, true);
setBitValue(equipment, "CB_Tripped", cb_num, false);
tag = "";
tag = cb + "_I1";
setPointValue(equipment, tag, total_load);
setPointValue(equipment, tag, cb_load * 1000.0f);
tag = "";
tag = cb + "_I2";
setPointValue(equipment, tag, total_load);
setPointValue(equipment, tag, cb_load * 1000.0f);
tag = "";
tag = cb + "_I3";
setPointValue(equipment, tag, total_load);
setPointValue(equipment, tag, cb_load * 1000.0f);
tag = "";
tag = cb + "_L1KW";
setPointValue(equipment, tag, total_load*1.715f);
tag = cb + "_PF";
setPointValue(equipment, tag, 910.0f);
tag = "";
tag = cb + "_L2KW";
setPointValue(equipment, tag, total_load*1.715f);
tag = "";
tag = cb + "_L3KW";
setPointValue(equipment, tag, total_load*1.715f);
tag = "";
tag = cb + "_L1KVar";
setPointValue(equipment, tag, total_load*1.715*0.9f);
tag = "";
tag = cb + "_L2KVar";
setPointValue(equipment, tag, total_load*1.715*0.9f);
tag = "";
tag = cb + "_L3KVar";
setPointValue(equipment, tag, total_load*1.715*0.9f);
}else{
tag = "";
tag = cb + "_V1N";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(0.0f);
tag = "";
tag = cb + "_V2N";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(0.0f);
tag = "";
tag = cb + "_V3N";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(0.0f);
tag = "";
tag = cb + "_V12";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(0.0f);
tag = "";
tag = cb + "_V23";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(0.0f);
tag = "";
tag = cb + "_V31";
strategy = getStrategy(tag);
static_cast<SingleValueStrategy*>(strategy)->setSetpoint(0.0f);
tag = cb + "_PF";
float pf = getPointValue(equipment, tag);
tag = "";
tag = cb + "_I1";
setPointValue(equipment, tag, total_load);
tag = "";
tag = cb + "_I2";
setPointValue(equipment, tag, total_load);
tag = "";
tag = cb + "_I3";
setPointValue(equipment, tag, total_load);
tag = "";
tag = cb + "_L1KW";
setPointValue(equipment, tag, total_load*0.0f);
tag = cb + "_kW1";
setPointValue(equipment, tag, cb_load * 48000.0f * pf);
float kW1 = getPointValue(equipment, tag);
tag = "";
tag = cb + "_L2KW";
setPointValue(equipment, tag, total_load*0.0f);
tag = cb + "_kW2";
setPointValue(equipment, tag, cb_load * 48000.0f * pf);
float kW2 = getPointValue(equipment, tag);
tag = "";
tag = cb + "_L3KW";
setPointValue(equipment, tag, total_load*0.0f);
tag = cb + "_kW3";
setPointValue(equipment, tag, cb_load * 48000.0f * pf);
float kW3 = getPointValue(equipment, tag);
tag = "";
tag = cb + "_kW";
setPointValue(equipment, tag, ((kW1 + kW2 + kW3) / 3.0f)*1000.0f);
tag = "";
tag = cb + "_kWh";
setPointValue(equipment, tag, 1325.0f);
tag = "";
tag = cb + "_kVA";
setPointValue(equipment, tag, cb_load * 480.0f * 10000.0f);
tag = "";
tag = cb + "_kVA";
setPointValue(equipment, tag, cb_load * 480.0f * 10000.0f);
tag = "";
tag = cb + "_kVA";
setPointValue(equipment, tag, cb_load * 480.0f * 10000.0f);
tag = "";
tag = cb + "_L1KVar";
setPointValue(equipment, tag, total_load*0.0f);
tag = cb + "_kVA1";
float kVA1 = getPointValue(equipment, tag);
tag = "";
tag = cb + "_L2KVar";
setPointValue(equipment, tag, total_load*0.0f);
tag = cb + "_kVA2";
float kVA2 = getPointValue(equipment, tag);
tag = "";
tag = cb + "_L3KVar";
setPointValue(equipment, tag, total_load*0.0f);
tag = cb + "_kVA3";
float kVA3 = getPointValue(equipment, tag);
float kVA = (kVA1 + kVA2 + kVA3) * 1732.0f;
tag = "";
tag = cb + "_kVA";
setPointValue(equipment, tag, kVA);
tag = "";
tag = cb + "_kVAR";
setPointValue(equipment, tag, kVA / pf);
}
else{
if (cb_status == 2.0f){
setBitValue(equipment, "CB_Tripped", cb_num, true);
} else {
setBitValue(equipment, "CB_Tripped", cb_num, false);
}
setBitValue(equipment, "CB_Status", cb_num, false);
tag = "";
tag = cb + "_I1";
setPointValue(equipment, tag, 40.0f);
tag = "";
tag = cb + "_I2";
setPointValue(equipment, tag, 0.0f);
tag = "";
tag = cb + "_I3";
setPointValue(equipment, tag, 40.0f);
tag = "";
tag = cb + "_kVA";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kVA1";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kVA2";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kVA3";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kVAR";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kW";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kW1";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kW2";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kW3";
setPointValue(equipment, tag, 150.0f);
tag = "";
tag = cb + "_kWh";
setPointValue(equipment, tag, 0.5f);
tag = "";
tag = cb + "_PF";
setPointValue(equipment, tag, 150.0f);
}
cb_num++;
}
Serial.printf("CB_ CLosed = %f \n", cb_count);
if (cb_count > 0.0f){
Serial.println("At least one breaker closed...");
float percent_load = getPointValue(equipment, "Px Load");
float cb_load = 400.0f * (percent_load /1000.0f);
setPointValue(equipment, "Input_I1", cb_count * cb_load *100.0f);
setPointValue(equipment, "Input_I2", cb_count * cb_load *100.0f);
setPointValue(equipment, "Input_I3", cb_count * cb_load *100.0f);
setPointValue(equipment, "Output_I1", cb_count * cb_load *100.0f);
setPointValue(equipment, "Output_I2", cb_count * cb_load*100.0f);
setPointValue(equipment, "Output_I3", cb_count * cb_load*100.0f);
setPointValue(equipment, "Output_IG", cb_count * 750.0f);
setPointValue(equipment, "Output_IN", cb_count * 482.0f);
float pf = 0.92f;
setPointValue(equipment, "Input_PF", pf * 930.0f);
setPointValue(equipment, "Output_PF", pf);
float i1 = getPointValue(equipment, "Input_I1");
float i2 = getPointValue(equipment, "Input_I2");
float i3 = getPointValue(equipment, "Input_I3");
setPointValue(equipment, "Input_kW", 480.0f * ((i1 + i2 + i3) / 3.0f));
float kW = getPointValue(equipment, "Input_kW");
setPointValue(equipment, "Input_kVA", kW * 1.732f);
setPointValue(equipment, "Output_kVA1", (kW * 1.732f)/3.0f);
setPointValue(equipment, "Output_kVA2", (kW * 1.732f)/3.0f);
setPointValue(equipment, "Output_kVA3", (kW * 1.732f)/3.0f);
setPointValue(equipment, "Input_kVAR", kW * 1.732f* pf);
setPointValue(equipment, "Output_kVAR", kW * 1.732f* pf);
setPointValue(equipment, "Output_kW1", kW /3.0f);
setPointValue(equipment, "Output_kW2", kW /3.0f);
setPointValue(equipment, "Output_kW3", kW /3.0f);
setPointValue(equipment, "Output_kWh", 1423.0f);
setPointValue(equipment, "Input_V_AB", 4800.0f);
setPointValue(equipment, "Input_V_AN", 2700.0f);
setPointValue(equipment, "Input_V_BC", 4800.0f);
setPointValue(equipment, "Input_V_BN", 2700.0f);
setPointValue(equipment, "Input_V_CA", 4800.0f);
setPointValue(equipment, "Input_V_CN", 2700.0f);
setPointValue(equipment, "Input_LL_Avg", 4800.0f);
setPointValue(equipment, "Input_LN_Avg", 2700.0f);
setPointValue(equipment, "Output_V_AB", 4800.0f);
setPointValue(equipment, "Output_V_AN", 2700.0f);
setPointValue(equipment, "Output_V_BC", 4800.0f);
setPointValue(equipment, "Output_V_BN", 2700.0f);
setPointValue(equipment, "Output_V_CA", 4800.0f);
setPointValue(equipment, "Output_V_CN", 2700.0f);
} else {
Serial.println("No breaker closed...");
setPointValue(equipment, "Input_I1", 50.0f);
setPointValue(equipment, "Input_I2", 50.0f);
setPointValue(equipment, "Input_I3", 50.0f);
setPointValue(equipment, "Output_I1", 50.0f);
setPointValue(equipment, "Output_I2", 50.0f);
setPointValue(equipment, "Output_I3", 50.0f);
setPointValue(equipment, "Output_IG", 50.0f);
setPointValue(equipment, "Output_IN", 50.0f);
setPointValue(equipment, "Input_PF", 0.0f);
setPointValue(equipment, "Output_PF", 0.0f);
setPointValue(equipment, "Input_kW", 0.0f);
setPointValue(equipment, "Input_kVA", 0.0f);
setPointValue(equipment, "Output_kVA1", 0.0f);
setPointValue(equipment, "Output_kVA2", 0.0f);
setPointValue(equipment, "Output_kVA3", 0.0f);
setPointValue(equipment, "Input_kVAR", 0.0f);
setPointValue(equipment, "Output_kVAR", 0.0f);
setPointValue(equipment, "Output_kW1", 0.0f);
setPointValue(equipment, "Output_kW2", 0.0f);
setPointValue(equipment, "Output_kW3", 0.0f);
setPointValue(equipment, "Output_kWh", 1.0f);
setPointValue(equipment, "Input_V_AB", 4800.0f);
setPointValue(equipment, "Input_V_AN", 2700.0f);
setPointValue(equipment, "Input_V_BC", 4800.0f);
setPointValue(equipment, "Input_V_BN", 2700.0f);
setPointValue(equipment, "Input_V_CA", 4800.0f);
setPointValue(equipment, "Input_V_CN", 2700.0f);
setPointValue(equipment, "Input_LL_Avg", 4800.0f);
setPointValue(equipment, "Input_LN_Avg", 2700.0f);
setPointValue(equipment, "Output_V_AB", 10.0f);
setPointValue(equipment, "Output_V_AN", 10.0f);
setPointValue(equipment, "Output_V_BC", 10.0f);
setPointValue(equipment, "Output_V_BN", 10.0f);
setPointValue(equipment, "Output_V_CA", 10.0f);
setPointValue(equipment, "Output_V_CN", 10.0f);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;

View File

@@ -79,6 +79,16 @@ template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Input_V_AB", 0.0f);
setPointValue(equipment, "Input_V_AN", 0.0f);
setPointValue(equipment, "Input_V_BC", 0.0f);
setPointValue(equipment, "Input_V_BN", 0.0f);
setPointValue(equipment, "Input_V_CA", 0.0f);
setPointValue(equipment, "Input_V_CN", 0.0f);
setPointValue(equipment, "Input_LL_Avg", 0.0f);
setPointValue(equipment, "Input_LN_Avg", 0.0f);
setPointValue(equipment, "Output_PF", 0.0f);
}
/**

View File

@@ -21,11 +21,11 @@
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_ssid"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 178); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(254, 254, 254, 0); /**< @brief The subnet mask. */
const char *ssid = "QTS_CDR_Arduino"; /**< @brief The SSID of the WiFi network. */
const char *password = "123abc456"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 33, 181); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
@@ -46,8 +46,6 @@
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
@@ -64,441 +62,189 @@ modbusMap mb_map[] = {
// Write Registers (as Input Registers - 3X)
//***************************************
{HR, 9, 0, "Px Ctrl"},
{HR, 10, 0, "Px Rating"}, //Watts
{HR, 10, 0, "Px Rating"}, //Amps
{HR, 11, 0, "Px Load"}, //%load
{COIL, 9, 0, "Px_CB0"},
{COIL, 10, 0, "Px_CB1"},
{COIL, 11, 0, "Px_CB2"},
{COIL, 12, 0, "Px_CB3"},
{COIL, 13, 0, "Px_CB4"},
{COIL, 14, 0, "Px_CB5"},
{COIL, 15, 0, "Px_CB6"},
{COIL, 16, 0, "Px_CB7"},
{COIL, 17, 0, "Px_CB8"},
{HR, 19, 0, "Px_Input"},
{HR, 20, 0, "Px_CB1"},
{HR, 21, 0, "Px_CB2"},
{HR, 22, 0, "Px_CB3"},
{HR, 23, 0, "Px_CB4"},
{HR, 24, 0, "Px_CB5"},
{HR, 25, 0, "Px_CB6"},
{HR, 26, 0, "Px_CB7"},
{HR, 27, 0, "Px_CB8"},
{HR, 28, 0, "Px_Output"},
// System Status
{IR_FLOAT, 0, 0, "CB0_V1N" },
{IR_FLOAT, 2, 0, "CB0_V2N" },
{IR_FLOAT, 4, 0, "CB0_V3N" },
{IR_FLOAT, 6, 0, "CB0_I1" },
{IR_FLOAT, 8, 0, "CB0_I2" },
{IR_FLOAT, 10, 0, "CB0_I3" },
{IR_FLOAT, 12, 0, "CB0_L1KW" },
{IR_FLOAT, 14, 0, "CB0_L2KW" },
{IR_FLOAT, 16, 0, "CB0_L3KW" },
{IR_FLOAT, 18, 0, "CB0_L1KVar" },
{IR_FLOAT, 20, 0, "CB0_L2KVar" },
{IR_FLOAT, 22, 0, "CB0_L3KVar" },
{IR_FLOAT, 24, 0, "CB0_L1KVA" },
{IR_FLOAT, 26, 0, "CB0_L2KVA" },
{IR_FLOAT, 28, 0, "CB0_L3KVA" },
{IR_FLOAT, 30, 0, "CB0_L1PF" },
{IR_FLOAT, 32, 0, "CB0_L2PF" },
{IR_FLOAT, 34, 0, "CB0_L3PF" },
{IR_FLOAT, 36, 0, "CB0_V1THD" },
{IR_FLOAT, 38, 0, "CB0_V2THD" },
{IR_FLOAT, 40, 0, "CB0_V3THD" },
{IR_FLOAT, 42, 0, "CB0_I1THD" },
{IR_FLOAT, 44, 0, "CB0_I2THD" },
{IR_FLOAT, 46, 0, "CB0_I3THD" },
{IR_FLOAT, 48, 0, "CB0_I1Kfactor" },
{IR_FLOAT, 50, 0, "CB0_I2Kfactor" },
{IR_FLOAT, 52, 0, "CB0_I3Kfactor" },
{IR_FLOAT, 54, 0, "CB0_I1TDD" },
{IR_FLOAT, 56, 0, "CB0_I2TDD" },
{IR_FLOAT, 58, 0, "CB0_I3TDD" },
{IR_FLOAT, 60, 0, "CB0_V12" },
{IR_FLOAT, 62, 0, "CB0_V23" },
{IR_FLOAT, 64, 0, "CB0_V31" },
{IR_FLOAT, 66, 0, "CB0_TotalKW" },
{IR_FLOAT, 68, 0, "CB0_TotalKVar" },
{IR_FLOAT, 70, 0, "CB0_TotalKVA" },
{IR_FLOAT, 72, 0, "CB0_TotalPF" },
{IR_FLOAT, 74, 0, "CB0_TotalPFLag" },
{IR_FLOAT, 76, 0, "CB0_TotalPFLead" },
{IR_FLOAT, 78, 0, "CB0_TotalKWImport" },
{IR_FLOAT, 80, 0, "CB0_TotalKWExport" },
{IR_FLOAT, 82, 0, "CB0_TotalKVarImport" },
{IR_FLOAT, 84, 0, "CB0_TotalKVarExport" },
{IR_FLOAT, 86, 0, "CB0_LN_Avg" },
{IR_FLOAT, 88, 0, "CB0_LL_Avg" },
{IR_FLOAT, 92, 0, "CB0_TotalKWh" },
// PDU Input
{IR_LONG, 6, 0, "Input_I1" }, //0.01
{IR_LONG, 8, 0, "Input_I2" }, //0.01
{IR_LONG, 10, 0, "Input_I3" }, //0.01
{IR_LONG, 70, 0, "Input_kVA" }, //0.001
{IR_LONG, 68, 0, "Input_KVAR" }, //0.001
{IR_LONG, 66, 0, "Input_kW" }, //0.001
{IR_LONG, 66, 0, "Input_kWh" }, //0.1
{IR_LONG, 72, 0, "Input_PF" }, //0.001
{IR_LONG, 60, 0, "Input_V_AB" }, //0.1
{IR_LONG, 0, 0, "Input_V_AN" }, //0.1
{IR_LONG, 62, 0, "Input_V_BC" }, //0.1
{IR_LONG, 2, 0, "Input_V_BN" }, //0.1
{IR_LONG, 64, 0, "Input_V_CA" }, //0.1
{IR_LONG, 4, 0, "Input_V_CN" }, //0.1
{IR_LONG, 88, 0, "Input_LL_Avg" },//0.1
{IR_LONG, 86, 0, "Input_LN_Avg" },//0.1
// Circuit Breaker 1 (CB1)
{IR_LONG, 106, 0, "CB1_I1" }, //0.01x
{IR_LONG, 108, 0, "CB1_I2" }, //0.01x
{IR_LONG, 110, 0, "CB1_I3" }, //0.01x
{IR_LONG, 170, 0, "CB1_kVA" }, //0.001x
{IR_LONG, 124, 0, "CB1_kVA1" }, //0.001x
{IR_LONG, 126, 0, "CB1_kVA2" }, //0.001x
{IR_LONG, 128, 0, "CB1_kVA3" }, //0.001x
{IR_LONG, 168, 0, "CB1_kVAR" }, //0.001x
{IR_LONG, 166, 0, "CB1_kW" }, //0.001x
{IR_LONG, 112, 0, "CB1_kW1" }, //0.001x
{IR_LONG, 114, 0, "CB1_kW2" }, //0.001x
{IR_LONG, 116, 0, "CB1_kW3" }, //0.001x
{IR_LONG, 1113, 0, "CB1_kWh" },
{IR_LONG, 172, 0, "CB1_PF" }, //0.001x
// Circuit Breaker 1 (OB01)
{IR_FLOAT, 100, 0, "CB1_V1N" },
{IR_FLOAT, 102, 0, "CB1_V2N" },
{IR_FLOAT, 104, 0, "CB1_V3N" },
{IR_FLOAT, 106, 0, "CB1_I1" },
{IR_FLOAT, 108, 0, "CB1_I2" },
{IR_FLOAT, 110, 0, "CB1_I3" },
{IR_FLOAT, 112, 0, "CB1_L1KW" },
{IR_FLOAT, 114, 0, "CB1_L2KW" },
{IR_FLOAT, 116, 0, "CB1_L3KW" },
{IR_FLOAT, 118, 0, "CB1_L1KVar" },
{IR_FLOAT, 120, 0, "CB1_L2KVar" },
{IR_FLOAT, 122, 0, "CB1_L3KVar" },
{IR_FLOAT, 124, 0, "CB1_L1KVA" },
{IR_FLOAT, 126, 0, "CB1_L2KVA" },
{IR_FLOAT, 128, 0, "CB1_L3KVA" },
{IR_FLOAT, 130, 0, "CB1_L1PF" },
{IR_FLOAT, 132, 0, "CB1_L2PF" },
{IR_FLOAT, 134, 0, "CB1_L3PF" },
{IR_FLOAT, 136, 0, "CB1_V1THD" },
{IR_FLOAT, 138, 0, "CB1_V2THD" },
{IR_FLOAT, 140, 0, "CB1_V3THD" },
{IR_FLOAT, 142, 0, "CB1_I1THD" },
{IR_FLOAT, 144, 0, "CB1_I2THD" },
{IR_FLOAT, 146, 0, "CB1_I3THD" },
{IR_FLOAT, 148, 0, "CB1_I1Kfactor" },
{IR_FLOAT, 150, 0, "CB1_I2Kfactor" },
{IR_FLOAT, 152, 0, "CB1_I3Kfactor" },
{IR_FLOAT, 154, 0, "CB1_I1TDD" },
{IR_FLOAT, 156, 0, "CB1_I2TDD" },
{IR_FLOAT, 158, 0, "CB1_I3TDD" },
{IR_FLOAT, 160, 0, "CB1_V12" },
{IR_FLOAT, 162, 0, "CB1_V23" },
{IR_FLOAT, 164, 0, "CB1_V31" },
{IR_FLOAT, 166, 0, "CB1_TotalKW" },
{IR_FLOAT, 168, 0, "CB1_TotalKVar" },
{IR_FLOAT, 170, 0, "CB1_TotalKVA" },
{IR_FLOAT, 172, 0, "CB1_TotalPF" },
{IR_FLOAT, 174, 0, "CB1_TotalPFLag" },
{IR_FLOAT, 176, 0, "CB1_TotalPFLead" },
{IR_FLOAT, 178, 0, "CB1_TotalKWImport" },
{IR_FLOAT, 180, 0, "CB1_TotalKWExport" },
{IR_FLOAT, 182, 0, "CB1_TotalKVarImport" },
{IR_FLOAT, 184, 0, "CB1_TotalKVarExport" },
{IR_FLOAT, 186, 0, "CB1_LN_Avg" },
{IR_FLOAT, 188, 0, "CB1_LL_Avg" },
// Circuit Breaker 2 (CB1)
{IR_LONG, 206, 0, "CB2_I1" }, //0.01x
{IR_LONG, 208, 0, "CB2_I2" }, //0.01x
{IR_LONG, 210, 0, "CB2_I3" }, //0.01x
{IR_LONG, 270, 0, "CB2_kVA" }, //0.001x
{IR_LONG, 224, 0, "CB2_kVA1" }, //0.001x
{IR_LONG, 226, 0, "CB2_kVA2" }, //0.001x
{IR_LONG, 228, 0, "CB2_kVA3" }, //0.001x
{IR_LONG, 268, 0, "CB2_kVAR" }, //0.001x
{IR_LONG, 266, 0, "CB2_kW" }, //0.001x
{IR_LONG, 212, 0, "CB2_kW1" }, //0.001x
{IR_LONG, 214, 0, "CB2_kW2" }, //0.001x
{IR_LONG, 216, 0, "CB2_kW3" }, //0.001x
{IR_LONG, 1168, 0, "CB2_kWh" },
{IR_LONG, 272, 0, "CB2_PF" }, //0.001x
// Circuit Breaker 2 (OB02)
{IR_FLOAT, 200, 0, "CB2_V1N" },
{IR_FLOAT, 202, 0, "CB2_V2N" },
{IR_FLOAT, 204, 0, "CB2_V3N" },
{IR_FLOAT, 206, 0, "CB2_I1" },
{IR_FLOAT, 208, 0, "CB2_I2" },
{IR_FLOAT, 210, 0, "CB2_I3" },
{IR_FLOAT, 212, 0, "CB2_L1KW" },
{IR_FLOAT, 214, 0, "CB2_L2KW" },
{IR_FLOAT, 216, 0, "CB2_L3KW" },
{IR_FLOAT, 218, 0, "CB2_L1KVar" },
{IR_FLOAT, 220, 0, "CB2_L2KVar" },
{IR_FLOAT, 222, 0, "CB2_L3KVar" },
{IR_FLOAT, 224, 0, "CB2_L1KVA" },
{IR_FLOAT, 226, 0, "CB2_L2KVA" },
{IR_FLOAT, 228, 0, "CB2_L3KVA" },
{IR_FLOAT, 230, 0, "CB2_L1PF" },
{IR_FLOAT, 232, 0, "CB2_L2PF" },
{IR_FLOAT, 234, 0, "CB2_L3PF" },
{IR_FLOAT, 236, 0, "CB2_V1THD" },
{IR_FLOAT, 238, 0, "CB2_V2THD" },
{IR_FLOAT, 240, 0, "CB2_V3THD" },
{IR_FLOAT, 242, 0, "CB2_I1THD" },
{IR_FLOAT, 244, 0, "CB2_I2THD" },
{IR_FLOAT, 246, 0, "CB2_I3THD" },
{IR_FLOAT, 248, 0, "CB2_I1Kfactor" },
{IR_FLOAT, 250, 0, "CB2_I2Kfactor" },
{IR_FLOAT, 252, 0, "CB2_I3Kfactor" },
{IR_FLOAT, 254, 0, "CB2_I1TDD" },
{IR_FLOAT, 256, 0, "CB2_I2TDD" },
{IR_FLOAT, 258, 0, "CB2_I3TDD" },
{IR_FLOAT, 260, 0, "CB2_V12" },
{IR_FLOAT, 262, 0, "CB2_V23" },
{IR_FLOAT, 264, 0, "CB2_V31" },
{IR_FLOAT, 266, 0, "CB2_TotalKW" },
{IR_FLOAT, 268, 0, "CB2_TotalKVar" },
{IR_FLOAT, 270, 0, "CB2_TotalKVA" },
{IR_FLOAT, 272, 0, "CB2_TotalPF" },
{IR_FLOAT, 274, 0, "CB2_TotalPFLag" },
{IR_FLOAT, 276, 0, "CB2_TotalPFLead" },
{IR_FLOAT, 278, 0, "CB2_TotalKWImport" },
{IR_FLOAT, 280, 0, "CB2_TotalKWExport" },
{IR_FLOAT, 282, 0, "CB2_TotalKVarImport" },
{IR_FLOAT, 284, 0, "CB2_TotalKVarExport" },
{IR_FLOAT, 286, 0, "CB2_LN_Avg" },
{IR_FLOAT, 288, 0, "CB2_LL_Avg" },
// Circuit Breaker 3 (CB1)
{IR_LONG, 306, 0, "CB3_I1" }, //0.01x
{IR_LONG, 308, 0, "CB3_I2" }, //0.01x
{IR_LONG, 310, 0, "CB3_I3" }, //0.01x
{IR_LONG, 370, 0, "CB3_kVA" }, //0.001x
{IR_LONG, 324, 0, "CB3_kVA1" }, //0.001x
{IR_LONG, 326, 0, "CB3_kVA2" }, //0.001x
{IR_LONG, 328, 0, "CB3_kVA3" }, //0.001x
{IR_LONG, 368, 0, "CB3_kVAR" }, //0.001x
{IR_LONG, 366, 0, "CB3_kW" }, //0.001x
{IR_LONG, 312, 0, "CB3_kW1" }, //0.001x
{IR_LONG, 314, 0, "CB3_kW2" }, //0.001x
{IR_LONG, 316, 0, "CB3_kW3" }, //0.001x
{IR_LONG, 1223, 0, "CB3_kWh" },
{IR_LONG, 372, 0, "CB3_PF" }, //0.001x
// Circuit Breaker 3 (OB03)
{IR_FLOAT, 300, 0, "CB3_V1N" },
{IR_FLOAT, 302, 0, "CB3_V2N" },
{IR_FLOAT, 304, 0, "CB3_V3N" },
{IR_FLOAT, 306, 0, "CB3_I1" },
{IR_FLOAT, 308, 0, "CB3_I2" },
{IR_FLOAT, 310, 0, "CB3_I3" },
{IR_FLOAT, 312, 0, "CB3_L1KW" },
{IR_FLOAT, 314, 0, "CB3_L2KW" },
{IR_FLOAT, 316, 0, "CB3_L3KW" },
{IR_FLOAT, 318, 0, "CB3_L1KVar" },
{IR_FLOAT, 320, 0, "CB3_L2KVar" },
{IR_FLOAT, 322, 0, "CB3_L3KVar" },
{IR_FLOAT, 324, 0, "CB3_L1KVA" },
{IR_FLOAT, 326, 0, "CB3_L2KVA" },
{IR_FLOAT, 328, 0, "CB3_L3KVA" },
{IR_FLOAT, 330, 0, "CB3_L1PF" },
{IR_FLOAT, 332, 0, "CB3_L2PF" },
{IR_FLOAT, 334, 0, "CB3_L3PF" },
{IR_FLOAT, 336, 0, "CB3_V1THD" },
{IR_FLOAT, 338, 0, "CB3_V2THD" },
{IR_FLOAT, 340, 0, "CB3_V3THD" },
{IR_FLOAT, 342, 0, "CB3_I1THD" },
{IR_FLOAT, 344, 0, "CB3_I2THD" },
{IR_FLOAT, 346, 0, "CB3_I3THD" },
{IR_FLOAT, 348, 0, "CB3_I1Kfactor" },
{IR_FLOAT, 350, 0, "CB3_I2Kfactor" },
{IR_FLOAT, 352, 0, "CB3_I3Kfactor" },
{IR_FLOAT, 354, 0, "CB3_I1TDD" },
{IR_FLOAT, 356, 0, "CB3_I2TDD" },
{IR_FLOAT, 358, 0, "CB3_I3TDD" },
{IR_FLOAT, 360, 0, "CB3_V12" },
{IR_FLOAT, 362, 0, "CB3_V23" },
{IR_FLOAT, 364, 0, "CB3_V31" },
{IR_FLOAT, 366, 0, "CB3_TotalKW" },
{IR_FLOAT, 368, 0, "CB3_TotalKVar" },
{IR_FLOAT, 370, 0, "CB3_TotalKVA" },
{IR_FLOAT, 372, 0, "CB3_TotalPF" },
{IR_FLOAT, 374, 0, "CB3_TotalPFLag" },
{IR_FLOAT, 376, 0, "CB3_TotalPFLead" },
{IR_FLOAT, 378, 0, "CB3_TotalKWImport" },
{IR_FLOAT, 380, 0, "CB3_TotalKWExport" },
{IR_FLOAT, 382, 0, "CB3_TotalKVarImport" },
{IR_FLOAT, 384, 0, "CB3_TotalKVarExport" },
{IR_FLOAT, 386, 0, "CB3_LN_Avg" },
{IR_FLOAT, 388, 0, "CB3_LL_Avg" },
// Circuit Breaker 4 (CB1)
{IR_LONG, 406, 0, "CB4_I1" }, //0.01x
{IR_LONG, 408, 0, "CB4_I2" }, //0.01x
{IR_LONG, 410, 0, "CB4_I3" }, //0.01x
{IR_LONG, 470, 0, "CB4_kVA" }, //0.001x
{IR_LONG, 424, 0, "CB4_kVA1" }, //0.001x
{IR_LONG, 426, 0, "CB4_kVA2" }, //0.001x
{IR_LONG, 428, 0, "CB4_kVA3" }, //0.001x
{IR_LONG, 468, 0, "CB4_kVAR" }, //0.001x
{IR_LONG, 466, 0, "CB4_kW" }, //0.001x
{IR_LONG, 412, 0, "CB4_kW1" }, //0.001x
{IR_LONG, 414, 0, "CB4_kW2" }, //0.001x
{IR_LONG, 416, 0, "CB4_kW3" }, //0.001x
{IR_LONG, 1278, 0, "CB4_kWh" },
{IR_LONG, 472, 0, "CB4_PF" }, //0.001x
// Circuit Breaker 4 (OB04)
{IR_FLOAT, 400, 0, "CB4_V1N" },
{IR_FLOAT, 402, 0, "CB4_V2N" },
{IR_FLOAT, 404, 0, "CB4_V3N" },
{IR_FLOAT, 406, 0, "CB4_I1" },
{IR_FLOAT, 408, 0, "CB4_I2" },
{IR_FLOAT, 410, 0, "CB4_I3" },
{IR_FLOAT, 412, 0, "CB4_L1KW" },
{IR_FLOAT, 414, 0, "CB4_L2KW" },
{IR_FLOAT, 416, 0, "CB4_L3KW" },
{IR_FLOAT, 418, 0, "CB4_L1KVar" },
{IR_FLOAT, 420, 0, "CB4_L2KVar" },
{IR_FLOAT, 422, 0, "CB4_L3KVar" },
{IR_FLOAT, 424, 0, "CB4_L1KVA" },
{IR_FLOAT, 426, 0, "CB4_L2KVA" },
{IR_FLOAT, 428, 0, "CB4_L3KVA" },
{IR_FLOAT, 430, 0, "CB4_L1PF" },
{IR_FLOAT, 432, 0, "CB4_L2PF" },
{IR_FLOAT, 434, 0, "CB4_L3PF" },
{IR_FLOAT, 436, 0, "CB4_V1THD" },
{IR_FLOAT, 438, 0, "CB4_V2THD" },
{IR_FLOAT, 440, 0, "CB4_V3THD" },
{IR_FLOAT, 442, 0, "CB4_I1THD" },
{IR_FLOAT, 444, 0, "CB4_I2THD" },
{IR_FLOAT, 446, 0, "CB4_I3THD" },
{IR_FLOAT, 448, 0, "CB4_I1Kfactor" },
{IR_FLOAT, 450, 0, "CB4_I2Kfactor" },
{IR_FLOAT, 452, 0, "CB4_I3Kfactor" },
{IR_FLOAT, 454, 0, "CB4_I1TDD" },
{IR_FLOAT, 456, 0, "CB4_I2TDD" },
{IR_FLOAT, 458, 0, "CB4_I3TDD" },
{IR_FLOAT, 460, 0, "CB4_V12" },
{IR_FLOAT, 462, 0, "CB4_V23" },
{IR_FLOAT, 464, 0, "CB4_V31" },
{IR_FLOAT, 466, 0, "CB4_TotalKW" },
{IR_FLOAT, 468, 0, "CB4_TotalKVar" },
{IR_FLOAT, 470, 0, "CB4_TotalKVA" },
{IR_FLOAT, 472, 0, "CB4_TotalPF" },
{IR_FLOAT, 474, 0, "CB4_TotalPFLag" },
{IR_FLOAT, 476, 0, "CB4_TotalPFLead" },
{IR_FLOAT, 478, 0, "CB4_TotalKWImport" },
{IR_FLOAT, 480, 0, "CB4_TotalKWExport" },
{IR_FLOAT, 482, 0, "CB4_TotalKVarImport" },
{IR_FLOAT, 484, 0, "CB4_TotalKVarExport" },
{IR_FLOAT, 486, 0, "CB4_LN_Avg" },
{IR_FLOAT, 488, 0, "CB4_LL_Avg" },
// Circuit Breaker 5 (CB1)
{IR_LONG, 506, 0, "CB5_I1" }, //0.01x
{IR_LONG, 508, 0, "CB5_I2" }, //0.01x
{IR_LONG, 510, 0, "CB5_I3" }, //0.01x
{IR_LONG, 570, 0, "CB5_kVA" }, //0.001x
{IR_LONG, 524, 0, "CB5_kVA1" }, //0.001x
{IR_LONG, 526, 0, "CB5_kVA2" }, //0.001x
{IR_LONG, 528, 0, "CB5_kVA3" }, //0.001x
{IR_LONG, 568, 0, "CB5_kVAR" }, //0.001x
{IR_LONG, 566, 0, "CB5_kW" }, //0.001x
{IR_LONG, 512, 0, "CB5_kW1" }, //0.001x
{IR_LONG, 514, 0, "CB5_kW2" }, //0.001x
{IR_LONG, 516, 0, "CB5_kW3" }, //0.001x
{IR_LONG, 1333, 0, "CB5_kWh" },
{IR_LONG, 572, 0, "CB5_PF" }, //0.001x
// Circuit Breaker 5 (OB05)
{IR_FLOAT, 500, 0, "CB5_V1N" },
{IR_FLOAT, 502, 0, "CB5_V2N" },
{IR_FLOAT, 504, 0, "CB5_V3N" },
{IR_FLOAT, 506, 0, "CB5_I1" },
{IR_FLOAT, 508, 0, "CB5_I2" },
{IR_FLOAT, 510, 0, "CB5_I3" },
{IR_FLOAT, 512, 0, "CB5_L1KW" },
{IR_FLOAT, 514, 0, "CB5_L2KW" },
{IR_FLOAT, 516, 0, "CB5_L3KW" },
{IR_FLOAT, 518, 0, "CB5_L1KVar" },
{IR_FLOAT, 520, 0, "CB5_L2KVar" },
{IR_FLOAT, 522, 0, "CB5_L3KVar" },
{IR_FLOAT, 524, 0, "CB5_L1KVA" },
{IR_FLOAT, 526, 0, "CB5_L2KVA" },
{IR_FLOAT, 528, 0, "CB5_L3KVA" },
{IR_FLOAT, 530, 0, "CB5_L1PF" },
{IR_FLOAT, 532, 0, "CB5_L2PF" },
{IR_FLOAT, 534, 0, "CB5_L3PF" },
{IR_FLOAT, 536, 0, "CB5_V1THD" },
{IR_FLOAT, 538, 0, "CB5_V2THD" },
{IR_FLOAT, 540, 0, "CB5_V3THD" },
{IR_FLOAT, 542, 0, "CB5_I1THD" },
{IR_FLOAT, 544, 0, "CB5_I2THD" },
{IR_FLOAT, 546, 0, "CB5_I3THD" },
{IR_FLOAT, 548, 0, "CB5_I1Kfactor" },
{IR_FLOAT, 550, 0, "CB5_I2Kfactor" },
{IR_FLOAT, 552, 0, "CB5_I3Kfactor" },
{IR_FLOAT, 554, 0, "CB5_I1TDD" },
{IR_FLOAT, 556, 0, "CB5_I2TDD" },
{IR_FLOAT, 558, 0, "CB5_I3TDD" },
{IR_FLOAT, 560, 0, "CB5_V12" },
{IR_FLOAT, 562, 0, "CB5_V23" },
{IR_FLOAT, 564, 0, "CB5_V31" },
{IR_FLOAT, 566, 0, "CB5_TotalKW" },
{IR_FLOAT, 568, 0, "CB5_TotalKVar" },
{IR_FLOAT, 570, 0, "CB5_TotalKVA" },
{IR_FLOAT, 572, 0, "CB5_TotalPF" },
{IR_FLOAT, 574, 0, "CB5_TotalPFLag" },
{IR_FLOAT, 576, 0, "CB5_TotalPFLead" },
{IR_FLOAT, 578, 0, "CB5_TotalKWImport" },
{IR_FLOAT, 580, 0, "CB5_TotalKWExport" },
{IR_FLOAT, 582, 0, "CB5_TotalKVarImport" },
{IR_FLOAT, 584, 0, "CB5_TotalKVarExport" },
{IR_FLOAT, 586, 0, "CB5_LN_Avg" },
{IR_FLOAT, 588, 0, "CB5_LL_Avg" },
// Circuit Breaker 6 (CB1)
{IR_LONG, 606, 0, "CB6_I1" }, //0.01x
{IR_LONG, 608, 0, "CB6_I2" }, //0.01x
{IR_LONG, 610, 0, "CB6_I3" }, //0.01x
{IR_LONG, 670, 0, "CB6_kVA" }, //0.001x
{IR_LONG, 624, 0, "CB6_kVA1" }, //0.001x
{IR_LONG, 626, 0, "CB6_kVA2" }, //0.001x
{IR_LONG, 628, 0, "CB6_kVA3" }, //0.001x
{IR_LONG, 668, 0, "CB6_kVAR" }, //0.001x
{IR_LONG, 666, 0, "CB6_kW" }, //0.001x
{IR_LONG, 612, 0, "CB6_kW1" }, //0.001x
{IR_LONG, 614, 0, "CB6_kW2" }, //0.001x
{IR_LONG, 616, 0, "CB6_kW3" }, //0.001x
{IR_LONG, 1388, 0, "CB6_kWh" },
{IR_LONG, 672, 0, "CB6_PF" }, //0.001x
// Circuit Breaker 6 (OB06)
{IR_FLOAT, 600, 0, "CB6_V1N" },
{IR_FLOAT, 602, 0, "CB6_V2N" },
{IR_FLOAT, 604, 0, "CB6_V3N" },
{IR_FLOAT, 606, 0, "CB6_I1" },
{IR_FLOAT, 608, 0, "CB6_I2" },
{IR_FLOAT, 610, 0, "CB6_I3" },
{IR_FLOAT, 612, 0, "CB6_L1KW" },
{IR_FLOAT, 614, 0, "CB6_L2KW" },
{IR_FLOAT, 616, 0, "CB6_L3KW" },
{IR_FLOAT, 618, 0, "CB6_L1KVar" },
{IR_FLOAT, 620, 0, "CB6_L2KVar" },
{IR_FLOAT, 622, 0, "CB6_L3KVar" },
{IR_FLOAT, 624, 0, "CB6_L1KVA" },
{IR_FLOAT, 626, 0, "CB6_L2KVA" },
{IR_FLOAT, 628, 0, "CB6_L3KVA" },
{IR_FLOAT, 630, 0, "CB6_L1PF" },
{IR_FLOAT, 632, 0, "CB6_L2PF" },
{IR_FLOAT, 634, 0, "CB6_L3PF" },
{IR_FLOAT, 636, 0, "CB6_V1THD" },
{IR_FLOAT, 638, 0, "CB6_V2THD" },
{IR_FLOAT, 640, 0, "CB6_V3THD" },
{IR_FLOAT, 642, 0, "CB6_I1THD" },
{IR_FLOAT, 644, 0, "CB6_I2THD" },
{IR_FLOAT, 646, 0, "CB6_I3THD" },
{IR_FLOAT, 648, 0, "CB6_I1Kfactor" },
{IR_FLOAT, 650, 0, "CB6_I2Kfactor" },
{IR_FLOAT, 652, 0, "CB6_I3Kfactor" },
{IR_FLOAT, 654, 0, "CB6_I1TDD" },
{IR_FLOAT, 656, 0, "CB6_I2TDD" },
{IR_FLOAT, 658, 0, "CB6_I3TDD" },
{IR_FLOAT, 660, 0, "CB6_V12" },
{IR_FLOAT, 662, 0, "CB6_V23" },
{IR_FLOAT, 664, 0, "CB6_V31" },
{IR_FLOAT, 666, 0, "CB6_TotalKW" },
{IR_FLOAT, 668, 0, "CB6_TotalKVar" },
{IR_FLOAT, 670, 0, "CB6_TotalKVA" },
{IR_FLOAT, 672, 0, "CB6_TotalPF" },
{IR_FLOAT, 674, 0, "CB6_TotalPFLag" },
{IR_FLOAT, 676, 0, "CB6_TotalPFLead" },
{IR_FLOAT, 678, 0, "CB6_TotalKWImport" },
{IR_FLOAT, 680, 0, "CB6_TotalKWExport" },
{IR_FLOAT, 682, 0, "CB6_TotalKVarImport" },
{IR_FLOAT, 684, 0, "CB6_TotalKVarExport" },
{IR_FLOAT, 686, 0, "CB6_LN_Avg" },
{IR_FLOAT, 688, 0, "CB6_LL_Avg" },
// Circuit Breaker 7 (OB07)
{IR_FLOAT, 700, 0, "CB7_V1N" },
{IR_FLOAT, 702, 0, "CB7_V2N" },
{IR_FLOAT, 704, 0, "CB7_V3N" },
{IR_FLOAT, 706, 0, "CB7_I1" },
{IR_FLOAT, 708, 0, "CB7_I2" },
{IR_FLOAT, 710, 0, "CB7_I3" },
{IR_FLOAT, 712, 0, "CB7_L1KW" },
{IR_FLOAT, 714, 0, "CB7_L2KW" },
{IR_FLOAT, 716, 0, "CB7_L3KW" },
{IR_FLOAT, 718, 0, "CB7_L1KVar" },
{IR_FLOAT, 720, 0, "CB7_L2KVar" },
{IR_FLOAT, 722, 0, "CB7_L3KVar" },
{IR_FLOAT, 724, 0, "CB7_L1KVA" },
{IR_FLOAT, 726, 0, "CB7_L2KVA" },
{IR_FLOAT, 728, 0, "CB7_L3KVA" },
{IR_FLOAT, 730, 0, "CB7_L1PF" },
{IR_FLOAT, 732, 0, "CB7_L2PF" },
{IR_FLOAT, 734, 0, "CB7_L3PF" },
{IR_FLOAT, 736, 0, "CB7_V1THD" },
{IR_FLOAT, 738, 0, "CB7_V2THD" },
{IR_FLOAT, 740, 0, "CB7_V3THD" },
{IR_FLOAT, 742, 0, "CB7_I1THD" },
{IR_FLOAT, 744, 0, "CB7_I2THD" },
{IR_FLOAT, 746, 0, "CB7_I3THD" },
{IR_FLOAT, 748, 0, "CB7_I1Kfactor" },
{IR_FLOAT, 750, 0, "CB7_I2Kfactor" },
{IR_FLOAT, 752, 0, "CB7_I3Kfactor" },
{IR_FLOAT, 754, 0, "CB7_I1TDD" },
{IR_FLOAT, 756, 0, "CB7_I2TDD" },
{IR_FLOAT, 758, 0, "CB7_I3TDD" },
{IR_FLOAT, 760, 0, "CB7_V12" },
{IR_FLOAT, 762, 0, "CB7_V23" },
{IR_FLOAT, 764, 0, "CB7_V31" },
{IR_FLOAT, 766, 0, "CB7_TotalKW" },
{IR_FLOAT, 768, 0, "CB7_TotalKVar" },
{IR_FLOAT, 770, 0, "CB7_TotalKVA" },
{IR_FLOAT, 772, 0, "CB7_TotalPF" },
{IR_FLOAT, 774, 0, "CB7_TotalPFLag" },
{IR_FLOAT, 776, 0, "CB7_TotalPFLead" },
{IR_FLOAT, 778, 0, "CB7_TotalKWImport" },
{IR_FLOAT, 780, 0, "CB7_TotalKWExport" },
{IR_FLOAT, 782, 0, "CB7_TotalKVarImport" },
{IR_FLOAT, 784, 0, "CB7_TotalKVarExport" },
{IR_FLOAT, 786, 0, "CB7_LN_Avg" },
{IR_FLOAT, 788, 0, "CB7_LL_Avg" },
// Circuit Breaker 8 (OB08)
{IR_FLOAT, 800, 0, "CB8_V1N" },
{IR_FLOAT, 802, 0, "CB8_V2N" },
{IR_FLOAT, 804, 0, "CB8_V3N" },
{IR_FLOAT, 806, 0, "CB8_I1" },
{IR_FLOAT, 808, 0, "CB8_I2" },
{IR_FLOAT, 810, 0, "CB8_I3" },
{IR_FLOAT, 812, 0, "CB8_L1KW" },
{IR_FLOAT, 814, 0, "CB8_L2KW" },
{IR_FLOAT, 816, 0, "CB8_L3KW" },
{IR_FLOAT, 818, 0, "CB8_L1KVar" },
{IR_FLOAT, 820, 0, "CB8_L2KVar" },
{IR_FLOAT, 822, 0, "CB8_L3KVar" },
{IR_FLOAT, 824, 0, "CB8_L1KVA" },
{IR_FLOAT, 826, 0, "CB8_L2KVA" },
{IR_FLOAT, 828, 0, "CB8_L3KVA" },
{IR_FLOAT, 830, 0, "CB8_L1PF" },
{IR_FLOAT, 832, 0, "CB8_L2PF" },
{IR_FLOAT, 834, 0, "CB8_L3PF" },
{IR_FLOAT, 836, 0, "CB8_V1THD" },
{IR_FLOAT, 838, 0, "CB8_V2THD" },
{IR_FLOAT, 840, 0, "CB8_V3THD" },
{IR_FLOAT, 842, 0, "CB8_I1THD" },
{IR_FLOAT, 844, 0, "CB8_I2THD" },
{IR_FLOAT, 846, 0, "CB8_I3THD" },
{IR_FLOAT, 848, 0, "CB8_I1Kfactor" },
{IR_FLOAT, 850, 0, "CB8_I2Kfactor" },
{IR_FLOAT, 852, 0, "CB8_I3Kfactor" },
{IR_FLOAT, 854, 0, "CB8_I1TDD" },
{IR_FLOAT, 856, 0, "CB8_I2TDD" },
{IR_FLOAT, 858, 0, "CB8_I3TDD" },
{IR_FLOAT, 860, 0, "CB8_V12" },
{IR_FLOAT, 862, 0, "CB8_V23" },
{IR_FLOAT, 864, 0, "CB8_V31" },
{IR_FLOAT, 866, 0, "CB8_TotalKW" },
{IR_FLOAT, 868, 0, "CB8_TotalKVar" },
{IR_FLOAT, 870, 0, "CB8_TotalKVA" },
{IR_FLOAT, 872, 0, "CB8_TotalPF" },
{IR_FLOAT, 874, 0, "CB8_TotalPFLag" },
{IR_FLOAT, 876, 0, "CB8_TotalPFLead" },
{IR_FLOAT, 878, 0, "CB8_TotalKWImport" },
{IR_FLOAT, 880, 0, "CB8_TotalKWExport" },
{IR_FLOAT, 882, 0, "CB8_TotalKVarImport" },
{IR_FLOAT, 884, 0, "CB8_TotalKVarExport" },
{IR_FLOAT, 886, 0, "CB8_LN_Avg" },
{IR_FLOAT, 888, 0, "CB8_LL_Avg" },
// Circuit Breaker 7 (CB1)
{IR_LONG, 706, 0, "CB7_I1" }, //0.01x
{IR_LONG, 708, 0, "CB7_I2" }, //0.01x
{IR_LONG, 710, 0, "CB7_I3" }, //0.01x
{IR_LONG, 770, 0, "CB7_kVA" }, //0.001x
{IR_LONG, 724, 0, "CB7_kVA1" }, //0.001x
{IR_LONG, 726, 0, "CB7_kVA2" }, //0.001x
{IR_LONG, 728, 0, "CB7_kVA3" }, //0.001x
{IR_LONG, 768, 0, "CB7_kVAR" }, //0.001x
{IR_LONG, 766, 0, "CB7_kW" }, //0.001x
{IR_LONG, 712, 0, "CB7_kW1" }, //0.001x
{IR_LONG, 714, 0, "CB7_kW2" }, //0.001x
{IR_LONG, 716, 0, "CB7_kW3" }, //0.001x
{IR_LONG, 1443, 0, "CB7_kWh" },
{IR_LONG, 772, 0, "CB7_PF" }, //0.001x
// Circuit Breaker 8 (CB1)
{IR_LONG, 806, 0, "CB8_I1" }, //0.01x
{IR_LONG, 808, 0, "CB8_I2" }, //0.01x
{IR_LONG, 810, 0, "CB8_I3" }, //0.01x
{IR_LONG, 870, 0, "CB8_kVA" }, //0.001x
{IR_LONG, 824, 0, "CB8_kVA1" }, //0.001x
{IR_LONG, 826, 0, "CB8_kVA2" }, //0.001x
{IR_LONG, 828, 0, "CB8_kVA3" }, //0.001x
{IR_LONG, 868, 0, "CB8_kVAR" }, //0.001x
{IR_LONG, 866, 0, "CB8_kW" }, //0.001x
{IR_LONG, 812, 0, "CB8_kW1" }, //0.001x
{IR_LONG, 814, 0, "CB8_kW2" }, //0.001x
{IR_LONG, 816, 0, "CB8_kW3" }, //0.001x
{IR_LONG, 1498, 0, "CB8_kWh" },
{IR_LONG, 872, 0, "CB8_PF" }, //0.001x
// PDU Output
{IR_LONG, 906, 0, "Output_I1" }, //0.01x
{IR_LONG, 908, 0, "Output_I2" },
{IR_LONG, 910, 0, "Output_I3" },
{IR_LONG, 1068, 0, "Output_IG" },
{IR_LONG, 1066, 0, "Output_IN" },
{IR_LONG, 924, 0, "Output_kVA1" }, //0.001
{IR_LONG, 926, 0, "Output_kVA2" },
{IR_LONG, 928, 0, "Output_kVA3" },
{IR_LONG, 968, 0, "Output_kVAR" }, //0.001
{IR_LONG, 912, 0, "Output_kW1" }, //0.001x
{IR_LONG, 914, 0, "Output_kW2" },
{IR_LONG, 916, 0, "Output_kW3" },
{IR_LONG, 1086, 0, "Output_kWh" },
{IR_LONG, 1091, 0, "Output_PF" }, //0.001
{IR_LONG, 960, 0, "Output_V_AB" },
{IR_LONG, 900, 0, "Output_V_AN" }, //0.01x
{IR_LONG, 962, 0, "Output_V_BC" },
{IR_LONG, 902, 0, "Output_V_BN" },
{IR_LONG, 964, 0, "Output_V_CA" },
{IR_LONG, 904, 0, "Output_V_CN" },
{IR, 1550, 0, "CB_Status" },
{IR, 1551, 0, "CB_Tripped" },
};
//Size of modbus map used in FOR cycles, automatically calculated.

View File

@@ -43,7 +43,7 @@ RunningState<ModbusIP>::RunningState() {
addStrategy("Volts CA", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("PF", new SingleValueStrategy(0.9f, 0.05f, 1000));
addStrategy("Frequency", new SingleValueStrategy(60.0f, 0.7f, 1000));
addStrategy("Amps A", new SingleValueStrategy(1.0f, 10.0f, 1000));
addStrategy("Amps B", new SingleValueStrategy(1.0f, 10.0f, 1000));
addStrategy("Amps C", new SingleValueStrategy(1.0f, 10.0f, 1000));

View File

@@ -87,6 +87,7 @@ void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
setPointValue(equipment, "Amps C", 0.0f);
setPointValue(equipment, "kW", 0.0f);
setPointValue(equipment, "kVA", 0.0f);
setPointValue(equipment, "Frequency", 0.0f);
}
/**

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,99 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Totalizer.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("TT01", new SingleValueStrategy(870.0F, 10.0f, 1000));
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. It reads the "ON/OFF Command By BMS" point. If it's 0, it transitions to StandbyState.
* 2. It reads the "Fault Code" point. If it's non-zero, it transitions to FailState,
* passing the corresponding alarm description.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Running update function");
float State_Ctrl = getPointValue(equipment, "Remote_Start");
if (State_Ctrl == 0){
return new StandbyState<ModbusIP>();
}
float TT01 = getPointValue(equipment, "TT01");
float TT02 = getPointValue(equipment, "TT02");
setPointValue(equipment, "TT01_TT02", (TT01 + TT02)/2.0f);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Run Status" for all EC fans to 1 to indicate they are active.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "Status", 1);
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}

View File

@@ -0,0 +1,224 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the StandbyState, which defines
* the behavior of the equipment when it is in an idle or standby mode.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new StandbyState object.
*
* In this state, the equipment is idle. This constructor initializes strategies
* to bring the system to a safe, idle condition. It sets a stable value for
* the SAT reading and creates ramp strategies to bring the CW valve and all
* EC fan speeds down to zero.
*/
template<>
StandbyState<ModbusIP>::StandbyState() {
// You can add initialization code here if needed
addStrategy("In_freq", new SingleValueStrategy(60.0F, 1.0f, 1000));
addStrategy("InV_L1N", new SingleValueStrategy(220.0F, 1.0f, 1000));
addStrategy("InV_L2N", new SingleValueStrategy(220.0F, 1.0f, 1000));
addStrategy("InV_L3N", new SingleValueStrategy(220.0F, 1.0f, 1000));
addStrategy("InV_L12", new SingleValueStrategy(480.0F, 1.0f, 1000));
addStrategy("InV_L23", new SingleValueStrategy(480.0F, 1.0f, 1000));
addStrategy("InV_L31", new SingleValueStrategy(480.0F, 1.0f, 1000));
addStrategy("InTHD_L1N", new SingleValueStrategy(2.5F, 0.5f, 1000));
addStrategy("InTHD_L2N", new SingleValueStrategy(3.1F, 0.5f, 1000));
addStrategy("InTHD_L3N", new SingleValueStrategy(2.4F, 0.5f, 1000));
addStrategy("InTHD_L1_1st", new SingleValueStrategy(2.4F, 0.2f, 1000));
addStrategy("InTHD_L1_3rd", new SingleValueStrategy(2.1F, 0.2f, 1000));
addStrategy("InTHD_L1_5th", new SingleValueStrategy(1.9F, 0.2f, 1000));
addStrategy("InTHD_L1_7th", new SingleValueStrategy(2.1F, 0.2f, 1000));
addStrategy("InTHD_L1_9th", new SingleValueStrategy(1.8F, 0.2f, 1000));
addStrategy("InTHD_L2_1st", new SingleValueStrategy(2.3F, 0.2f, 1000));
addStrategy("InTHD_L2_3rd", new SingleValueStrategy(2.2F, 0.2f, 1000));
addStrategy("InTHD_L2_5th", new SingleValueStrategy(2.4F, 0.2f, 1000));
addStrategy("InTHD_L2_7th", new SingleValueStrategy(2.5F, 0.2f, 1000));
addStrategy("InTHD_L2_9th", new SingleValueStrategy(2.6F, 0.2f, 1000));
addStrategy("InTHD_L3_1st", new SingleValueStrategy(2.2F, 0.2f, 1000));
addStrategy("InTHD_L3_3rd", new SingleValueStrategy(2.3F, 0.2f, 1000));
addStrategy("InTHD_L3_5th", new SingleValueStrategy(2.1F, 0.2f, 1000));
addStrategy("InTHD_L3_7th", new SingleValueStrategy(2.4F, 0.2f, 1000));
addStrategy("InTHD_L3_9th", new SingleValueStrategy(2.5F, 0.2f, 1000));
addStrategy("InTHD_L12", new SingleValueStrategy(3.1F, 0.2f, 1000));
addStrategy("InTHD_L23", new SingleValueStrategy(2.1F, 0.2f, 1000));
addStrategy("InTHD_L31", new SingleValueStrategy(1.8F, 0.2f, 1000));
addStrategy("CB1_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB2_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB3_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB4_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB5_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB6_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB7_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB8_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB9_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB10_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB11_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB12_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB13_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB14_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB15_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("CB16_kW", new SingleValueStrategy(3.1F, 3.0f, 1000));
addStrategy("MainCB_PF", new SingleValueStrategy(0.9F, 0.05f, 1000));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method applies the strategies defined for the standby state (e.g.,
* ramping values to zero).
*
* @warning This method currently does not check for a command to transition to the
* Running state. This logic needs to be added to allow the unit to start.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
// Calculate Avergae for voltage LN points
float InV_L1N = getPointValue(equipment, "InV_L1N");
float InV_L2N = getPointValue(equipment, "InV_L2N");
float InV_L3N = getPointValue(equipment, "InV_L3N");
setPointValue(equipment, "InV_LN_avg", (InV_L1N + InV_L2N + InV_L3N)/3.0f);
// Calculate Avergae for voltage LL points
float InV_L12 = getPointValue(equipment, "InV_L12");
float InV_L23 = getPointValue(equipment, "InV_L23");
float InV_L31 = getPointValue(equipment, "InV_L31");
setPointValue(equipment, "InV_LL_avg", (InV_L12 + InV_L23 + InV_L31)/3.0f);
for (int i = 1; i <= 16; i++) {
std::string cb_name = "CB" + std::to_string(i);
std::string kw_name = cb_name + "_kW";
std::string max_kw_name = cb_name + "_max_kW";
std::string max_current_name = cb_name + "_maxCurrent";
float cb_value = getPointValue(equipment, cb_name.c_str());
if (cb_value == 1.0f){
float kw = 350.0f;
Strategy_Behavior* svs_cb_kW = getStrategy(kw_name.c_str());
static_cast<SingleValueStrategy*>(svs_cb_kW)->setSetpoint(kw);
float in_v_ll_avg = getPointValue(equipment, "InV_LL_avg");
float current = (kw*1000.0f)/(in_v_ll_avg*1.73f);
setPointValue(equipment, max_current_name.c_str(), current);
float max_kw = getPointValue(equipment, max_kw_name.c_str());
if (kw > max_kw){
setPointValue(equipment, max_kw_name.c_str(), kw);
}
float max_current = getPointValue(equipment, max_current_name.c_str());
if (current > max_current){
setPointValue(equipment, max_current_name.c_str(), current);
}
}
}
float mainCB_total_kW = 0.0f;
float mainCB_maxCurrent = 0.0f;
float mainCB_neutralCurrent = 0.0f;
float mainCB_maxTotalKw = 0.0f;
float mainCB_maxTotalCurrent = 0.0f;
for (int i = 1; i <= 16; i++) {
std::string kw_name = "CB" + std::to_string(i) + "_kW";
std::string max_current_name = "CB" + std::to_string(i) + "_maxCurrent";
mainCB_total_kW += getPointValue(equipment, kw_name.c_str());
mainCB_maxCurrent += getPointValue(equipment, max_current_name.c_str());
float max_kw = getPointValue(equipment, ("CB" + std::to_string(i) + "_max_kW").c_str());
if (max_kw > mainCB_maxTotalKw) {
mainCB_maxTotalKw = max_kw;
}
float max_current = getPointValue(equipment, max_current_name.c_str());
if (max_current > mainCB_maxTotalCurrent) {
mainCB_maxTotalCurrent = max_current;
}
}
float mainCB_PF = getPointValue(equipment, "MainCB_PF");
setPointValue(equipment, "MainCB_Total_kW", mainCB_total_kW);
setPointValue(equipment, "MainCB_maxCurrent", mainCB_maxCurrent);
setPointValue(equipment, "MainCB_neutralCurrent", mainCB_neutralCurrent);
setPointValue(equipment, "MainCB_maxTotalkW", mainCB_maxTotalKw);
setPointValue(equipment, "MainCB_maxTotalCurrent", mainCB_maxTotalCurrent);
setPointValue(equipment, "MainCB_L1_kW", mainCB_total_kW);
setPointValue(equipment, "MainCB_L1_kVA", mainCB_total_kW*1.3f);
setPointValue(equipment, "MainCB_L1_Current", mainCB_maxCurrent);
setPointValue(equipment, "MainCB_L1_PF", mainCB_PF);
setPointValue(equipment, "MainCB_L1_max_kW", mainCB_maxTotalKw);
setPointValue(equipment, "MainCB_L1_max_current", mainCB_maxTotalCurrent);
setPointValue(equipment, "MainCB_L2_kW", mainCB_total_kW);
setPointValue(equipment, "MainCB_L2_kVA", mainCB_total_kW*1.3f);
setPointValue(equipment, "MainCB_L2_Current", mainCB_maxCurrent);
setPointValue(equipment, "MainCB_L2_PF", mainCB_PF);
setPointValue(equipment, "MainCB_L2_max_kW", mainCB_maxTotalKw);
setPointValue(equipment, "MainCB_L2_max_current", mainCB_maxTotalCurrent);
setPointValue(equipment, "MainCB_L3_kW", mainCB_total_kW);
setPointValue(equipment, "MainCB_L3_kVA", mainCB_total_kW*1.3f);
setPointValue(equipment, "MainCB_L3_Current", mainCB_maxCurrent);
setPointValue(equipment, "MainCB_L3_PF", mainCB_PF);
setPointValue(equipment, "MainCB_L3_max_kW", mainCB_maxTotalKw);
setPointValue(equipment, "MainCB_L3_max_current", mainCB_maxTotalCurrent);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* This method performs cleanup by setting all alarm points and all EC fan
* run status points to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Status", 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,219 @@
/**
* @file config.h
* @brief Main configuration file for the CRAH Unit (TCP) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "Oracle_SA"; /**< @brief The SSID of the WiFi network. */
const char *password = "Prime!123"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(172, 17, 38, 51); /**< @brief The static IP address for the device. */
IPAddress gateway(172, 17, 38, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 2046, 0, "CB1"},
{HR, 2047, 0, "CB2"},
{HR, 2048, 0, "CB3"},
{HR, 2049, 0, "CB4"},
{HR, 2050, 0, "CB5"},
{HR, 2051, 0, "CB6"},
{HR, 2052, 0, "CB7"},
{HR, 2053, 0, "CB8"},
{HR, 2054, 0, "CB9"},
{HR, 2055, 0, "CB10"},
{HR, 2056, 0, "CB11"},
{HR, 2057, 0, "CB12"},
{HR, 2058, 0, "CB13"},
{HR, 2059, 0, "CB14"},
{HR, 2060, 0, "CB15"},
{HR, 2061, 0, "CB16"},
{HR_FLOAT, 9000, 0, "In_freq"},
{HR_FLOAT, 9002, 0, "InV_L1N"},
{HR_FLOAT, 9004, 0, "InV_L2N"},
{HR_FLOAT, 9006, 0, "InV_L3N"},
{HR_FLOAT, 9008, 0, "InV_LN_avg"},
{HR_FLOAT, 9010, 0, "InV_L12"},
{HR_FLOAT, 9012, 0, "InV_L23"},
{HR_FLOAT, 9014, 0, "InV_L31"},
{HR_FLOAT, 9016, 0, "InV_LL_avg"},
{HR_FLOAT, 9018, 0, "InTHD_L1N"},
{HR_FLOAT, 9020, 0, "InTHD_L2N"},
{HR_FLOAT, 9022, 0, "InTHD_L3N"},
{HR_FLOAT, 9036, 0, "InTHD_L1_1st"},
{HR_FLOAT, 9040, 0, "InTHD_L1_3rd"},
{HR_FLOAT, 9044, 0, "InTHD_L1_5th"},
{HR_FLOAT, 9048, 0, "InTHD_L1_7th"},
{HR_FLOAT, 9052, 0, "InTHD_L1_9th"},
{HR_FLOAT, 9162, 0, "InTHD_L2_1st"},
{HR_FLOAT, 9166, 0, "InTHD_L2_3rd"},
{HR_FLOAT, 9170, 0, "InTHD_L2_5th"},
{HR_FLOAT, 9174, 0, "InTHD_L2_7th"},
{HR_FLOAT, 9178, 0, "InTHD_L2_9th"},
{HR_FLOAT, 9288, 0, "InTHD_L3_1st"},
{HR_FLOAT, 9292, 0, "InTHD_L3_3rd"},
{HR_FLOAT, 9296, 0, "InTHD_L3_5th"},
{HR_FLOAT, 9300, 0, "InTHD_L3_7th"},
{HR_FLOAT, 9304, 0, "InTHD_L3_9th"},
{HR_FLOAT, 9018, 0, "InTHD_L12"},
{HR_FLOAT, 9020, 0, "InTHD_L23"},
{HR_FLOAT, 9022, 0, "InTHD_L31"},
{HR_FLOAT, 13456, 0, "CB1_kW"},
{HR_FLOAT, 13460, 0, "CB2_kW"},
{HR_FLOAT, 13464, 0, "CB3_kW"},
{HR_FLOAT, 13468, 0, "CB4_kW"},
{HR_FLOAT, 13472, 0, "CB5_kW"},
{HR_FLOAT, 13476, 0, "CB6_kW"},
{HR_FLOAT, 13480, 0, "CB7_kW"},
{HR_FLOAT, 13484, 0, "CB8_kW"},
{HR_FLOAT, 13488, 0, "CB9_kW"},
{HR_FLOAT, 13492, 0, "CB10_kW"},
{HR_FLOAT, 13496, 0, "CB11_kW"},
{HR_FLOAT, 13500, 0, "CB12_kW"},
{HR_FLOAT, 13504, 0, "CB13_kW"},
{HR_FLOAT, 13508, 0, "CB14_kW"},
{HR_FLOAT, 13512, 0, "CB15_kW"},
{HR_FLOAT, 13516, 0, "CB16_kW"},
{HR_FLOAT, 14608, 0, "CB1_Current"},
{HR_FLOAT, 14612, 0, "CB2_Current"},
{HR_FLOAT, 14616, 0, "CB3_Current"},
{HR_FLOAT, 14620, 0, "CB4_Current"},
{HR_FLOAT, 14624, 0, "CB5_Current"},
{HR_FLOAT, 14628, 0, "CB6_Current"},
{HR_FLOAT, 14632, 0, "CB7_Current"},
{HR_FLOAT, 14636, 0, "CB8_Current"},
{HR_FLOAT, 14640, 0, "CB9_Current"},
{HR_FLOAT, 14644, 0, "CB10_Current"},
{HR_FLOAT, 14648, 0, "CB11_Current"},
{HR_FLOAT, 14652, 0, "CB12_Current"},
{HR_FLOAT, 14656, 0, "CB13_Current"},
{HR_FLOAT, 14660, 0, "CB14_Current"},
{HR_FLOAT, 14664, 0, "CB15_Current"},
{HR_FLOAT, 14668, 0, "CB16_Current"},
{HR_FLOAT, 16912, 0, "CB1_max_kW"},
{HR_FLOAT, 16916, 0, "CB2_max_kW"},
{HR_FLOAT, 16920, 0, "CB3_max_kW"},
{HR_FLOAT, 16924, 0, "CB4_max_kW"},
{HR_FLOAT, 16928, 0, "CB5_max_kW"},
{HR_FLOAT, 16932, 0, "CB6_max_kW"},
{HR_FLOAT, 16936, 0, "CB7_max_kW"},
{HR_FLOAT, 16940, 0, "CB8_max_kW"},
{HR_FLOAT, 16944, 0, "CB9_max_kW"},
{HR_FLOAT, 16948, 0, "CB10_max_kW"},
{HR_FLOAT, 16952, 0, "CB11_max_kW"},
{HR_FLOAT, 16956, 0, "CB12_max_kW"},
{HR_FLOAT, 16960, 0, "CB13_max_kW"},
{HR_FLOAT, 16964, 0, "CB14_max_kW"},
{HR_FLOAT, 16968, 0, "CB15_max_kW"},
{HR_FLOAT, 16972, 0, "CB16_max_kW"},
{HR_FLOAT, 17296, 0, "CB1_maxCurrent"},
{HR_FLOAT, 17300, 0, "CB2_maxCurrent"},
{HR_FLOAT, 17304, 0, "CB3_maxCurrent"},
{HR_FLOAT, 17308, 0, "CB4_maxCurrent"},
{HR_FLOAT, 17312, 0, "CB5_maxCurrent"},
{HR_FLOAT, 17316, 0, "CB6_maxCurrent"},
{HR_FLOAT, 17320, 0, "CB7_maxCurrent"},
{HR_FLOAT, 17324, 0, "CB8_maxCurrent"},
{HR_FLOAT, 17328, 0, "CB9_maxCurrent"},
{HR_FLOAT, 17332, 0, "CB10_maxCurrent"},
{HR_FLOAT, 17336, 0, "CB11_maxCurrent"},
{HR_FLOAT, 17340, 0, "CB12_maxCurrent"},
{HR_FLOAT, 17344, 0, "CB13_maxCurrent"},
{HR_FLOAT, 17348, 0, "CB14_maxCurrent"},
{HR_FLOAT, 17352, 0, "CB15_maxCurrent"},
{HR_FLOAT, 17356, 0, "CB16_maxCurrent"},
{HR_FLOAT, 40058, 0, "MainCB_Total_kW"},
{HR_FLOAT, 40064, 0, "MainCB_maxCurrent"},
{HR_FLOAT, 40068, 0, "MainCB_neutralCurrent"},
{HR_FLOAT, 40070, 0, "MainCB_PF"},
{HR_FLOAT, 40074, 0, "MainCB_maxTotalkW"},
{HR_FLOAT, 40076, 0, "MainCB_maxTotalCurrent"},
{HR_FLOAT, 40108, 0, "MainCB_L1_kW"},
{HR_FLOAT, 40112, 0, "MainCB_L1_kVA"},
{HR_FLOAT, 40114, 0, "MainCB_L1_Current"},
{HR_FLOAT, 40116, 0, "MainCB_L1_PF"},
{HR_FLOAT, 40126, 0, "MainCB_L1_max_kW"},
{HR_FLOAT, 40128, 0, "MainCB_L1_max_current"},
{HR_FLOAT, 40158, 0, "MainCB_L2_kW"},
{HR_FLOAT, 40162, 0, "MainCB_L2_kVA"},
{HR_FLOAT, 40164, 0, "MainCB_L2_Current"},
{HR_FLOAT, 40166, 0, "MainCB_L2_PF"},
{HR_FLOAT, 40176, 0, "MainCB_L2_max_kW"},
{HR_FLOAT, 40178, 0, "MainCB_L2_max_current"},
{HR_FLOAT, 40208, 0, "MainCB_L3_kW"},
{HR_FLOAT, 40212, 0, "MainCB_L3_kVA"},
{HR_FLOAT, 40214, 0, "MainCB_L3_Current"},
{HR_FLOAT, 40216, 0, "MainCB_L3_PF"},
{HR_FLOAT, 40226, 0, "MainCB_L3_max_kW"},
{HR_FLOAT, 40228, 0, "MainCB_L3_max_current"},
};
//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);
}
}

Some files were not shown because too many files have changed in this diff Show More