Merge pull request #30 from emmanuelsrlok/develop

Update Main
This commit is contained in:
Emmanuel HC
2025-10-20 11:08:03 -05:00
committed by GitHub
28 changed files with 2699 additions and 744 deletions

View File

@@ -0,0 +1,59 @@
/**
* @file State_Battery.h
* @brief Defines the BatteryState class for the device.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the definition for the BatteryState, which represents
* the state where the equipment is actively performing its primary function.
*/
#ifndef Battery_State_h
#define Battery_State_h
#include "State.h" // Include the base class header
template<typename T> class Equipment;
/**
* @class BatteryState
* @brief Represents the active Battery state of the equipment.
*
* In this state, the equipment is fully operational and performing its main
* tasks. It applies a set of predefined strategies to its Modbus points to
* simulate active behavior (e.g., fans Battery at various speeds) and waits
* for a command to transition to another state.
*/
template<typename T>
class BatteryState : public State<T> {
public:
/**
* @brief Constructs a new BatteryState object.
* Initializes the strategies for various Modbus points that are active
* during the Battery state, such as setting fan speed behaviors.
*/
BatteryState();
/**
* @brief Executes the Battery state's logic for one update cycle.
* This method applies all active strategies (e.g., for fan speeds, temperatures)
* and checks for conditions that would trigger a state transition, such as a
* command to stop or a fault condition.
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
State<T>* update(Equipment<T>* equipment) override;
/**
* @brief Logic to execute once when entering the Battery state.
* Typically sets status bits to indicate the equipment is active (e.g.,
* setting an "On/Off" point to 1).
* @param equipment Pointer to the Equipment instance.
*/
void enterState(Equipment<T>* equipment) override;
/**
* @brief Logic to execute once when exiting the Battery state.
* Typically resets status bits to indicate the equipment is no longer
* active before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
void exitState(Equipment<T>* equipment) override;
};
#endif

View File

@@ -0,0 +1,59 @@
/**
* @file State_Bypass.h
* @brief Defines the BypassState class for the device.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the definition for the BypassState, which represents
* the state where the equipment is actively performing its primary function.
*/
#ifndef Bypass_State_h
#define Bypass_State_h
#include "State.h" // Include the base class header
template<typename T> class Equipment;
/**
* @class BypassState
* @brief Represents the active Bypass state of the equipment.
*
* In this state, the equipment is fully operational and performing its main
* tasks. It applies a set of predefined strategies to its Modbus points to
* simulate active behavior (e.g., fans Bypass at various speeds) and waits
* for a command to transition to another state.
*/
template<typename T>
class BypassState : public State<T> {
public:
/**
* @brief Constructs a new BypassState object.
* Initializes the strategies for various Modbus points that are active
* during the Bypass state, such as setting fan speed behaviors.
*/
BypassState();
/**
* @brief Executes the Bypass state's logic for one update cycle.
* This method applies all active strategies (e.g., for fan speeds, temperatures)
* and checks for conditions that would trigger a state transition, such as a
* command to stop or a fault condition.
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
State<T>* update(Equipment<T>* equipment) override;
/**
* @brief Logic to execute once when entering the Bypass state.
* Typically sets status bits to indicate the equipment is active (e.g.,
* setting an "On/Off" point to 1).
* @param equipment Pointer to the Equipment instance.
*/
void enterState(Equipment<T>* equipment) override;
/**
* @brief Logic to execute once when exiting the Bypass state.
* Typically resets status bits to indicate the equipment is no longer
* active before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
void exitState(Equipment<T>* equipment) override;
};
#endif

View File

@@ -9,7 +9,9 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = ATS_Eaton_ATC900_RPD_TCP ; Select here the name of the configuration you want to download
default_envs = PDU_Maverick_Power_TCP ; Select here the name of the configuration you want to download
[env]
upload_port = COM15
@@ -123,12 +125,6 @@ board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_src_filter = -<*> +<BMS/VFD/VFD_ABB_ACH580_RTU>
[env:CH_York_XXXXX_RTU]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_src_filter = -<*> +<BMS/CHILLER/CH_York_XXXXX_RTU>
[env:ATS_800_RPD]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
@@ -179,8 +175,6 @@ extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<EPMS/PQM/PQM_PM9000_TCP>
[env:ATS_Eaton_ATC900_RPD_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
@@ -193,4 +187,11 @@ platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<EPMS/ATS/ATS_Woodward_DTSC200A_TCP>
build_src_filter = -<*> +<EPMS/ATS/ATS_Woodward_DTSC200A_TCP>
[env:UPS_Vertiv_APM2_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<EPMS/UPS/UPS_Vertiv_APM2_TCP>

View File

@@ -54,7 +54,7 @@ template<>
State<ModbusRTU>* FailState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Fail update function");
Modbus_Point<ModbusRTU>* clearAlm = equipment->getModbus_Point("Clear Alm");
Modbus_Point<ModbusRTU>* clearAlm = equipment->getModbus_Point("Clear Alarms");
int nextStateId = clearAlm ? clearAlm->getValue() : 0;
if (nextStateId == 1){
return new StandbyState<ModbusRTU>();

View File

@@ -38,10 +38,11 @@
*/
template<>
RunningState<ModbusRTU>::RunningState() {
addStrategy("Actual Capacity", new PIDStrategy("Active SP", 1000, "Supply Temp"));
addStrategy("Comp1 Percent RLA", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Comp2 Percent RLA", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("Return Temp", new SingleValueStrategy(85,3.0f, 1000));
addStrategy("Actual Capacity", new PIDStrategy("Chiller Local Setpoint", 1000, "PICs Supply Temp"));
addStrategy("C1 Comp 1 Motor Percent (RLA)", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("C2 Comp 1 Motor Percent (RLA)", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("C3 Comp 1 Motor Percent (RLA)", new RampStrategy(0.0f, 5.0f, 1000));
addStrategy("PICs Return Temp", new SingleValueStrategy(85,3.0f, 1000));
}
/**
@@ -75,25 +76,27 @@ State<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipmen
// Determine the correct setpoint based on the current operating mode.
switch(currentMode){
case 1:
currentSP = getPointValue(equipment, "Ice SP");
currentSP = getPointValue(equipment, "Ice Setpoint");
currentSP = currentSP - 20;
break; // Added break to prevent fall-through
case 2:
currentSP = getPointValue(equipment, "Cool SP");
currentSP = getPointValue(equipment, "Cooling Active Setpoint");
currentSP = currentSP + 20;
break; // Added break
default:
// The default value is already set.
break;
}
Strategy_Behavior* ramp_strategy1 = getStrategy("Comp1 Percent RLA");
Strategy_Behavior* ramp_strategy2 = getStrategy("Comp2 Percent RLA");
Strategy_Behavior* ramp_strategy1 = getStrategy("C1 Comp 1 Motor Percent (RLA)");
Strategy_Behavior* ramp_strategy2 = getStrategy("C2 Comp 1 Motor Percent (RLA)");
Strategy_Behavior* ramp_strategy3 = getStrategy("C3 Comp 1 Motor Percent (RLA)");
int actualCapacity = getPointValue(equipment, "Actual Capacity");
if (actualCapacity < 50){
actualCapacity = actualCapacity * 2;
if (actualCapacity > 100) actualCapacity = 100;
static_cast<RampStrategy*>(ramp_strategy1)->setTarget(actualCapacity);
static_cast<RampStrategy*>(ramp_strategy2)->setTarget(0);
static_cast<RampStrategy*>(ramp_strategy3)->setTarget(0);
} else {
if (actualCapacity > 100) actualCapacity = 100;
static_cast<RampStrategy*>(ramp_strategy1)->setTarget(actualCapacity);
@@ -110,7 +113,7 @@ State<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipmen
static_cast<PIDStrategy*>(strategy)->setSetpoint(currentSP);
}
float OutdoorTemp = getPointValue(equipment, "Outdoor Air Temp");
float OutdoorTemp = getPointValue(equipment, "Ambient Temperature");
Serial.printf("Outdoor Temp: %f\n", OutdoorTemp);
if (OutdoorTemp >50.0f) {
setPointValue(equipment, "Chiller Mode SP", 1.0f);
@@ -120,10 +123,10 @@ State<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipmen
setPointValue(equipment, "Chiller Mode Output", 2.0f);
}
float SupplyTemp = getPointValue(equipment, "Supply Temp");
setPointValue(equipment, "Return Temp", SupplyTemp + 14.0f);
float SupplyTemp = getPointValue(equipment, "PICs Supply Temp");
setPointValue(equipment, "PICs Return Temp", SupplyTemp + 14.0f);
setPointValue(equipment, "Active SP", currentSP);
setPointValue(equipment, "Chiller Local Setpoint", currentSP);
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -139,7 +142,7 @@ void RunningState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
setPointValue(equipment, "Run Enabled", 1);
setPointValue(equipment, "Run Enable", 1);
setPointValue(equipment, "Flow Switch", 1);
}

View File

@@ -32,9 +32,10 @@
*/
template<>
StandbyState<ModbusRTU>::StandbyState() {
addStrategy("Comp1 Percent RLA", new RampStrategy(0,5,1000));
addStrategy("Comp2 Percent RLA", new RampStrategy(0,5,1000));
addStrategy("Return Temp", new SingleValueStrategy(85,3.0f, 1000));
addStrategy("C1 Comp 1 Motor Percent (RLA)", new RampStrategy(0,5,1000));
addStrategy("C2 Comp 1 Motor Percent (RLA)", new RampStrategy(0,5,1000));
addStrategy("C3 Comp 1 Motor Percent (RLA)", new RampStrategy(0,5,1000));
addStrategy("PICs Return Temp", new SingleValueStrategy(85,3.0f, 1000));
}
/**
@@ -55,7 +56,7 @@ State<ModbusRTU>* StandbyState<ModbusRTU>::update(Equipment<ModbusRTU>* equipmen
if (CH_Enable_SP == 1){
return new RunningState<ModbusRTU>();
}
float OutdoorTemp = getPointValue(equipment, "Outdoor Air Temp");
float OutdoorTemp = getPointValue(equipment, "Ambient Temperature");
Serial.printf("Outdoor Temp: %f\n", OutdoorTemp);
if (OutdoorTemp >50.0f) {
setPointValue(equipment, "Chiller Mode SP", 1);
@@ -76,7 +77,7 @@ template<>
void StandbyState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Run Enabled", 0);
setPointValue(equipment, "Run Enable", 0);
setPointValue(equipment, "Flow Switch", 0);
}

View File

@@ -22,10 +22,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, 78); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 17, 33, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
@@ -54,67 +54,51 @@
*/
modbusMap mb_map[] =
{
{HR, 100, 0, "State Control"}, //Internal to control from Modscan
{HR, 101, 0, "Fault Code"},
{HR_FLOAT, 102, 0, "Supply Temp"},
{HR_FLOAT, 103, 0, "Return Temp"}, //+14
{HR_FLOAT, 104, 0, "Flow Switch"},
{HR, 0, 0, "Chiller Local-Network"},
{HR, 1, 0, "Chiller Enable Output"},
{HR, 2, 0, "Run Enabled"},
{HR, 3, 0, "Chiller Capacity Limited"},
{HR, 4, 0, "Alm Digital Output"},
{HR, 6, 0, "Evap Flow Switch Sts"},
{HR, 7, 0, "Cond Flow Switch Sts"},
{HR, 8, 0, "Chiller On-Off"},
{HR, 9, 0, "Chiller Enable SP"},
{HR, 10, 0, "Clear Alm"},
{HR, 11, 0, "Chiller Mode Output"},
{HR_10x, 12, 0, "Active SP"},
{HR_10x, 13, 0, "Actual Capacity"},
{HR_10x, 14, 0, "Active Capacity Limit"},
{HR, 15, 0, "Chiller Sts"},
{HR_10x, 16, 0, "Evap Entering Fluid Temp"},
{HR_10x, 17, 0, "Evap Leaving Fluid Temp"},
{HR, 18, 0, "Evap Fluid Flow Rate"},
{HR_10x, 19, 0, "Cond Entering Fluid Temp"},
{HR_10x, 20, 0, "Cond Leaving Fluid Temp"},
{HR, 21, 0, "Cond Fluid Flow Rate"},
{HR_10x, 24, 0, "Outdoor Air Temp"},
{HR, 25, 0, "Chiller Current"},
{HR, 27, 0, "Total Kw"},
{HR, 28, 0, "Warning Alm Idx"},
{HR, 29, 0, "Problem Alm Idx"},
{HR, 30, 0, "Fault Alm Idx"},
{HR, 31, 0, "Warning Alm Code"},
{HR, 32, 0, "Problem Alm Code"},
{HR, 33, 0, "Fault Alm Code"},
{HR, 34, 0, "Chiller Mode SP"},
{HR_10x, 35, 0, "Cool SP"},
{HR_10x, 36, 0, "Ice SP"},
{HR_10x, 38, 0, "Capacity Limit SP"},
{HR_10x, 39, 0, "Cond Refrig Pressure"},
{HR_10x, 40, 0, "Cond Saturated Refrig Temp"},
{HR_10x, 41, 0, "Evap Refrig Pressure"},
{HR_10x, 42, 0, "Evap Saturated Refrig Temp"},
{HR, 65, 0, "Comp Suction Refrig Temp"},
{HR_10x, 68, 0, "Comp Discharge Refrig Temp"},
{HR, 69, 0, "Comp1 Percent RLA"},
{HR, 70, 0, "Comp1 Current"},
{HR, 71, 0, "Comp Voltage"},
{HR, 72, 0, "Comp Power"},
{HR, 73, 0, "Comp Starts"},
{HR, 74, 0, "Comp Run Hours"},
{HR, 75, 0, "Comp Run Hours"},
{HR, 82, 0, "Comp2 Percent RLA"},
{HR, 303, 0, "Evap Pump Run Hours"},
{HR, 304, 0, "Evap Pump Run Hours"},
{HR, 305, 0, "Evap Pump Sts"},
{HR, 316, 0, "Units"},
{HR, 317, 0, "Chiller Model"},
{HR, 1849, 0, "Oil Feed Pessure"},
{HR, 1854, 0, "Wtrside Econo State"},
{HR, 1855, 0, "Wtrside Econo En SP"},
{HR_FLOAT, 8100, 0, "PICs Supply Temp"}, //Internal to control from Modscan
{COIL, 8102, 0, "PICs Chiller Enable"},
{HR_FLOAT, 8104, 0, "PICs Chiller Flow"},
{HR_FLOAT, 8106, 0, "PICs Return Temp"},
{HR, 3, 0, "Run Enable"},
{HR, 9, 0, "Chiller Enable SP"},
{HR, 10, 0, "Clear Alarms"},
{HR, 11, 0, "Chiller Mode Output"},
{HR_10x, 12, 0, "Chiller Local Setpoint"},
{HR_10x, 13, 0, "Actual Capacity"},
{HR_10x, 14, 0, "Active Capacity Limit"},
{HR_10x, 16, 0, "System Chill Water In Temp"},
{HR_10x, 17, 0, "System Chill Water Out Temp"},
{HR_10x, 24, 0, "Ambient Temperature"},
{HR, 27, 0, "Chiller Total Power"},
{HR, 34, 0, "Chiller Mode SP"},
{HR_10x, 35, 0, "Cooling Active Setpoint"},
{HR_10x, 36, 0, "Ice Setpoint"},
{HR_10x, 39, 0, "C1 Cond Refrig Pres"},
{HR_10x, 41, 0, "C1 Evap Refrig Pres"},
{HR_10x, 43, 0, "C2 Cond Refrig Pres"},
{HR_10x, 45, 0, "C2 Evap Refrig Pres"},
{HR_10x, 47, 0, "C3 Cond Refrig Pres"},
{HR_10x, 49, 0, "C3 Evap Refrig Pres"},
{HR_10x, 51, 0, "C4 Cond Refrig Pres"},
{HR_10x, 53, 0, "C4 Evap Refrig Pres"},
{HR_10x, 63, 0, "C1 Comp Suction Refrig Pres"},
{HR_10x, 66, 0, "C1 Comp 1 Discharge Refrig Pres"},
{HR, 69, 0, "C1 Comp 1 Motor Percent (RLA)"},
{HR, 70, 0, "C1 Comp Current"},
{HR, 72, 0, "C1 Comp 1 Power"},
{HR_10x, 76, 0, "C1 Comp 2 Suction Refrig Pres"},
{HR_10x, 79, 0, "C1 Comp 2 Discharge Refrig Pres"},
{HR, 108, 0, "C2 Comp 1 Current"},
{HR, 109, 0, "C2 Comp 1 Current"},
{HR, 111, 0, "C2 Comp 1 Power"},
{HR, 147, 0, "C3 Comp 1 Motor Percent (RLA)"},
{HR, 148, 0, "C3 Comp 1 Current"},
{HR, 150, 0, "C3 Comp 1 Power"},
{HR, 592, 0, "Alarm Freeze Protection Evap 1"},
{HR, 593, 0, "Alarm Freeze Protection Evap 2"},
{HR_10x, 1731, 0, "C4 Comp 1 Oil Pres"},
{HR_10x, 1770, 0, "C3 Comp 1 Oil Pres"},
{HR_10x, 1809, 0, "C2 Comp 1 Oil Pres"},
{HR_10x, 1849, 0, "C1 Comp 1 Oil Pres"},
};
//Size of modbus map used in FOR cycles, automatically calculated.

View File

@@ -0,0 +1,48 @@
# Datahall CRAH UMAS 10FSV041206-058-117.00x114.00 TCP
## Brief Introduction
This is first of a kind CRAH, so Modbus maps are different.
BMS Control Source (0:speed, 1:external room temp) and
BMS Enable Source (0:keypad, 1:DI, 2:BMS) sent from PLC/Modscan
PLC code for these CRAHs use Speed Control, i.e. Speed Setpoint sent from PLC/BMS.
Return Air is generally not used for control during normal operation.
Leak Detect alarm is the only alarm that will send unit to FailState.
This is a (9) fan array CRAH unit.
## List of Equipmentt
This cofiguration has been used for these models:
* **Model: 10FSV041206-058-117.00x114.00**: 10-07-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
* **CW valve**: **Ramp Strategy** ramp to 0
* **Supply Air temperature**: **Ramp Strategy** ramp to 74
* **Return Air temperature**: **Ramp Strategy** ramp to 86
* **Fan speeds**: **Ramp Strategy** ramp to 0
* **Fan amps**: **Ramp Strategy** ramp to 0
* **Fan run status**: set to 0
### Running State
* **Fan run status**: set to 1
* **Fan min speed**: initialized to 30
* **Fan max speed**: initialized to 100
* **Fan speed**: **Ramp Strategy** dynamically ramps to speed setpoint
* **Fan amps**: **Ramp Strategy** ramp to 15
* **Fan operating hours**: initialize totalizers
* **Supply air temperature**: **Saw Strategy** ramps back and forth between 60 and 100 deg
* **CW valve**: **PID Strategy** adjusts until Supply Air Temperature matches Supply Air Temperature setpoint
### Fail State
* **Fan run status**: set to 0
* **CW valve**: **Ramp Strategy** ramp to 0
* **Fan speeds**: **Ramp Strategy** ramp to 0
* **Fan amps**: **Ramp Strategy** ramp to 0

View File

@@ -0,0 +1,122 @@
/**
* @file StateUtils.cpp
* @brief Implementation of the StateUtils class.
* @author Robert J. Davis
* @date 2025-10-03
*
* 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 is used to update the Control Mode (feedback),
* based on the selected BMS Control Source and BMS Enable Source.
*
* This is a function used in the update() of the Standby, Running, and Fail States.
*
*/
void updateControlMode(Equipment<ModbusIP>* equipment){
Modbus_Point<ModbusIP>* BMS_Control_Source_Pt = equipment -> getModbus_Point ("BMS Control Source");
Modbus_Point<ModbusIP>* BMS_Enable_Source_Pt = equipment -> getModbus_Point ("BMS Enable Source");
Modbus_Point<ModbusIP>* Control_Mode_Pt = equipment -> getModbus_Point ("Control Mode Selected");
int BMS_Control_Source = BMS_Control_Source_Pt ? BMS_Control_Source_Pt->getValue() : 0;
int BMS_Enable_Source = BMS_Enable_Source_Pt ? BMS_Enable_Source_Pt->getValue() : 0;
int Control_Mode_Selected = Control_Mode_Pt ? Control_Mode_Pt->getValue() : -1;
if (BMS_Control_Source == 0 && BMS_Enable_Source == 2) {
Control_Mode_Selected = 0;
} else if (BMS_Control_Source == 1 && BMS_Enable_Source == 2) {
Control_Mode_Selected = 1;
} else if (BMS_Enable_Source == 0) {
Control_Mode_Selected = 2;
}
equipment->setModbus_Point("Control Mode Selected", Control_Mode_Selected);
}
/**
* @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 1", "Alarm Fan 2", "Alarm Fan 3", "Alarm Fan 4",
"Alarm Fan 5", "Alarm Fan 6", "Alarm Fan 7", "Alarm Fan 8",
"Alarm Fan 9", "Alarm Dirty Filter", "Alarm Leak Detect",
"Alarm Condensate Pump", "Alarm Fire", "Alarm Smoke"
};
const std::vector<std::string> alarmCommands = {
"Alarm Fan 1 ON", "Alarm Fan 2 ON", "Alarm Fan 3 ON", "Alarm Fan 4 ON",
"Alarm Fan 5 ON", "Alarm Fan 6 ON", "Alarm Fan 7 ON", "Alarm Fan 8 ON",
"Alarm Fan 9 ON", "Alarm Dirty Filter ON", "Alarm Leak Detect ON",
"Alarm Condensate Pump ON", "Alarm Fire ON", "Alarm Smoke ON"
};
int numAlarms = 0;
for (int i =0; i< alarmCommands.size() && i < alarmDescriptions.size(); ++i) {
Modbus_Point<ModbusIP>* commandPoint = equipment->getModbus_Point(alarmCommands[i]);
Modbus_Point<ModbusIP>* alarmPoint = equipment->getModbus_Point(alarmDescriptions[i]);
if (commandPoint) {
alarmPoint->setValue(commandPoint->getValue());
if (alarmPoint->getValue() == 1) numAlarms++;
}
}
if (numAlarms >= 1) equipment->setModbus_Point("Common Alarm", 1);
else equipment->setModbus_Point("Common Alarm", 0);
}
/**
* @brief The purpose of this function is for testing the RA Temp and RA Humidity alarms (Ignition HMI display)
* User will manually set Alarms through Modscan coils, which will change RA Temp or RA Humidity accordingly.
* The RA Temp alarm limits (reference Ignition UDT) are 72, 100.
* The SA Temp alarm limits (reference Ignition UDT) are 72, 78.
* The RA Humidity alarm limits (reference Ignition UDT) are 20, 60.
* If an associated alarm coil is not active, the analog values are set to a safe, un-alarmed value.
*
* NOTE: These analog alarms will not activate the Common Alarm in the Arduino test.
* Since these alarms will be set in Ignition, will not be sent over Modbus from CRAH to Ignition.
*
* This is a function used in the update() of the Running State.
*
*/
// Note: The Common Alarm is not configured to annunciate with these analog low/high alarms.
void updateAnalogs(Equipment<ModbusIP>* equipment){
if (equipment->getModbus_Point("RA Temp Low Alarm ON")->getValue() ==1){
equipment->setModbus_Point("Return Air Temp", 68.0f);
}
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);
}
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

@@ -0,0 +1,50 @@
/**
* @file StateUtils.h
* @brief StateUtils class
* @author Robert J Davis
* @date 2025-10-06
*
* 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 the Control Mode based on BMS signals and writes it back to Modbus.
* @param equipment Pointer to the Equipment instance.
* @return The selected control mode (int).
*/
void updateControlMode(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 true if any common alarm is active, false otherwise.
*/
void updateAlarms(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 true if any common alarm is active, false otherwise.
*/
void updateAnalogs(Equipment<ModbusIP>* equipment);

View File

@@ -0,0 +1,130 @@
/**
* @file State_Fail.cpp
* @brief Implementation of the FailState class.
* @author Robert J Davis
* @date 2025-10-06
*
* This file contains the implementation for the FailState, which defines
* the behavior of the equipment when it has entered a fault condition.
* This can only be initiated when the Leak Detection Alarm is active according
* to the UMAS SOO. If leak detection alarm --> close the cooling valve, turn off fans.
* The Control Mode and Alarms can still be updated while in a Failed State.
* Also resets the BMS Command to OFF.
*/
#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 creates strategies to ramp the corresponding Modbus points down to 0.
*
*/
template<>
FailState<ModbusIP>::FailState(const std::vector<std::string>& activeAlarms) {
// The only failure mode is if the Leak Detect Alarm is activated, according to UMAS SOO.
// If leak detection alarm --> close cooling valve, turn off fans
// Fan speed --> 0, Run Status --> 0, Amps --> 0
addStrategy("CW Valve Position", new RampStrategy(0.0f, 5.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));
addStrategy("Speed Fan 4", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 5", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 6", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 7", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 8", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 9", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Amps Fan 1", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 2", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 3", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 4", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 5", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 6", new RampStrategy(0.0f, 4.5f, 1000));
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));
}
/**
* @brief Executes the fail state's logic for one update cycle.
*
* This method checks the status of the Leak Detect Alarm Modbus point, if alarm clears --> Standby State
* While the CRAH is in a failed state, the Control Mode and Alarms are still updated,
* but the BMS Command cannot be turned on.
*
* @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) {
Serial.println("Fail update function");
// Still want Control Mode and Alarms to be updated while in Fail State
// Ensure BMS Command is set to Off: want operator to re-start from BMS once Leak Detect Alarm is cleared.
updateControlMode(equipment);
updateAlarms(equipment);
updateAnalogs(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){
return new StandbyState<ModbusIP>();
}
_applyStrategies(equipment);
return nullptr;
}
/**
* @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
* @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...");
const std::vector<std::string> motorStatusDescriptions = {
"Run Status Fan 1", "Run Status Fan 2", "Run Status Fan 3",
"Run Status Fan 4", "Run Status Fan 5", "Run Status Fan 6",
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 0
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(0);
}
};
// Set BMS On/Off Command to 0
setPointValue(equipment, "ON/OFF Command By BMS", 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,198 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Robert J Davis
* @date 2025-10-03
*
* 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, such as a PID controller for the 'CW Valve Position', totalizers
* for the run-hours of each EC fan, fan speed, amps for each fan. Also sets
* the fan min and max speeds to a default value. The supply air temperature
* is set to a saw strategy between 60 and 100, which will also activate the
* SA high (78) and low (72) temperature alarms.
*/
template<>
RunningState<ModbusIP>::RunningState() {
addStrategy("Fan Min Speed", new SingleValueStrategy(30.0f, 0.0f, 1000));
addStrategy("Fan Max Speed", new SingleValueStrategy(100.0f, 0.0f, 1000));
addStrategy("Speed Fan 1", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 2", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 3", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 4", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 5", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 6", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 7", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 8", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Speed Fan 9", new RampStrategy(0.0f, 1.0f, 200));
addStrategy("Amps Fan 1", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 2", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 3", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 4", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 5", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 6", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 7", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 8", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Amps Fan 9", new RampStrategy(15.0f, 2.5f, 1000));
addStrategy("Operating Hours Fan 1", new TotalizerStrategy(1000)); // Does not retain these values when switching States.
addStrategy("Operating Hours Fan 2", new TotalizerStrategy(1000));
addStrategy("Operating Hours Fan 3", new TotalizerStrategy(1000));
addStrategy("Operating Hours Fan 4", new TotalizerStrategy(1000));
addStrategy("Operating Hours Fan 5", new TotalizerStrategy(1000));
addStrategy("Operating Hours Fan 6", new TotalizerStrategy(1000));
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("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.
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first updates the Control Mode (based on BMS Control Source and BMS Enable Source),
* and then updates the Alarms, setting the Common Alarm to 1 if any alarm is active. Alarms may be
* set using Coils 2-9 in Modscan (for Arduino testing only).
*
* If the Leak Detect alarm is active, the unit will transition to FailState.
* If the BMS Command is set to OFF, the unit will transition to StandbyState.
*
* If the unit is still in a RunningState, the fan speed will dynamically be updated
* to ramp to the speed setpoint sent from the PLC/Modscan.
*
* @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");
int On_Off_Command = getPointValue(equipment, "ON/OFF Command By BMS"); // Modscan COIL 1
int BMS_Control_Source = getPointValue(equipment, "BMS Control Source"); // Modscan HR 1
int BMS_Enable_Source = getPointValue(equipment, "BMS Enable Source"); // Modscan HR 2
updateControlMode(equipment);
updateAlarms(equipment);
updateAnalogs(equipment);
// Check to see if Leak Detect alarm is active (only alarm which will make unit FAIL and turn off) --> Send to FailState
bool leakDetect = getPointValue(equipment, "Alarm Leak Detect");
if (leakDetect){
return new FailState<ModbusIP>({"Alarm Leak Detect"});
}
// 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>();
}
// This will update the Fan Speed Setpoint dynamically while in run mode. Fan Speed Setpoint changed through Modscan.
// This functioanlity matches the UMAS SOO specifically for how it calculates Speed Setpoint.
float BMS_Speed_Setpoint = getPointValue(equipment, "Fan Speed Setpoint");
float BMS_Speed_Setpoint_Pct = BMS_Speed_Setpoint / 100.0f;
float Fan_Min_Speed = getPointValue(equipment, "Fan Min Speed");
float Fan_Max_Speed = getPointValue(equipment, "Fan Max Speed");
float Fan_Speed_Setpoint = BMS_Speed_Setpoint_Pct * (Fan_Max_Speed - Fan_Min_Speed) + Fan_Min_Speed;
for (int i = 0; i < 10; i++){
std::string pointName = "Speed Fan " + std::to_string(i);
Strategy_Behavior* strat = getStrategy(pointName);
if (strat) {
RampStrategy* ramp = static_cast<RampStrategy*>(strat);
if (ramp){
if (BMS_Speed_Setpoint > 100){
Fan_Speed_Setpoint = Fan_Max_Speed;
}
else if (BMS_Speed_Setpoint < 0){
Fan_Speed_Setpoint = Fan_Min_Speed;
}
ramp->setTarget(Fan_Speed_Setpoint);
}
}
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
* Sets the "Run Status" for all EC fans to 1 to indicate they are active.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
// You could also update a Modbus register to show the "standby" state
const std::vector<std::string> motorStatusDescriptions = {
"Run Status Fan 1", "Run Status Fan 2", "Run Status Fan 3",
"Run Status Fan 4", "Run Status Fan 5", "Run Status Fan 6",
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 1
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(1);
}
}
}
/**
* @brief Logic to execute once when exiting the running state.
* Sets the "Run Status" for all EC fans to 0 before transitioning to the next state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
const std::vector<std::string> motorStatusDescriptions = {
"Run Status Fan 1", "Run Status Fan 2", "Run Status Fan 3",
"Run Status Fan 4", "Run Status Fan 5", "Run Status Fan 6",
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 0
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(0);
}
}
}

View File

@@ -0,0 +1,142 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Robert J. Davis
* @date 2025-10-01
*
* 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 to a stable value for
* the SAT and RAT readings and creates ramp strategies to bring the CW valve and all
* EC fan speeds and amps down to zero.
*/
template<>
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("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));
addStrategy("Speed Fan 4", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 5", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 6", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 7", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 8", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Speed Fan 9", new RampStrategy(0.0f, 10.0f, 1000));
addStrategy("Amps Fan 1", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 2", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 3", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 4", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 5", new RampStrategy(0.0f, 4.5f, 1000));
addStrategy("Amps Fan 6", new RampStrategy(0.0f, 4.5f, 1000));
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));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* While in Standby, the Control Mode will be updated (based on BMS Control Source and BMS Enable Source),
* the Alarms will also be updated, with the Common Alarm being set to 1 if any alarm is active.
*
* If the Leak Detect alarm is active, the unit will transition to a FailState.
* If in the correct Control Mode, and BMS Command ON is sent from the PLC/Modscan,
* the unit will transition to a RunningState.
*
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Standby update function");
int On_Off_Command = getPointValue(equipment, "ON/OFF Command By BMS"); // Modscan COIL 1
int BMS_Control_Source = getPointValue(equipment, "BMS Control Source"); // Modscan HR 1
int BMS_Enable_Source = getPointValue(equipment, "BMS Enable Source"); // Modscan HR 2
updateControlMode(equipment);
updateAlarms(equipment);
updateAnalogs(equipment);
// Check to see if Leak Detect alarm is active (only alarm which will make unit FAIL and turn off) --> Send to FailState
bool leakDetect = getPointValue(equipment, "Alarm Leak Detect");
if (leakDetect){
return new FailState<ModbusIP>({"Alarm Leak Detect"});
}
if (On_Off_Command == 1 && BMS_Control_Source == 0 && BMS_Enable_Source == 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 EC fan run status points to 0.
* The BMS Command is also set to OFF.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
// A list of all motor run status descriptions
const std::vector<std::string> motorStatusDescriptions = {
"Run Status Fan 1", "Run Status Fan 2", "Run Status Fan 3",
"Run Status Fan 4", "Run Status Fan 5", "Run Status Fan 6",
"Run Status Fan 7", "Run Status Fan 8", "Run Status Fan 9"
};
// Loop through and set all motor statuses to 0
for (const auto& desc : motorStatusDescriptions) {
Modbus_Point<ModbusIP>* point = equipment->getModbus_Point(desc);
if (point) {
point->setValue(0);
}
};
setPointValue(equipment, "ON/OFF Command By BMS", 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,160 @@
/**
* @file config.h
* @brief Main configuration file for the UMAS CRAH Unit (TCP) emulator - used at PHX3 DC1
* @author Robert J Davis
* @date 2025-10-01
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "TP-Link_D91A"; /**< @brief The SSID of the WiFi network. */
const char *password = "52761492"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{COIL, 0, 0, "ON/OFF Command By BMS"}, // Receive signal from PLC
{COIL, 1, 0, "Alarm Fan 1 ON"}, // Just for Arduino testing. Sets Alarm Fan 1 to 1.
{COIL, 2, 0, "Alarm Fan 2 ON"}, // Just for Arduino testing. Sets Alarm Fan 2 to 1.
{COIL, 3, 0, "Alarm Dirty Filter ON"}, // Just for Arduino testing. Sets Alarm Dirty Filter to 1.
{COIL, 4, 0, "Alarm Leak Detect ON"}, // Just for Arduino testing. Sets Alarm Fan 1 to 1.
{COIL, 5, 0, "RA Temp Low Alarm ON"}, // Just for Arduino testing. Sets RA Temp to 68F (low alarm)
{COIL, 6, 0, "RA Temp High Alarm ON"}, // Just for Arduino testing. Sets RA Temp to 104F (high alarm)
{COIL, 7, 0, "RA Humidity Low Alarm ON"}, // Just for Arduino testing. Sets RA Humidity to 15% (low alarm)
{COIL, 8, 0, "RA Humidity High Alarm ON"}, // Just for Arduino testing. Sets RA Humidity to 65% (high alarm)
{COIL, 9, 0, "Alarm Fan 3 ON"}, // Just for Arduino testing. Sets Alarm Fan 3 to 1.
{COIL, 10, 0, "Alarm Fan 4 ON"}, // Just for Arduino testing. Sets Alarm Fan 4 to 1.
{COIL, 11, 0, "Alarm Fan 5 ON"}, // Just for Arduino testing. Sets Alarm Fan 5 to 1.
{COIL, 12, 0, "Alarm Fan 6 ON"}, // Just for Arduino testing. Sets Alarm Fan 6 to 1.
{COIL, 13, 0, "Alarm Fan 7 ON"}, // Just for Arduino testing. Sets Alarm Fan 7 to 1.
{COIL, 14, 0, "Alarm Fan 8 ON"}, // Just for Arduino testing. Sets Alarm Fan 8 to 1.
{COIL, 15, 0, "Alarm Fan 9 ON"}, // Just for Arduino testing. Sets Alarm Fan 9 to 1.
{COIL, 16, 0, "Alarm Condensate Pump ON"}, // Just for Arduino testing. Sets Alarm Condensate Pump to 1.
{COIL, 17, 0, "Alarm Fire ON"}, // Just for Arduino testing. Sets Alarm Fire to 1.
{COIL, 18, 0, "Alarm Smoke ON"}, // Just for Arduino testing. Sets Alarm Smoke to 1.
{DI, 4, 0, "Alarm Leak Detect"},
{DI, 5, 0, "Alarm Dirty Filter"},
{DI, 12, 0, "Common Alarm"}, // Send to PLC
{DI, 14, 0, "Alarm Condensate Pump"},
{DI, 15, 0, "Alarm Smoke"},
{DI, 16, 0, "Alarm Fire"},
{IR_FLOAT, 1, 0, "Supply Air Temp"},
{IR_FLOAT, 3, 0, "Return Air Humidity"}, // Ignition visual only
{IR_FLOAT, 5, 0, "Return Air Temp"}, // Send to PLC
{IR_FLOAT, 7, 0, "Filter Differential Pressure"}, // sawStrategy between 0 and 5
{IR_FLOAT, 9, 0, "CW Valve Position"},
{IR, 26, 0, "Alarm Fan 1"},
{IR, 30, 0, "Alarm Fan 2"},
{IR, 34, 0, "Alarm Fan 3"},
{IR, 38, 0, "Alarm Fan 4"},
{IR, 42, 0, "Alarm Fan 5"},
{IR, 46, 0, "Alarm Fan 6"},
{IR, 50, 0, "Alarm Fan 7"},
{IR, 54, 0, "Alarm Fan 8"},
{IR, 58, 0, "Alarm Fan 9"},
{IR, 27, 0, "Run Status Fan 1"}, // Send to PLC
{IR, 31, 0, "Run Status Fan 2"}, // Send to PLC
{IR, 35, 0, "Run Status Fan 3"}, // Send to PLC
{IR, 39, 0, "Run Status Fan 4"}, // Send to PLC
{IR, 43, 0, "Run Status Fan 5"}, // Send to PLC
{IR, 47, 0, "Run Status Fan 6"}, // Send to PLC
{IR, 51, 0, "Run Status Fan 7"}, // Send to PLC
{IR, 55, 0, "Run Status Fan 8"}, // Send to PLC
{IR, 59, 0, "Run Status Fan 9"}, // Send to PLC
{IR, 25, 0, "Speed Fan 1"},
{IR, 29, 0, "Speed Fan 2"},
{IR, 33, 0, "Speed Fan 3"},
{IR, 37, 0, "Speed Fan 4"},
{IR, 41, 0, "Speed Fan 5"},
{IR, 45, 0, "Speed Fan 6"},
{IR, 49, 0, "Speed Fan 7"},
{IR, 53, 0, "Speed Fan 8"},
{IR, 57, 0, "Speed Fan 9"},
{IR, 28, 0, "Operating Hours Fan 1"},
{IR, 32, 0, "Operating Hours Fan 2"},
{IR, 36, 0, "Operating Hours Fan 3"},
{IR, 40, 0, "Operating Hours Fan 4"},
{IR, 44, 0, "Operating Hours Fan 5"},
{IR, 48, 0, "Operating Hours Fan 6"},
{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_FLOAT, 63, 0, "Amps Fan 1"},
{IR_FLOAT, 65, 0, "Amps Fan 2"},
{IR_FLOAT, 67, 0, "Amps Fan 3"},
{IR_FLOAT, 69, 0, "Amps Fan 4"},
{IR_FLOAT, 71, 0, "Amps Fan 5"},
{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"},
{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
};
//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

@@ -36,8 +36,79 @@
* 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", };
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));
//
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 = "";
tag = cb + "_V3N";
addStrategy(tag, new SingleValueStrategy(0.0f, 2.0f, 1000));
tag = "";
tag = cb + "_L1PF";
addStrategy(tag, new SingleValueStrategy(93.0f, 2.0f, 1000));
tag = "";
tag = cb + "_L2PF";
addStrategy(tag, new SingleValueStrategy(93.0f, 2.0f, 1000));
tag = "";
tag = cb + "_L3PF";
addStrategy(tag, new SingleValueStrategy(93.0f, 2.0f, 1000));
tag = "";
tag = cb + "_V1THD";
addStrategy(tag, new SingleValueStrategy(2.0f, 2.0f, 1000));
tag = "";
tag = cb + "_V2THD";
addStrategy(tag, new SingleValueStrategy(2.0f, 2.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 = "";
tag = cb + "_I2THD";
addStrategy(tag, new SingleValueStrategy(10.0f, 2.0f, 1000));
tag = "";
tag = cb + "_I3THD";
addStrategy(tag, new SingleValueStrategy(10.0f, 2.0f, 1000));
tag = "";
tag = cb + "_I1Kfactor";
addStrategy(tag, new SingleValueStrategy(3.0f, 2.0f, 1000));
tag = "";
tag = cb + "_I2Kfactor";
addStrategy(tag, new SingleValueStrategy(3.0f, 2.0f, 1000));
tag = "";
tag = cb + "_I3Kfactor";
addStrategy(tag, new SingleValueStrategy(3.0f, 2.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));
}
}
/**
@@ -57,7 +128,153 @@ 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, "Px Ctrl");
switch (static_cast<int>(State_Ctrl)) {
case 1:
return new StandbyState<ModbusIP>();
break;
default:
break;
}
float cb_count = 0.0f;
for (const std::string& cb :cbs){
std::string tag = "";
tag = "Px " + cb;
if (cb == "CB0") continue;
float cb_status = getPointValue(equipment, tag);
if (static_cast<int>(cb_status)){
cb_count += 1.0f;
}
}
for (const std::string& cb : cbs) {
std::string tag = "";
Strategy_Behavior* strategy = nullptr;
tag = "";
tag = "Px_" + cb;
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);
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*1.715f);
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 = "";
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 = "";
tag = cb + "_L2KW";
setPointValue(equipment, tag, total_load*0.0f);
tag = "";
tag = cb + "_L3KW";
setPointValue(equipment, tag, total_load*0.0f);
tag = "";
tag = cb + "_L1KVar";
setPointValue(equipment, tag, total_load*0.0f);
tag = "";
tag = cb + "_L2KVar";
setPointValue(equipment, tag, total_load*0.0f);
tag = "";
tag = cb + "_L3KVar";
setPointValue(equipment, tag, total_load*0.0f);
}
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -86,4 +303,5 @@ void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}
}

View File

@@ -56,7 +56,14 @@ 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, "Px Ctrl");
switch (static_cast<int>(State_Ctrl)) {
case 2:
return new RunningState<ModbusIP>();
break;
default:
break;
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;

View File

@@ -21,11 +21,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. */
const char *ssid = "esrlok_portable"; /**< @brief The SSID of the WiFi network. */
const char *password = "m7g6eNMe?cy8S@z"; /**< @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(255, 255, 255, 0); /**< @brief The subnet mask. */
IPAddress subnet(254, 254, 254, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
@@ -63,429 +63,442 @@ modbusMap mb_map[] = {
//***************************************
// Write Registers (as Input Registers - 3X)
//***************************************
{HR, 9, 0, "Px Ctrl"},
{HR, 10, 0, "Px Rating"}, //Watts
{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"},
// System Status
{IR_FLOAT, 1, 0, "CB0_V1N" },
{IR_FLOAT, 3, 0, "CB0_V2N" },
{IR_FLOAT, 5, 0, "CB0_V3N" },
{IR_FLOAT, 7, 0, "CB0_I1" },
{IR_FLOAT, 9, 0, "CB0_I2" },
{IR_FLOAT, 11, 0, "CB0_I3" },
{IR_FLOAT, 13, 0, "CB0_L1KW" },
{IR_FLOAT, 15, 0, "CB0_L2KW" },
{IR_FLOAT, 17, 0, "CB0_L3KW" },
{IR_FLOAT, 19, 0, "CB0_L1KVar" },
{IR_FLOAT, 21, 0, "CB0_L2KVar" },
{IR_FLOAT, 23, 0, "CB0_L3KVar" },
{IR_FLOAT, 25, 0, "CB0_L1KVA" },
{IR_FLOAT, 27, 0, "CB0_L2KVA" },
{IR_FLOAT, 29, 0, "CB0_L3KVA" },
{IR_FLOAT, 31, 0, "CB0_L1PF" },
{IR_FLOAT, 33, 0, "CB0_L2PF" },
{IR_FLOAT, 35, 0, "CB0_L3PF" },
{IR_FLOAT, 37, 0, "CB0_V1THD" },
{IR_FLOAT, 39, 0, "CB0_V2THD" },
{IR_FLOAT, 41, 0, "CB0_V3THD" },
{IR_FLOAT, 43, 0, "CB0_I1THD" },
{IR_FLOAT, 45, 0, "CB0_I2THD" },
{IR_FLOAT, 47, 0, "CB0_I3THD" },
{IR_FLOAT, 49, 0, "CB0_I1Kfactor" },
{IR_FLOAT, 51, 0, "CB0_I2Kfactor" },
{IR_FLOAT, 53, 0, "CB0_I3Kfactor" },
{IR_FLOAT, 55, 0, "CB0_I1TDD" },
{IR_FLOAT, 57, 0, "CB0_I2TDD" },
{IR_FLOAT, 59, 0, "CB0_I3TDD" },
{IR_FLOAT, 61, 0, "CB0_V12" },
{IR_FLOAT, 63, 0, "CB0_V23" },
{IR_FLOAT, 65, 0, "CB0_V31" },
{IR_FLOAT, 67, 0, "CB0_TotalKW" },
{IR_FLOAT, 69, 0, "CB0_TotalKVar" },
{IR_FLOAT, 71, 0, "CB0_TotalKVA" },
{IR_FLOAT, 73, 0, "CB0_TotalPF" },
{IR_FLOAT, 75, 0, "CB0_TotalPFLag" },
{IR_FLOAT, 77, 0, "CB0_TotalPFLead" },
{IR_FLOAT, 79, 0, "CB0_TotalKWImport" },
{IR_FLOAT, 81, 0, "CB0_TotalKWExport" },
{IR_FLOAT, 83, 0, "CB0_TotalKVarImport" },
{IR_FLOAT, 85, 0, "CB0_TotalKVarExport" },
{IR_FLOAT, 87, 0, "CB0_LN_Avg" },
{IR_FLOAT, 89, 0, "CB0_LL_Avg" },
{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" },
// Circuit Breaker 1 (OB01)
{IR_FLOAT, 101, 0, "CB1_V1N" },
{IR_FLOAT, 103, 0, "CB1_V2N" },
{IR_FLOAT, 105, 0, "CB1_V3N" },
{IR_FLOAT, 107, 0, "CB1_I1" },
{IR_FLOAT, 109, 0, "CB1_I2" },
{IR_FLOAT, 111, 0, "CB1_I3" },
{IR_FLOAT, 113, 0, "CB1_L1KW" },
{IR_FLOAT, 115, 0, "CB1_L2KW" },
{IR_FLOAT, 117, 0, "CB1_L3KW" },
{IR_FLOAT, 119, 0, "CB1_L1KVar" },
{IR_FLOAT, 121, 0, "CB1_L2KVar" },
{IR_FLOAT, 123, 0, "CB1_L3KVar" },
{IR_FLOAT, 125, 0, "CB1_L1KVA" },
{IR_FLOAT, 127, 0, "CB1_L2KVA" },
{IR_FLOAT, 129, 0, "CB1_L3KVA" },
{IR_FLOAT, 131, 0, "CB1_L1PF" },
{IR_FLOAT, 133, 0, "CB1_L2PF" },
{IR_FLOAT, 135, 0, "CB1_L3PF" },
{IR_FLOAT, 137, 0, "CB1_V1THD" },
{IR_FLOAT, 139, 0, "CB1_V2THD" },
{IR_FLOAT, 141, 0, "CB1_V3THD" },
{IR_FLOAT, 143, 0, "CB1_I1THD" },
{IR_FLOAT, 145, 0, "CB1_I2THD" },
{IR_FLOAT, 147, 0, "CB1_I3THD" },
{IR_FLOAT, 149, 0, "CB1_I1Kfactor" },
{IR_FLOAT, 151, 0, "CB1_I2Kfactor" },
{IR_FLOAT, 153, 0, "CB1_I3Kfactor" },
{IR_FLOAT, 155, 0, "CB1_I1TDD" },
{IR_FLOAT, 157, 0, "CB1_I2TDD" },
{IR_FLOAT, 159, 0, "CB1_I3TDD" },
{IR_FLOAT, 161, 0, "CB1_V12" },
{IR_FLOAT, 163, 0, "CB1_V23" },
{IR_FLOAT, 165, 0, "CB1_V31" },
{IR_FLOAT, 167, 0, "CB1_TotalKW" },
{IR_FLOAT, 169, 0, "CB1_TotalKVar" },
{IR_FLOAT, 171, 0, "CB1_TotalKVA" },
{IR_FLOAT, 173, 0, "CB1_TotalPF" },
{IR_FLOAT, 175, 0, "CB1_TotalPFLag" },
{IR_FLOAT, 177, 0, "CB1_TotalPFLead" },
{IR_FLOAT, 179, 0, "CB1_TotalKWImport" },
{IR_FLOAT, 181, 0, "CB1_TotalKWExport" },
{IR_FLOAT, 183, 0, "CB1_TotalKVarImport" },
{IR_FLOAT, 185, 0, "CB1_TotalKVarExport" },
{IR_FLOAT, 187, 0, "CB1_LN_Avg" },
{IR_FLOAT, 189, 0, "CB1_LL_Avg" },
{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 1 (OB01)
{IR_FLOAT, 201, 0, "CB2_V1N" },
{IR_FLOAT, 203, 0, "CB2_V2N" },
{IR_FLOAT, 205, 0, "CB2_V3N" },
{IR_FLOAT, 207, 0, "CB2_I1" },
{IR_FLOAT, 209, 0, "CB2_I2" },
{IR_FLOAT, 211, 0, "CB2_I3" },
{IR_FLOAT, 213, 0, "CB2_L1KW" },
{IR_FLOAT, 215, 0, "CB2_L2KW" },
{IR_FLOAT, 217, 0, "CB2_L3KW" },
{IR_FLOAT, 219, 0, "CB2_L1KVar" },
{IR_FLOAT, 221, 0, "CB2_L2KVar" },
{IR_FLOAT, 223, 0, "CB2_L3KVar" },
{IR_FLOAT, 225, 0, "CB2_L1KVA" },
{IR_FLOAT, 227, 0, "CB2_L2KVA" },
{IR_FLOAT, 229, 0, "CB2_L3KVA" },
{IR_FLOAT, 231, 0, "CB2_L1PF" },
{IR_FLOAT, 233, 0, "CB2_L2PF" },
{IR_FLOAT, 235, 0, "CB2_L3PF" },
{IR_FLOAT, 237, 0, "CB2_V1THD" },
{IR_FLOAT, 239, 0, "CB2_V2THD" },
{IR_FLOAT, 241, 0, "CB2_V3THD" },
{IR_FLOAT, 243, 0, "CB2_I1THD" },
{IR_FLOAT, 245, 0, "CB2_I2THD" },
{IR_FLOAT, 247, 0, "CB2_I3THD" },
{IR_FLOAT, 249, 0, "CB2_I1Kfactor" },
{IR_FLOAT, 251, 0, "CB2_I2Kfactor" },
{IR_FLOAT, 253, 0, "CB2_I3Kfactor" },
{IR_FLOAT, 255, 0, "CB2_I1TDD" },
{IR_FLOAT, 257, 0, "CB2_I2TDD" },
{IR_FLOAT, 259, 0, "CB2_I3TDD" },
{IR_FLOAT, 261, 0, "CB2_V12" },
{IR_FLOAT, 263, 0, "CB2_V23" },
{IR_FLOAT, 265, 0, "CB2_V31" },
{IR_FLOAT, 267, 0, "CB2_TotalKW" },
{IR_FLOAT, 269, 0, "CB2_TotalKVar" },
{IR_FLOAT, 271, 0, "CB2_TotalKVA" },
{IR_FLOAT, 273, 0, "CB2_TotalPF" },
{IR_FLOAT, 275, 0, "CB2_TotalPFLag" },
{IR_FLOAT, 277, 0, "CB2_TotalPFLead" },
{IR_FLOAT, 279, 0, "CB2_TotalKWImport" },
{IR_FLOAT, 281, 0, "CB2_TotalKWExport" },
{IR_FLOAT, 283, 0, "CB2_TotalKVarImport" },
{IR_FLOAT, 285, 0, "CB2_TotalKVarExport" },
{IR_FLOAT, 287, 0, "CB2_LN_Avg" },
{IR_FLOAT, 289, 0, "CB2_LL_Avg" },
// 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 1 (OB01)
{IR_FLOAT, 301, 0, "CB3_V1N" },
{IR_FLOAT, 303, 0, "CB3_V2N" },
{IR_FLOAT, 305, 0, "CB3_V3N" },
{IR_FLOAT, 307, 0, "CB3_I1" },
{IR_FLOAT, 309, 0, "CB3_I2" },
{IR_FLOAT, 311, 0, "CB3_I3" },
{IR_FLOAT, 313, 0, "CB3_L1KW" },
{IR_FLOAT, 315, 0, "CB3_L2KW" },
{IR_FLOAT, 317, 0, "CB3_L3KW" },
{IR_FLOAT, 319, 0, "CB3_L1KVar" },
{IR_FLOAT, 321, 0, "CB3_L2KVar" },
{IR_FLOAT, 323, 0, "CB3_L3KVar" },
{IR_FLOAT, 325, 0, "CB3_L1KVA" },
{IR_FLOAT, 327, 0, "CB3_L2KVA" },
{IR_FLOAT, 329, 0, "CB3_L3KVA" },
{IR_FLOAT, 331, 0, "CB3_L1PF" },
{IR_FLOAT, 333, 0, "CB3_L2PF" },
{IR_FLOAT, 335, 0, "CB3_L3PF" },
{IR_FLOAT, 337, 0, "CB3_V1THD" },
{IR_FLOAT, 339, 0, "CB3_V2THD" },
{IR_FLOAT, 341, 0, "CB3_V3THD" },
{IR_FLOAT, 343, 0, "CB3_I1THD" },
{IR_FLOAT, 345, 0, "CB3_I2THD" },
{IR_FLOAT, 347, 0, "CB3_I3THD" },
{IR_FLOAT, 349, 0, "CB3_I1Kfactor" },
{IR_FLOAT, 351, 0, "CB3_I2Kfactor" },
{IR_FLOAT, 353, 0, "CB3_I3Kfactor" },
{IR_FLOAT, 355, 0, "CB3_I1TDD" },
{IR_FLOAT, 357, 0, "CB3_I2TDD" },
{IR_FLOAT, 359, 0, "CB3_I3TDD" },
{IR_FLOAT, 361, 0, "CB3_V12" },
{IR_FLOAT, 363, 0, "CB3_V23" },
{IR_FLOAT, 365, 0, "CB3_V31" },
{IR_FLOAT, 367, 0, "CB3_TotalKW" },
{IR_FLOAT, 369, 0, "CB3_TotalKVar" },
{IR_FLOAT, 371, 0, "CB3_TotalKVA" },
{IR_FLOAT, 373, 0, "CB3_TotalPF" },
{IR_FLOAT, 375, 0, "CB3_TotalPFLag" },
{IR_FLOAT, 377, 0, "CB3_TotalPFLead" },
{IR_FLOAT, 379, 0, "CB3_TotalKWImport" },
{IR_FLOAT, 381, 0, "CB3_TotalKWExport" },
{IR_FLOAT, 383, 0, "CB3_TotalKVarImport" },
{IR_FLOAT, 385, 0, "CB3_TotalKVarExport" },
{IR_FLOAT, 387, 0, "CB3_LN_Avg" },
{IR_FLOAT, 389, 0, "CB3_LL_Avg" },
// 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 1 (OB01)
{IR_FLOAT, 401, 0, "CB4_V1N" },
{IR_FLOAT, 403, 0, "CB4_V2N" },
{IR_FLOAT, 405, 0, "CB4_V3N" },
{IR_FLOAT, 407, 0, "CB4_I1" },
{IR_FLOAT, 409, 0, "CB4_I2" },
{IR_FLOAT, 411, 0, "CB4_I3" },
{IR_FLOAT, 413, 0, "CB4_L1KW" },
{IR_FLOAT, 415, 0, "CB4_L2KW" },
{IR_FLOAT, 417, 0, "CB4_L3KW" },
{IR_FLOAT, 419, 0, "CB4_L1KVar" },
{IR_FLOAT, 421, 0, "CB4_L2KVar" },
{IR_FLOAT, 423, 0, "CB4_L3KVar" },
{IR_FLOAT, 425, 0, "CB4_L1KVA" },
{IR_FLOAT, 427, 0, "CB4_L2KVA" },
{IR_FLOAT, 429, 0, "CB4_L3KVA" },
{IR_FLOAT, 431, 0, "CB4_L1PF" },
{IR_FLOAT, 433, 0, "CB4_L2PF" },
{IR_FLOAT, 435, 0, "CB4_L3PF" },
{IR_FLOAT, 437, 0, "CB4_V1THD" },
{IR_FLOAT, 439, 0, "CB4_V2THD" },
{IR_FLOAT, 441, 0, "CB4_V3THD" },
{IR_FLOAT, 443, 0, "CB4_I1THD" },
{IR_FLOAT, 445, 0, "CB4_I2THD" },
{IR_FLOAT, 447, 0, "CB4_I3THD" },
{IR_FLOAT, 449, 0, "CB4_I1Kfactor" },
{IR_FLOAT, 451, 0, "CB4_I2Kfactor" },
{IR_FLOAT, 453, 0, "CB4_I3Kfactor" },
{IR_FLOAT, 455, 0, "CB4_I1TDD" },
{IR_FLOAT, 457, 0, "CB4_I2TDD" },
{IR_FLOAT, 459, 0, "CB4_I3TDD" },
{IR_FLOAT, 461, 0, "CB4_V12" },
{IR_FLOAT, 463, 0, "CB4_V23" },
{IR_FLOAT, 465, 0, "CB4_V31" },
{IR_FLOAT, 467, 0, "CB4_TotalKW" },
{IR_FLOAT, 469, 0, "CB4_TotalKVar" },
{IR_FLOAT, 471, 0, "CB4_TotalKVA" },
{IR_FLOAT, 473, 0, "CB4_TotalPF" },
{IR_FLOAT, 475, 0, "CB4_TotalPFLag" },
{IR_FLOAT, 477, 0, "CB4_TotalPFLead" },
{IR_FLOAT, 479, 0, "CB4_TotalKWImport" },
{IR_FLOAT, 481, 0, "CB4_TotalKWExport" },
{IR_FLOAT, 483, 0, "CB4_TotalKVarImport" },
{IR_FLOAT, 485, 0, "CB4_TotalKVarExport" },
{IR_FLOAT, 487, 0, "CB4_LN_Avg" },
{IR_FLOAT, 489, 0, "CB4_LL_Avg" },
// 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 1 (OB01)
{IR_FLOAT, 501, 0, "CB5_V1N" },
{IR_FLOAT, 503, 0, "CB5_V2N" },
{IR_FLOAT, 505, 0, "CB5_V3N" },
{IR_FLOAT, 507, 0, "CB5_I1" },
{IR_FLOAT, 509, 0, "CB5_I2" },
{IR_FLOAT, 511, 0, "CB5_I3" },
{IR_FLOAT, 513, 0, "CB5_L1KW" },
{IR_FLOAT, 515, 0, "CB5_L2KW" },
{IR_FLOAT, 517, 0, "CB5_L3KW" },
{IR_FLOAT, 519, 0, "CB5_L1KVar" },
{IR_FLOAT, 521, 0, "CB5_L2KVar" },
{IR_FLOAT, 523, 0, "CB5_L3KVar" },
{IR_FLOAT, 525, 0, "CB5_L1KVA" },
{IR_FLOAT, 527, 0, "CB5_L2KVA" },
{IR_FLOAT, 529, 0, "CB5_L3KVA" },
{IR_FLOAT, 531, 0, "CB5_L1PF" },
{IR_FLOAT, 533, 0, "CB5_L2PF" },
{IR_FLOAT, 535, 0, "CB5_L3PF" },
{IR_FLOAT, 537, 0, "CB5_V1THD" },
{IR_FLOAT, 539, 0, "CB5_V2THD" },
{IR_FLOAT, 541, 0, "CB5_V3THD" },
{IR_FLOAT, 543, 0, "CB5_I1THD" },
{IR_FLOAT, 545, 0, "CB5_I2THD" },
{IR_FLOAT, 547, 0, "CB5_I3THD" },
{IR_FLOAT, 549, 0, "CB5_I1Kfactor" },
{IR_FLOAT, 551, 0, "CB5_I2Kfactor" },
{IR_FLOAT, 553, 0, "CB5_I3Kfactor" },
{IR_FLOAT, 555, 0, "CB5_I1TDD" },
{IR_FLOAT, 557, 0, "CB5_I2TDD" },
{IR_FLOAT, 559, 0, "CB5_I3TDD" },
{IR_FLOAT, 561, 0, "CB5_V12" },
{IR_FLOAT, 563, 0, "CB5_V23" },
{IR_FLOAT, 565, 0, "CB5_V31" },
{IR_FLOAT, 567, 0, "CB5_TotalKW" },
{IR_FLOAT, 569, 0, "CB5_TotalKVar" },
{IR_FLOAT, 571, 0, "CB5_TotalKVA" },
{IR_FLOAT, 573, 0, "CB5_TotalPF" },
{IR_FLOAT, 575, 0, "CB5_TotalPFLag" },
{IR_FLOAT, 577, 0, "CB5_TotalPFLead" },
{IR_FLOAT, 579, 0, "CB5_TotalKWImport" },
{IR_FLOAT, 581, 0, "CB5_TotalKWExport" },
{IR_FLOAT, 583, 0, "CB5_TotalKVarImport" },
{IR_FLOAT, 585, 0, "CB5_TotalKVarExport" },
{IR_FLOAT, 587, 0, "CB5_LN_Avg" },
{IR_FLOAT, 589, 0, "CB5_LL_Avg" },
// 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 1 (OB01)
{IR_FLOAT, 601, 0, "CB6_V1N" },
{IR_FLOAT, 603, 0, "CB6_V2N" },
{IR_FLOAT, 605, 0, "CB6_V3N" },
{IR_FLOAT, 607, 0, "CB6_I1" },
{IR_FLOAT, 609, 0, "CB6_I2" },
{IR_FLOAT, 611, 0, "CB6_I3" },
{IR_FLOAT, 613, 0, "CB6_L1KW" },
{IR_FLOAT, 615, 0, "CB6_L2KW" },
{IR_FLOAT, 617, 0, "CB6_L3KW" },
{IR_FLOAT, 619, 0, "CB6_L1KVar" },
{IR_FLOAT, 621, 0, "CB6_L2KVar" },
{IR_FLOAT, 623, 0, "CB6_L3KVar" },
{IR_FLOAT, 625, 0, "CB6_L1KVA" },
{IR_FLOAT, 627, 0, "CB6_L2KVA" },
{IR_FLOAT, 629, 0, "CB6_L3KVA" },
{IR_FLOAT, 631, 0, "CB6_L1PF" },
{IR_FLOAT, 633, 0, "CB6_L2PF" },
{IR_FLOAT, 635, 0, "CB6_L3PF" },
{IR_FLOAT, 637, 0, "CB6_V1THD" },
{IR_FLOAT, 639, 0, "CB6_V2THD" },
{IR_FLOAT, 641, 0, "CB6_V3THD" },
{IR_FLOAT, 643, 0, "CB6_I1THD" },
{IR_FLOAT, 645, 0, "CB6_I2THD" },
{IR_FLOAT, 647, 0, "CB6_I3THD" },
{IR_FLOAT, 649, 0, "CB6_I1Kfactor" },
{IR_FLOAT, 651, 0, "CB6_I2Kfactor" },
{IR_FLOAT, 653, 0, "CB6_I3Kfactor" },
{IR_FLOAT, 655, 0, "CB6_I1TDD" },
{IR_FLOAT, 657, 0, "CB6_I2TDD" },
{IR_FLOAT, 659, 0, "CB6_I3TDD" },
{IR_FLOAT, 661, 0, "CB6_V12" },
{IR_FLOAT, 663, 0, "CB6_V23" },
{IR_FLOAT, 665, 0, "CB6_V31" },
{IR_FLOAT, 667, 0, "CB6_TotalKW" },
{IR_FLOAT, 669, 0, "CB6_TotalKVar" },
{IR_FLOAT, 671, 0, "CB6_TotalKVA" },
{IR_FLOAT, 673, 0, "CB6_TotalPF" },
{IR_FLOAT, 675, 0, "CB6_TotalPFLag" },
{IR_FLOAT, 677, 0, "CB6_TotalPFLead" },
{IR_FLOAT, 679, 0, "CB6_TotalKWImport" },
{IR_FLOAT, 681, 0, "CB6_TotalKWExport" },
{IR_FLOAT, 683, 0, "CB6_TotalKVarImport" },
{IR_FLOAT, 685, 0, "CB6_TotalKVarExport" },
{IR_FLOAT, 687, 0, "CB6_LN_Avg" },
{IR_FLOAT, 689, 0, "CB6_LL_Avg" },
// 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 1 (OB01)
{IR_FLOAT, 701, 0, "CB7_V1N" },
{IR_FLOAT, 703, 0, "CB7_V2N" },
{IR_FLOAT, 705, 0, "CB7_V3N" },
{IR_FLOAT, 707, 0, "CB7_I1" },
{IR_FLOAT, 709, 0, "CB7_I2" },
{IR_FLOAT, 711, 0, "CB7_I3" },
{IR_FLOAT, 713, 0, "CB7_L1KW" },
{IR_FLOAT, 715, 0, "CB7_L2KW" },
{IR_FLOAT, 717, 0, "CB7_L3KW" },
{IR_FLOAT, 719, 0, "CB7_L1KVar" },
{IR_FLOAT, 721, 0, "CB7_L2KVar" },
{IR_FLOAT, 723, 0, "CB7_L3KVar" },
{IR_FLOAT, 725, 0, "CB7_L1KVA" },
{IR_FLOAT, 727, 0, "CB7_L2KVA" },
{IR_FLOAT, 729, 0, "CB7_L3KVA" },
{IR_FLOAT, 731, 0, "CB7_L1PF" },
{IR_FLOAT, 733, 0, "CB7_L2PF" },
{IR_FLOAT, 735, 0, "CB7_L3PF" },
{IR_FLOAT, 737, 0, "CB7_V1THD" },
{IR_FLOAT, 739, 0, "CB7_V2THD" },
{IR_FLOAT, 741, 0, "CB7_V3THD" },
{IR_FLOAT, 743, 0, "CB7_I1THD" },
{IR_FLOAT, 745, 0, "CB7_I2THD" },
{IR_FLOAT, 747, 0, "CB7_I3THD" },
{IR_FLOAT, 749, 0, "CB7_I1Kfactor" },
{IR_FLOAT, 751, 0, "CB7_I2Kfactor" },
{IR_FLOAT, 753, 0, "CB7_I3Kfactor" },
{IR_FLOAT, 755, 0, "CB7_I1TDD" },
{IR_FLOAT, 757, 0, "CB7_I2TDD" },
{IR_FLOAT, 759, 0, "CB7_I3TDD" },
{IR_FLOAT, 761, 0, "CB7_V12" },
{IR_FLOAT, 763, 0, "CB7_V23" },
{IR_FLOAT, 765, 0, "CB7_V31" },
{IR_FLOAT, 767, 0, "CB7_TotalKW" },
{IR_FLOAT, 769, 0, "CB7_TotalKVar" },
{IR_FLOAT, 771, 0, "CB7_TotalKVA" },
{IR_FLOAT, 773, 0, "CB7_TotalPF" },
{IR_FLOAT, 775, 0, "CB7_TotalPFLag" },
{IR_FLOAT, 777, 0, "CB7_TotalPFLead" },
{IR_FLOAT, 779, 0, "CB7_TotalKWImport" },
{IR_FLOAT, 781, 0, "CB7_TotalKWExport" },
{IR_FLOAT, 783, 0, "CB7_TotalKVarImport" },
{IR_FLOAT, 785, 0, "CB7_TotalKVarExport" },
{IR_FLOAT, 787, 0, "CB7_LN_Avg" },
{IR_FLOAT, 789, 0, "CB7_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 1 (OB01)
{IR_FLOAT, 801, 0, "CB8_V1N" },
{IR_FLOAT, 803, 0, "CB8_V2N" },
{IR_FLOAT, 805, 0, "CB8_V3N" },
{IR_FLOAT, 807, 0, "CB8_I1" },
{IR_FLOAT, 809, 0, "CB8_I2" },
{IR_FLOAT, 811, 0, "CB8_I3" },
{IR_FLOAT, 813, 0, "CB8_L1KW" },
{IR_FLOAT, 815, 0, "CB8_L2KW" },
{IR_FLOAT, 817, 0, "CB8_L3KW" },
{IR_FLOAT, 819, 0, "CB8_L1KVar" },
{IR_FLOAT, 821, 0, "CB8_L2KVar" },
{IR_FLOAT, 823, 0, "CB8_L3KVar" },
{IR_FLOAT, 825, 0, "CB8_L1KVA" },
{IR_FLOAT, 827, 0, "CB8_L2KVA" },
{IR_FLOAT, 829, 0, "CB8_L3KVA" },
{IR_FLOAT, 831, 0, "CB8_L1PF" },
{IR_FLOAT, 833, 0, "CB8_L2PF" },
{IR_FLOAT, 835, 0, "CB8_L3PF" },
{IR_FLOAT, 837, 0, "CB8_V1THD" },
{IR_FLOAT, 839, 0, "CB8_V2THD" },
{IR_FLOAT, 841, 0, "CB8_V3THD" },
{IR_FLOAT, 843, 0, "CB8_I1THD" },
{IR_FLOAT, 845, 0, "CB8_I2THD" },
{IR_FLOAT, 847, 0, "CB8_I3THD" },
{IR_FLOAT, 849, 0, "CB8_I1Kfactor" },
{IR_FLOAT, 851, 0, "CB8_I2Kfactor" },
{IR_FLOAT, 853, 0, "CB8_I3Kfactor" },
{IR_FLOAT, 855, 0, "CB8_I1TDD" },
{IR_FLOAT, 857, 0, "CB8_I2TDD" },
{IR_FLOAT, 859, 0, "CB8_I3TDD" },
{IR_FLOAT, 861, 0, "CB8_V12" },
{IR_FLOAT, 863, 0, "CB8_V23" },
{IR_FLOAT, 865, 0, "CB8_V31" },
{IR_FLOAT, 867, 0, "CB8_TotalKW" },
{IR_FLOAT, 869, 0, "CB8_TotalKVar" },
{IR_FLOAT, 871, 0, "CB8_TotalKVA" },
{IR_FLOAT, 873, 0, "CB8_TotalPF" },
{IR_FLOAT, 875, 0, "CB8_TotalPFLag" },
{IR_FLOAT, 877, 0, "CB8_TotalPFLead" },
{IR_FLOAT, 879, 0, "CB8_TotalKWImport" },
{IR_FLOAT, 881, 0, "CB8_TotalKWExport" },
{IR_FLOAT, 883, 0, "CB8_TotalKVarImport" },
{IR_FLOAT, 885, 0, "CB8_TotalKVarExport" },
{IR_FLOAT, 887, 0, "CB8_LN_Avg" },
{IR_FLOAT, 889, 0, "CB8_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" },
};
//Size of modbus map used in FOR cycles, automatically calculated.

View File

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

@@ -1,141 +0,0 @@
/**
* @file config.h
* @brief Main configuration file for the CRAH Unit (TCP) emulator.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-02
*
* This file contains two important configurations: WiFi network parameters
* and the Modbus register map for the device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 15, 0, "State Control"}, //Internal to control from Modscan
{HR, 16, 0, "Fault Code"}, //Internal Fault code from Modscan
{DI, 20, 0, "Bypass Not Ready"},
{DI, 244, 0, "System Shutdown-EPO"},
{DI, 247, 0, "System Fan Failure"},
{DI, 245, 0, "Fuse Failure"},
{DI, 239, 0, "Internal Comms Failure"},
{DI, 254, 0, "UPS Output on Bypass"},
{DI, 249, 0, "System Output Off"},
{DI, 11, 0, "Output Overload"},
{DI, 263, 0, "Battery Low"},
{IR, 183, 0, "UPS Battery Status"},
{IR, 180, 0, "Battery Time Remaining"},
{IR, 29, 0, "Bypass Input Frequency"},
{IR, 30, 0, "Bypass Power Phase A"},
{IR, 31, 0, "Bypass Power Phase B"},
{IR, 32, 0, "Bypass Power Phase C"},
{IR, 23, 0, "Bypass Input Voltage RMS A-B"},
{IR, 26, 0, "Bypass Input Voltage RMS A-N"},
{IR, 24, 0, "Bypass Input Voltage RMS B-C"},
{IR, 27, 0, "Bypass Input Voltage RMS B-N"},
{IR, 25, 0, "Bypass Input Voltage RMS C-A"},
{IR, 28, 0, "Bypass Input Voltage RMS C-N"},
{IR, 175, 0, "DC Bus Voltage"},
{IR, 7, 0, "System Input RMS Current Phase A"},
{IR, 8, 0, "System Input RMS Current Phase B"},
{IR, 9, 0, "System Input RMS Current Phase C"},
{IR, 10, 0, "System Input Frequency"},
{IR, 17, 0, "System Input Apparent Power Phs A"},
{IR, 18, 0, "System Input Apparent Power Phs B"},
{IR, 19, 0, "System Input Apparent Power Phs C"},
{IR, 14, 0, "System Input Power Phase A"},
{IR, 15, 0, "System Input Power Phase B"},
{IR, 16, 0, "System Input Power Phase C"},
{IR, 11, 0, "System Input Power Factor Phs A"},
{IR, 12, 0, "System Input Power Factor Phs B"},
{IR, 13, 0, "System Input Power Factor Phs C"},
{IR, 1, 0, "System Input RMS A-B"},
{IR, 4, 0, "System Input RMS A-N"},
{IR, 2, 0, "System Input RMS B-C"},
{IR, 5, 0, "System Input RMS B-N"},
{IR, 3, 0, "System Input RMS C-A"},
{IR, 6, 0, "System Input RMS C-N"},
{IR, 50, 0, "System Output Frequency"},
{IR, 44, 0, "System Output RMS Current Phase A"},
{IR, 45, 0, "System Output RMS Current Phase B"},
{IR, 46, 0, "System Output RMS Current Phase C"},
{IR, 61, 0, "System Output Apparent Power"},
{IR, 57, 0, "System Output Apparent Power Phs A"},
{IR, 58, 0, "System Output Apparent Power Phs B"},
{IR, 59, 0, "System Output Apparent Power Phs C"},
{IR, 60, 0, "System Output Power"},
{IR, 54, 0, "System Output Power Phase A"},
{IR, 55, 0, "System Output Power Phase B"},
{IR, 56, 0, "System Output Power Phase C"},
{IR, 51, 0, "System Output Power Factor Phs A"},
{IR, 52, 0, "System Output Power Factor Phs B"},
{IR, 53, 0, "System Output Power Factor Phs C"},
{IR, 38, 0, "System Output Voltage RMS A-B"},
{IR, 41, 0, "System Output Voltage RMS A-N"},
{IR, 39, 0, "System Output Voltage RMS B-C"},
{IR, 42, 0, "System Output Voltage RMS B-N"},
{IR, 40, 0, "System Output Voltage RMS C-A"},
{IR, 43, 0, "System Output Voltage RMS C-N"},
{IR, 164, 0, "UPS Loading 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,200 @@
/**
* @file State_Battery.cpp
* @brief Implementation of the BatteryState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the BatteryState, which defines
* the behavior of the equipment when it is actively Battery.
*/
#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_Battery.h"
#include "States/State_Bypass.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 BatteryState object.
*
* This constructor initializes behavior strategies active during the Battery
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
BatteryState<ModbusIP>::BatteryState() {
addStrategy("System Output RMS A-B", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS B-C", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS C-A", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS A-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS B-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS C-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Frequency", new SingleValueStrategy(60.0F, 2.0f, 1000));
addStrategy("System Output Power Factor Phs A", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Factor Phs B", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Factor Phs C", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("Battery Time Remaining", new RampStrategy(0.0F, 0.3f, 1000));
}
/**
* @brief Executes the Battery 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 Battery 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>* BatteryState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Battery update function");
float State_Ctrl = getPointValue(equipment, "Px State");
switch (static_cast<int>(State_Ctrl)) {
case 1:
return new StandbyState<ModbusIP>();
break;
case 2:
return new RunningState<ModbusIP>();
break;
case 4:
return new BypassState<ModbusIP>();
break;
default:
break;
}
float rating = getPointValue(equipment, "Px Rating");
float load = getPointValue(equipment, "Px Load");
float real_load = rating * (load/100.f);
Strategy_Behavior* ramp_strat = nullptr;
//Output strategies
float Out_Vab = getPointValue(equipment, "System Output RMS A-B");
ramp_strat = getStrategy("System Output RMS Current Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vab);
float Out_Vbc = getPointValue(equipment, "System Output RMS B-C");
ramp_strat = getStrategy("System Output RMS Current Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vbc);
float Out_Vca = getPointValue(equipment, "System Output RMS C-A");
ramp_strat = getStrategy("System Output RMS Current Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vca);
float Out_Van = getPointValue(equipment, "System Output RMS A-N");
float Out_Ia = getPointValue(equipment, "System Output RMS Current Phase A");
float Out_PFa = getPointValue(equipment, "System Output Power Factor Phs A");
ramp_strat = getStrategy("System Output Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Van * Out_Ia);
ramp_strat = getStrategy("System Output Apparent Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Van * Out_Ia * Out_PFa);
float Out_Vbn = getPointValue(equipment, "System Output RMS B-N");
float Out_Ib = getPointValue(equipment, "System Output RMS Current Phase B");
float Out_PFb = getPointValue(equipment, "System Output Power Factor Phs B");
ramp_strat = getStrategy("System Output Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vbn * Out_Ib);
ramp_strat = getStrategy("System Output Apparent Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vbn * Out_Ib * Out_PFb);
float Out_Vcn = getPointValue(equipment, "System Output RMS C-N");
float Out_Ic = getPointValue(equipment, "System Output RMS Current Phase C");
float Out_PFc = getPointValue(equipment, "System Output Power Factor Phs C");
ramp_strat = getStrategy("System Output Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vcn * Out_Ic);
ramp_strat = getStrategy("System Output Apparent Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vcn * Out_Ic * Out_PFc);
float Battery_time = getPointValue(equipment, "Battery Time Remaining");
float Bat_Percent = Battery_time /4.80f;
if (Bat_Percent > 98.0f){
setPointValue(equipment, "UPS Battery Status2", 0.0f);
}
if (Bat_Percent > 20.0f) {
setPointValue(equipment, "UPS Battery Status1", 2.0f);
setPointValue(equipment, "Battery Low", 0.0f);
}
if (Bat_Percent <= 20.0f && Bat_Percent >= 5.0f){
setPointValue(equipment, "UPS Battery Status1", 3.0f);
setPointValue(equipment, "Battery Low", 1.0f);
}
if (Bat_Percent < 5.0f){
setPointValue(equipment, "UPS Battery Status1", 4.0f);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the Battery 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 BatteryState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Battery State...");
setPointValue(equipment, "Bypass Input Voltage RMS A-B", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS B-C", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS C-A", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS A-N", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS B-N", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS C-N", 0.0f);
setPointValue(equipment, "Bypass Input Frequency", 0.0f);
setPointValue(equipment, "Bypass Power Phase A", 0.0f);
setPointValue(equipment, "Bypass Power Phase B", 0.0f);
setPointValue(equipment, "Bypass Power Phase C", 0.0f);
setPointValue(equipment, "UPS Loading Status", 6.0f);
setPointValue(equipment, "UPS Battery Status2", 2.0f);
// You could also update a Modbus register to show the "standby" state
}
/**
* @brief Logic to execute once when exiting the Battery 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 BatteryState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Battery State...");
}

View File

@@ -0,0 +1,271 @@
/**
* @file State_Bypass.cpp
* @brief Implementation of the BypassState class.
* @author Emmanuel Hernandez Cruz
* @date 2025-09-05
*
* This file contains the implementation for the BypassState, which defines
* the behavior of the equipment when it is actively Bypass.
*/
#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_Bypass.h"
#include "States/State_Battery.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 BypassState object.
*
* This constructor initializes behavior strategies active during the Bypass
* state, such as a PID controller for the 'CW Valve Position' and totalizers
* for the run-hours of each EC fan.
*/
template<>
BypassState<ModbusIP>::BypassState() {
//Input System
addStrategy("System Input RMS A-B", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Input RMS B-C", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Input RMS C-A", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Input RMS A-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Input RMS B-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
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("System Input RMS Current Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input RMS Current Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Frequency", new SingleValueStrategy(60.0F, 2.0f, 1000));
addStrategy("System Input Power Factor Phs A", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Input Power Factor Phs B", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Input Power Factor Phs C", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Input Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Apparent Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Apparent Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Apparent Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
//Bypass System
addStrategy("Bypass Input Voltage RMS A-B", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("Bypass Input Voltage RMS B-C", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("Bypass Input Voltage RMS C-A", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("Bypass Input Voltage RMS A-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("Bypass Input Voltage RMS B-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("Bypass Input Voltage RMS C-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("Bypass Input Frequency", new SingleValueStrategy(60.0F, 2.0f, 1000));
addStrategy("Bypass Input Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("Bypass Input Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("Bypass Input Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
//Output System
addStrategy("System Output RMS A-B", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS B-C", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS C-A", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS A-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS B-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS C-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Frequency", new SingleValueStrategy(60.0F, 2.0f, 1000));
addStrategy("System Output Power Factor Phs A", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Factor Phs B", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Factor Phs C", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("Battery Time Remaining", new RampStrategy(480.0F, 0.3f, 1000));
addStrategy("DC Bus Voltage", new SingleValueStrategy(518.0F, 5.0f, 1000));
}
/**
* @brief Executes the Bypass 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 Bypass 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>* BypassState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
// STATE control, add conditions if change to a different state is needed
Serial.println("Bypass update function");
float State_Ctrl = getPointValue(equipment, "Px State");
switch (static_cast<int>(State_Ctrl)) {
case 1:
return new StandbyState<ModbusIP>();
break;
case 2:
return new RunningState<ModbusIP>();
break;
case 3:
return new BatteryState<ModbusIP>();
break;
default:
break;
}
float rating = getPointValue(equipment, "Px Rating");
float load = getPointValue(equipment, "Px Load");
float real_load = rating * (load/100.f);
Strategy_Behavior* ramp_strat = nullptr;
//Input strategies
float In_Vab = getPointValue(equipment, "System Input RMS A-B");
ramp_strat = getStrategy("System Input RMS Current Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/In_Vab);
float In_Vbc = getPointValue(equipment, "System Input RMS B-C");
ramp_strat = getStrategy("System Input RMS Current Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/In_Vbc);
float In_Vca = getPointValue(equipment, "System Input RMS C-A");
ramp_strat = getStrategy("System Input RMS Current Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/In_Vca);
float In_Van = getPointValue(equipment, "System Input RMS A-N");
float In_Ia = getPointValue(equipment, "System Input RMS Current Phase A");
float In_PFa = getPointValue(equipment, "System Input Power Factor Phs A");
ramp_strat = getStrategy("System Input Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Van * In_Ia);
ramp_strat = getStrategy("Bypass Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Van * In_Ia);
ramp_strat = getStrategy("System Input Apparent Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Van * In_Ia * In_PFa);
float In_Vbn = getPointValue(equipment, "System Input RMS B-N");
float In_Ib = getPointValue(equipment, "System Input RMS Current Phase B");
float In_PFb = getPointValue(equipment, "System Input Power Factor Phs B");
ramp_strat = getStrategy("System Input Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vbn * In_Ib);
ramp_strat = getStrategy("Bypass Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vbn * In_Ib);
ramp_strat = getStrategy("System Input Apparent Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vbn * In_Ib * In_PFb);
float In_Vcn = getPointValue(equipment, "System Input RMS C-N");
float In_Ic = getPointValue(equipment, "System Input RMS Current Phase C");
float In_PFc = getPointValue(equipment, "System Input Power Factor Phs C");
ramp_strat = getStrategy("System Input Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vcn * In_Ic);
ramp_strat = getStrategy("Bypass Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vcn * In_Ic);
ramp_strat = getStrategy("System Input Apparent Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vcn * In_Ic * In_PFc);
//Output strategies
float Out_Vab = getPointValue(equipment, "System Output RMS A-B");
ramp_strat = getStrategy("System Output RMS Current Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vab);
float Out_Vbc = getPointValue(equipment, "System Output RMS B-C");
ramp_strat = getStrategy("System Output RMS Current Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vbc);
float Out_Vca = getPointValue(equipment, "System Output RMS C-A");
ramp_strat = getStrategy("System Output RMS Current Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vca);
float Out_Van = getPointValue(equipment, "System Output RMS A-N");
float Out_Ia = getPointValue(equipment, "System Output RMS Current Phase A");
float Out_PFa = getPointValue(equipment, "System Output Power Factor Phs A");
ramp_strat = getStrategy("System Output Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Van * Out_Ia);
ramp_strat = getStrategy("System Output Apparent Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Van * Out_Ia * Out_PFa);
float Out_Vbn = getPointValue(equipment, "System Output RMS B-N");
float Out_Ib = getPointValue(equipment, "System Output RMS Current Phase B");
float Out_PFb = getPointValue(equipment, "System Output Power Factor Phs B");
ramp_strat = getStrategy("System Output Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vbn * Out_Ib);
ramp_strat = getStrategy("System Output Apparent Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vbn * Out_Ib * Out_PFb);
float Out_Vcn = getPointValue(equipment, "System Output RMS C-N");
float Out_Ic = getPointValue(equipment, "System Output RMS Current Phase C");
float Out_PFc = getPointValue(equipment, "System Output Power Factor Phs C");
ramp_strat = getStrategy("System Output Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vcn * Out_Ic);
ramp_strat = getStrategy("System Output Apparent Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vcn * Out_Ic * Out_PFc);
float Battery_time = getPointValue(equipment, "Battery Time Remaining");
float Bat_Percent = Battery_time /4.80f;
if (Bat_Percent > 98.0f){
setPointValue(equipment, "UPS Battery Status2", 0.0f);
}
if (Bat_Percent > 20.0f) {
setPointValue(equipment, "UPS Battery Status1", 2.0f);
setPointValue(equipment, "Battery Low", 0.0f);
}
if (Bat_Percent <= 20.0f && Bat_Percent >= 5.0f){
setPointValue(equipment, "UPS Battery Status1", 3.0f);
setPointValue(equipment, "Battery Low", 1.0f);
}
if (Bat_Percent < 5.0f){
setPointValue(equipment, "UPS Battery Status1", 4.0f);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the Bypass 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 BypassState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Battery State...");
setPointValue(equipment, "UPS Loading Status", 4.0f);
setPointValue(equipment, "UPS Battery Status2", 3.0f);
// You could also update a Modbus register to show the "standby" state
}
/**
* @brief Logic to execute once when exiting the Bypass 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 BypassState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Bypass State...");
}

View File

@@ -14,6 +14,7 @@
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Battery.h"
#include "States/State_Fail.h"
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
@@ -53,7 +54,13 @@ 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");
float State_Ctrl = getPointValue(equipment, "Px State");
if (State_Ctrl == 2){
return new RunningState<ModbusIP>();
}
if (State_Ctrl == 1){
return new StandbyState<ModbusIP>();
}
_applyStrategies(equipment);
return nullptr;
}

View File

@@ -0,0 +1,264 @@
/**
* @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_Battery.h"
#include "States/State_Bypass.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("System Input RMS A-B", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Input RMS B-C", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Input RMS C-A", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Input RMS A-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Input RMS B-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
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("System Input RMS Current Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input RMS Current Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Frequency", new SingleValueStrategy(60.0F, 2.0f, 1000));
addStrategy("System Input Power Factor Phs A", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Input Power Factor Phs B", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Input Power Factor Phs C", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Input Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Apparent Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Apparent Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Input Apparent Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output RMS A-B", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS B-C", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS C-A", new SingleValueStrategy(480.0F, 5.0f, 1000));
addStrategy("System Output RMS A-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS B-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS C-N", new SingleValueStrategy(270.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output RMS Current Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Frequency", new SingleValueStrategy(60.0F, 2.0f, 1000));
addStrategy("System Output Power Factor Phs A", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Factor Phs B", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Factor Phs C", new SingleValueStrategy(93.0F, 5.0f, 1000));
addStrategy("System Output Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase A", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase B", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("System Output Apparent Power Phase C", new RampStrategy(10.0F, 5.0f, 1000));
addStrategy("Battery Time Remaining", new RampStrategy(480.0F, 1.0f, 1000));
addStrategy("DC Bus Voltage", new SingleValueStrategy(518.0F, 5.0f, 1000));
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks for state transition commands:
* 1. It reads the "ON/OFF Command By BMS" point. If it's 0, it transitions to StandbyState.
* 2. It reads the "Fault Code" point. If it's non-zero, it transitions to FailState,
* passing the corresponding alarm description.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<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, "Px State");
switch (static_cast<int>(State_Ctrl)) {
case 1:
return new StandbyState<ModbusIP>();
break;
case 3:
return new BatteryState<ModbusIP>();
break;
case 4:
return new BypassState<ModbusIP>();
break;
default:
break;
}
float rating = getPointValue(equipment, "Px Rating");
float load = getPointValue(equipment, "Px Load");
float real_load = (rating*1000.0f) * (load/100.f);
Strategy_Behavior* ramp_strat = nullptr;
//Input strategies
float In_Vab = getPointValue(equipment, "System Input RMS A-B");
ramp_strat = getStrategy("System Input RMS Current Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/In_Vab);
float In_Vbc = getPointValue(equipment, "System Input RMS B-C");
ramp_strat = getStrategy("System Input RMS Current Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/In_Vbc);
float In_Vca = getPointValue(equipment, "System Input RMS C-A");
ramp_strat = getStrategy("System Input RMS Current Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/In_Vca);
float In_Van = getPointValue(equipment, "System Input RMS A-N");
float In_Ia = getPointValue(equipment, "System Input RMS Current Phase A");
float In_PFa = getPointValue(equipment, "System Input Power Factor Phs A");
ramp_strat = getStrategy("System Input Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Van * In_Ia);
ramp_strat = getStrategy("System Input Apparent Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Van * In_Ia * (In_PFa/100.0f));
float In_Vbn = getPointValue(equipment, "System Input RMS B-N");
float In_Ib = getPointValue(equipment, "System Input RMS Current Phase B");
float In_PFb = getPointValue(equipment, "System Input Power Factor Phs B");
ramp_strat = getStrategy("System Input Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vbn * In_Ib);
ramp_strat = getStrategy("System Input Apparent Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vbn * In_Ib * (In_PFb/100.0f));
float In_Vcn = getPointValue(equipment, "System Input RMS C-N");
float In_Ic = getPointValue(equipment, "System Input RMS Current Phase C");
float In_PFc = getPointValue(equipment, "System Input Power Factor Phs C");
ramp_strat = getStrategy("System Input Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vcn * In_Ic);
ramp_strat = getStrategy("System Input Apparent Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(In_Vcn * In_Ic * (In_PFc/100.0f));
//Output strategies
float Out_Vab = getPointValue(equipment, "System Output RMS A-B");
ramp_strat = getStrategy("System Output RMS Current Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vab);
float Out_Vbc = getPointValue(equipment, "System Output RMS B-C");
ramp_strat = getStrategy("System Output RMS Current Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vbc);
float Out_Vca = getPointValue(equipment, "System Output RMS C-A");
ramp_strat = getStrategy("System Output RMS Current Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(real_load/Out_Vca);
float Out_Van = getPointValue(equipment, "System Output RMS A-N");
float Out_Ia = getPointValue(equipment, "System Output RMS Current Phase A");
float Out_PFa = getPointValue(equipment, "System Output Power Factor Phs A");
ramp_strat = getStrategy("System Output Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Van * Out_Ia);
ramp_strat = getStrategy("System Output Apparent Power Phase A");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Van * Out_Ia * (Out_PFa/100.0f));
float Out_Vbn = getPointValue(equipment, "System Output RMS B-N");
float Out_Ib = getPointValue(equipment, "System Output RMS Current Phase B");
float Out_PFb = getPointValue(equipment, "System Output Power Factor Phs B");
ramp_strat = getStrategy("System Output Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vbn * Out_Ib);
ramp_strat = getStrategy("System Output Apparent Power Phase B");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vbn * Out_Ib * (Out_PFb/100.0f));
float Out_Vcn = getPointValue(equipment, "System Output RMS C-N");
float Out_Ic = getPointValue(equipment, "System Output RMS Current Phase C");
float Out_PFc = getPointValue(equipment, "System Output Power Factor Phs C");
ramp_strat = getStrategy("System Output Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vcn * Out_Ic);
ramp_strat = getStrategy("System Output Apparent Power Phase C");
static_cast<RampStrategy*>(ramp_strat)->setTarget(Out_Vcn * Out_Ic * (Out_PFc/100.0f));
float Battery_time = getPointValue(equipment, "Battery Time Remaining");
float Bat_Percent = Battery_time /4.80f;
if (Bat_Percent > 98.0f){
setPointValue(equipment, "UPS Battery Status2", 0.0f);
}
if (Bat_Percent > 20.0f) {
setPointValue(equipment, "UPS Battery Status1", 2.0f);
setPointValue(equipment, "Battery Low", 0.0f);
}
if (Bat_Percent <= 20.0f && Bat_Percent >= 5.0f){
setPointValue(equipment, "UPS Battery Status1", 3.0f);
setPointValue(equipment, "Battery Low", 1.0f);
}
if (Bat_Percent < 5.0f){
setPointValue(equipment, "UPS Battery Status1", 4.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, "Bypass Input Voltage RMS A-B", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS B-C", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS C-A", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS A-N", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS B-N", 0.0f);
setPointValue(equipment, "Bypass Input Voltage RMS C-N", 0.0f);
setPointValue(equipment, "Bypass Input Frequency", 0.0f);
setPointValue(equipment, "Bypass Power Phase A", 0.0f);
setPointValue(equipment, "Bypass Power Phase B", 0.0f);
setPointValue(equipment, "Bypass Power Phase C", 0.0f);
setPointValue(equipment, "UPS Loading Status", 3.0f);
setPointValue(equipment, "UPS Battery Status2", 1.0f);
}
/**
* @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

@@ -19,6 +19,8 @@
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State_Battery.h"
#include "States/State_Bypass.h"
#include "States/State.h"
#include <vector>
#include <string>
@@ -57,6 +59,20 @@ 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, "Px State");
switch (static_cast<int>(State_Ctrl)) {
case 2:
return new RunningState<ModbusIP>();
break;
case 3:
return new BatteryState<ModbusIP>();
break;
case 4:
return new BypassState<ModbusIP>();
break;
default:
break;
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
@@ -72,6 +88,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, "UPS Loading Status", 2.0f);
}
/**

View File

@@ -0,0 +1,145 @@
/**
* @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 = "esrlok_portable"; /**< @brief The SSID of the WiFi network. */
const char *password = "m7g6eNMe?cy8S@z"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @defgroup ModbusMapConfig Modbus Map Configuration
* @brief Defines the Modbus register map and related parameters for the emulator.
* @{
*/
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{HR, 9, 0, "Px State"}, //1-standby, 2 Running (Normal - charging), 3 Battery, 4 Bypass
{HR, 10, 0, "Px Load"}, //% Internal Fault code from Modscan
{HR, 11, 0, "Px Rating"}, //kVA Internal Fault code from Modscan
{DI, 11, 0, "Output Overload"},
{DI, 20, 0, "Bypass Not Ready"},
{DI, 239, 0, "Internal Comms Failure"},
{DI, 244, 0, "System Shutdown-EPO"},
{DI, 245, 0, "Fuse Failure"},
{DI, 247, 0, "System Fan Failure"},
{DI, 249, 0, "System Output Off"},
{DI, 254, 0, "UPS Output on Bypass"},
{DI, 263, 0, "Battery Low"},
{IR_10x, 1, 0, "System Input RMS A-B"},
{IR_10x, 2, 0, "System Input RMS B-C"},
{IR_10x, 3, 0, "System Input RMS C-A"},
{IR_10x, 4, 0, "System Input RMS A-N"},
{IR_10x, 5, 0, "System Input RMS B-N"},
{IR_10x, 6, 0, "System Input RMS C-N"},
{IR_10x, 7, 0, "System Input RMS Current Phase A"},
{IR_10x, 8, 0, "System Input RMS Current Phase B"},
{IR_10x, 9, 0, "System Input RMS Current Phase C"},
{IR_10x, 10, 0, "System Input Frequency"},
{IR_10x, 11, 0, "System Input Power Factor Phs A"},
{IR_10x, 12, 0, "System Input Power Factor Phs B"},
{IR_10x, 13, 0, "System Input Power Factor Phs C"},
{IR_10x, 14, 0, "System Input Power Phase A"},
{IR_10x, 15, 0, "System Input Power Phase B"},
{IR_10x, 16, 0, "System Input Power Phase C"},
{IR_10x, 17, 0, "System Input Apparent Power Phs A"},
{IR_10x, 18, 0, "System Input Apparent Power Phs B"},
{IR_10x, 19, 0, "System Input Apparent Power Phs C"},
{IR_10x, 23, 0, "Bypass Input Voltage RMS A-B"},
{IR_10x, 24, 0, "Bypass Input Voltage RMS B-C"},
{IR_10x, 25, 0, "Bypass Input Voltage RMS C-A"},
{IR_10x, 26, 0, "Bypass Input Voltage RMS A-N"},
{IR_10x, 27, 0, "Bypass Input Voltage RMS B-N"},
{IR_10x, 28, 0, "Bypass Input Voltage RMS C-N"},
{IR_10x, 29, 0, "Bypass Input Frequency"},
{IR_10x, 30, 0, "Bypass Power Phase A"},
{IR_10x, 31, 0, "Bypass Power Phase B"},
{IR_10x, 32, 0, "Bypass Power Phase C"},
{IR_10x, 38, 0, "System Output RMS A-B"},
{IR_10x, 39, 0, "System Output RMS B-C"},
{IR_10x, 40, 0, "System Output RMS C-A"},
{IR_10x, 41, 0, "System Output RMS A-N"},
{IR_10x, 42, 0, "System Output RMS B-N"},
{IR_10x, 43, 0, "System Output RMS C-N"},
{IR_10x, 44, 0, "System Output RMS Current Phase A"},
{IR_10x, 45, 0, "System Output RMS Current Phase B"},
{IR_10x, 46, 0, "System Output RMS Current Phase C"},
{IR_10x, 50, 0, "System Output Frequency"},
{IR_10x, 51, 0, "System Output Power Factor Phs A"},
{IR_10x, 52, 0, "System Output Power Factor Phs B"},
{IR_10x, 53, 0, "System Output Power Factor Phs C"},
{IR_10x, 54, 0, "System Output Power Phase A"},
{IR_10x, 55, 0, "System Output Power Phase B"},
{IR_10x, 56, 0, "System Output Power Phase C"},
{IR_10x, 57, 0, "System Output Apparent Power Phs A"},
{IR_10x, 58, 0, "System Output Apparent Power Phs B"},
{IR_10x, 59, 0, "System Output Apparent Power Phs C"},
{IR_10x, 60, 0, "System Output Power"},
{IR_10x, 61, 0, "System Output Apparent Power"},
{IR, 164, 0, "UPS Loading Status"},
{IR_10x, 175, 0, "DC Bus Voltage"},
{IR, 180, 0, "Battery Time Remaining"},
{IR, 183, 0, "UPS Battery Status1"},
{IR, 184, 0, "UPS Battery Status2"},
};
//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);
}
}