added York YVAA RTU

This commit is contained in:
RobertJDavis
2025-10-21 16:09:00 -07:00
parent 6bfc701302
commit ce71ec9267
9 changed files with 790 additions and 3 deletions

View File

@@ -11,10 +11,10 @@
[platformio]
default_envs = PDU_Maverick_Power_TCP ; Select here the name of the configuration you want to download
default_envs = CH_York_YVAA_RTU ; Select here the name of the configuration you want to download
[env]
upload_port = COM15
upload_port = COM9
[common_env_options]
framework = arduino
@@ -132,7 +132,6 @@ extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<EPMS/ATS/ATS_800_RPD>
[env:Susol_Smart_MCCB_TCP]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
@@ -195,3 +194,9 @@ board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_flags = -D USE_MODBUS_IP
build_src_filter = -<*> +<EPMS/UPS/UPS_Vertiv_APM2_TCP>
[env:CH_York_YVAA_RTU]
platform = espressif32
board = dfrobot_firebeetle2_esp32e
extends = common_env_options
build_src_filter = -<*> +<BMS/CHILLER/CH_York_YVAA_RTU>

View File

@@ -0,0 +1,43 @@
# CHILLER YORK YVAA 0428IOK46BAVTXX TCP
## Brief Introduction
*** NOTE! ***
This code has not been verified with Chiller and Chiller Manager PLC program.
It is a best-guess based on a preliminary review of Chiller PLC program, but
has yet to be fully vetted and local tested with PLC programs.
Chiller receives Temp SP and Enable from PLC (Modscan)
Alarms are also simulated via Modscan, though those signals will be internal to Chiller
Many hard IO points are simulated using Modscan.
Assumes all Modbus points are for monitoring only and go to Ignition - not sent to PLC
## List of Equipment
This cofiguration has been used for these models:
* **YVAA**: 10-14-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
Updates Alarms States. If any active alarms --> FailState
Updates Free Cooling Mode: Free Cooling Mode is activated using a coil, for simulation purposes only.
Modbus points are simulated, mostly with a SingleValue strategy for image verification in Ignition.
While in RunningState, the Supply Temp dynamically ramps to the Supply Temp SP sent from PLC (Modscan)
The CHW In and CHW Out temperature values also dynamically ramp to match the Return and Supply Temps.
### Standby State
* **Chiller Status**: set to 0
* **Operational Code**: set to 77
* **Chiller Start Command**: set to 0
### Running State
* **Chiller status**: set to 1
* **Supply Temperature**: **Ramp Strategy** ramps to Temp Setpoint from PLC (Modscan)
### Fail State
* **Chiller Status**: set to 0

View File

@@ -0,0 +1,85 @@
/**
* @file StateUtils.cpp
* @brief Implementation of the StateUtils class.
* @author Robert J. Davis
* @date 2025-10-14
*
* 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 Updates Alarms states
*
* This function will update the Alarm status DI bits according to the Alarm Commands from Coils (Modscan)
* The appropriate Fault Code will also be set to 56 (Condenser Fan VSD Warning)
*
* This is a function used in the update() of the Standby, Running, and Fail States.
*
*/
void updateAlarms(Equipment<ModbusRTU>* equipment){
Modbus_Point<ModbusRTU>* Sys1FanAlarmCommand = equipment->getModbus_Point("Sys 1 Fan Fault ON");
Modbus_Point<ModbusRTU>* Sys2FanAlarmCommand = equipment->getModbus_Point("Sys 2 Fan Fault ON");
Modbus_Point<ModbusRTU>* Sys1FanAlarm = equipment->getModbus_Point("Sys 1 Fan Fault Alarm");
Modbus_Point<ModbusRTU>* Sys2FanAlarm = equipment->getModbus_Point("Sys 2 Fan Fault Alarm");
if (Sys1FanAlarmCommand) {
Sys1FanAlarm->setValue(Sys1FanAlarmCommand->getValue());
if (Sys1FanAlarmCommand->getValue() == 1){
equipment->setModbus_Point("Sys 1 Fault Code", 56);
}
else equipment->setModbus_Point("Sys 1 Fault Code", 0);
}
if (Sys2FanAlarmCommand) {
Sys2FanAlarm->setValue(Sys2FanAlarmCommand->getValue());
if (Sys2FanAlarmCommand->getValue() == 1){
equipment->setModbus_Point("Sys 2 Fault Code", 56);
}
else equipment->setModbus_Point("Sys 2 Fault Code", 0);
}
}
/**
* @brief Updates Free Cooling Mode
*
* This function will update the Free Cooling Mode and Valve based on Free Cooling Command
* received from Modscan. This is for simulation purposes only - in practice, the Chiller
* will transition to Free Cooling Mode based on its own internal logic.
*
* For ease of testing, this is a function used in the update() of the Standby, Running, and Fail States.
*
*/
void updateFreeCooling(Equipment<ModbusRTU>* equipment){
Modbus_Point<ModbusRTU>* FreeCoolingCommand = equipment->getModbus_Point("Free Cooling Mode ON");
Modbus_Point<ModbusRTU>* FreeCoolingMode = equipment->getModbus_Point("Free Cooling Mode");
Modbus_Point<ModbusRTU>* FreeCoolingValve = equipment->getModbus_Point("Free Cooling Valve");
if (FreeCoolingCommand->getValue() == 1) {
FreeCoolingMode->setValue(1);
FreeCoolingValve->setValue(1);
}
else {
FreeCoolingMode->setValue(0);
FreeCoolingValve->setValue(0);
}
}

View File

@@ -0,0 +1,43 @@
/**
* @file config.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 Alarms states
* @param equipment Pointer to the Equipment instance.
* @return void
*/
void updateAlarms(Equipment<ModbusRTU>* equipment);
/**
* @brief Updates Free Cooling Mode
* @param equipment Pointer to the Equipment instance.
* @return void
*/
void updateFreeCooling(Equipment<ModbusRTU>* equipment);

View File

@@ -0,0 +1,107 @@
/**
* @file State_Fail.cpp
* @brief Implementation of the FailState class.
* @author Robert J. Davis
* @date 2025-10-14
*
* This file contains the implementation for the FailState, which defines
* the behavior of the equipment when it has entered a fault condition.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_PID.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "StateUtils.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new FailState object.
*
* This constructor receives a list of alarm descriptions and creates strategies
* to set the Compressor and Fan kW to 0.
*
* @param activeAlarms A vector of strings, where each string is the
* description of a Modbus point to be set as an active alarm.
*/
template<>
FailState<ModbusRTU>::FailState(const std::vector<std::string>& activeAlarms) {
addStrategy("Sys 1 Compressor Pct FLA", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 2 Compressor Pct FLA", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 1 Fan KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 2 Fan KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 1 Compressor KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 2 Compressor KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
}
/**
* @brief Executes the fail state's logic for one update cycle.
*
* This method first updates all alarms states and Free Cooling Mode (for ease of testing).
* If all alarms have been cleared --> StandbyState.
* If alarms are still active, ensures the Chiller Start Command remains at 0.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* FailState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// Update alarms states, Free Cooling mode, Freeze Protection Mode
updateAlarms(equipment);
updateFreeCooling(equipment);
const std::vector<std::string> alarmDescriptions = {
"Sys 1 Alarm", "Sys 2 Alarm", "Sys 1 Fan Fault Alarm", "Sys 2 Fan Fault Alarm",
};
// If no alarms active --> send to StandbyState()
bool alarms_active = false;
for (const auto& desc : alarmDescriptions) {
Modbus_Point<ModbusRTU>* point = equipment->getModbus_Point(desc);
if (point->getValue() == 1) {
alarms_active = true;
}
}
if (!alarms_active) return new StandbyState<ModbusRTU>();
setPointValue(equipment, "Chiller Start Command", 0);
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the fail state.
* Sets the Chiller Status to off, and updates the Operational Code for Systems to 77 (Not Running)
* The Chiller Start Command is also set to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Fail State...");
setPointValue(equipment, "Chiller Start Command", 0);
setPointValue(equipment, "Chiller Status", 0);
setPointValue(equipment, "Sys 1 Operational Code", 77);
setPointValue(equipment, "Sys 2 Operational Code", 77);
}
/**
* @brief Logic to execute once when exiting the fail state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void FailState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Fail State...");
}

View File

@@ -0,0 +1,171 @@
/**
* @file State_Running.cpp
* @brief Implementation of the RunningState class.
* @author Robert J Davis
* @date 2025-10-12
*
* This file contains the implementation for the RunningState, which defines
* the behavior of the equipment when it is actively running.
*/
#include "ModbusPoints/Modbus_Point.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "Equipment/Equipment.h"
#include "Strategies/Strategy_Ramp.h"
#include "Strategies/Strategy_Random.h"
#include "Strategies/Strategy_Saw.h"
#include "Strategies/Strategy_SingleValue.h"
#include "Strategies/Strategy_Square.h"
#include "Strategies/Strategy_PID.h"
#include "Strategies/Strategy_Totalizer.h"
#include "States/State_Standby.h"
#include "States/State_Running.h"
#include "States/State_Fail.h"
#include "States/State.h"
#include "StateUtils.h"
#include <vector>
#include <string>
#if defined(USE_MODBUS_IP)
#include <ModbusIP_ESP8266.h>
#else
#include <ModbusRTU.h>
#endif
/**
* @brief Constructs a new RunningState object.
*
* This constructor initializes behavior strategies active during the running
* state including various analog values, number of starts for each System and totalizers
* for the run-hours of each System.
*/
template<>
RunningState<ModbusRTU>::RunningState() {
addStrategy("Supply Temp", new RampStrategy(67.0f, 1.0f, 1000));
addStrategy("Return Temp", new SawStrategy(79.0f, 83.0f, 1.0f, 1000));
addStrategy("Ambient Temp", new SingleValueStrategy(100.0f, 1.0f, 1000));
addStrategy("System CHW Out", new RampStrategy(67.0f, 1.0f, 1000));
addStrategy("System CHW In", new RampStrategy(81.0f, 1.0f, 1000));
addStrategy("Sys 1 Condenser Temp", new SingleValueStrategy(125.0f, 5.0f, 1000));
addStrategy("Sys 2 Condenser Temp", new SingleValueStrategy(125.0f, 5.0f, 1000));
addStrategy("Sys 1 Oil Pressure", new SingleValueStrategy(450.0f, 5.0f, 1000));
addStrategy("Sys 2 Oil Pressure", new SingleValueStrategy(450.0f, 5.0f, 1000));
addStrategy("Sys 1 Suction Pressure", new SingleValueStrategy(70.0f, 2.0f, 1000));
addStrategy("Sys 2 Suction Pressure", new SingleValueStrategy(70.0f, 2.0f, 1000));
addStrategy("Sys 1 Discharge Pressure", new SingleValueStrategy(375.0f, 4.0f, 1000));
addStrategy("Sys 2 Discharge Pressure", new SingleValueStrategy(375.0f, 4.0f, 1000));
addStrategy("Sys 1 Compressor Pct FLA", new SingleValueStrategy(93.0f, 2.0f, 1000));
addStrategy("Sys 2 Compressor Pct FLA", new SingleValueStrategy(93.0f, 2.0f, 1000));
addStrategy("Sys 1 Run Hours", new TotalizerStrategy(1000));
addStrategy("Sys 2 Run Hours", new TotalizerStrategy(1000));
addStrategy("Local Leaving Temp Setpoint", new SingleValueStrategy(70.0f, 0.0f, 1000));
addStrategy("Sys 1 Fan KW", new SingleValueStrategy(35.0f, 2.0f, 1000));
addStrategy("Sys 2 Fan KW", new SingleValueStrategy(23.0f, 2.0f, 1000));
addStrategy("Sys 1 Compressor KW", new SingleValueStrategy(304.0f, 5.0f, 1000));
addStrategy("Sys 2 Compressor KW", new SingleValueStrategy(198.0f, 5.0f, 1000));
}
/**
* @brief Executes the running state's logic for one update cycle.
*
* This method first checks if there are any active alarms --> FailState.
* Also checks for Free Cooling Mode (for ease of testing)
* If no alarms are active, checks for "Chiller Start Command" = 0 (Modscan, but will be from PLC)
* for a command to transition to the Standby state. If no transition is requested, it updates the
* rampStrategy targetValues for the Supply Temp, CHW In, CHW Out analog values and applies the
* strategies defined for the running state.
*
* If no transition occurs, it applies the strategies defined for the running state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// Update alarms states, Free Cooling mode, Freeze Protection Mode
updateAlarms(equipment);
updateFreeCooling(equipment);
std::vector<std::string> activeAlarmsDescriptions = {};
const std::vector<std::string> alarmDescriptions = {
"Sys 1 Alarm", "Sys 2 Alarm", "Sys 1 Fan Fault Alarm", "Sys 2 Fan Fault Alarm",
};
// Loop through alarms, create array of active alarms and send to FailState if any alarms are active
bool alarms_active = false;
for (const auto& desc : alarmDescriptions) {
Modbus_Point<ModbusRTU>* point = equipment->getModbus_Point(desc);
if (point->getValue() == 1) {
activeAlarmsDescriptions.push_back(desc);
alarms_active = true;
}
}
if (alarms_active) return new FailState<ModbusRTU>(activeAlarmsDescriptions);
// If no alarms active and Start Command = 0--> send to StandbyState()
int Chiller_Enable = getPointValue(equipment, "Chiller Start Command"); // Modscan COIL 1
if (Chiller_Enable == 0){
return new StandbyState<ModbusRTU>();
}
// Set the Supply Temp ramp target value equal to the Chiller Temp Setpoint
// Ramp CHW In Temp to Return Temp and CHW Out Temp to Supply Temp
float BMS_Temp_Setpoint = getPointValue(equipment, "Chiller Temp Setpoint");
float supplyTemp = getPointValue(equipment, "Supply Temp");
float returnTemp = getPointValue(equipment, "Return Temp");
Strategy_Behavior* Supply_Temp_strat = getStrategy("Supply Temp");
Strategy_Behavior* CHW_Out_strat = getStrategy("System CHW Out");
Strategy_Behavior* CHW_In_strat = getStrategy("System CHW In");
if (Supply_Temp_strat){
static_cast<RampStrategy*>(Supply_Temp_strat)->setTarget(BMS_Temp_Setpoint);
static_cast<RampStrategy*>(CHW_Out_strat)->setTarget(supplyTemp);
static_cast<RampStrategy*>(CHW_In_strat)->setTarget(returnTemp);
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the running state.
*
* Sets the "Chiller Status" point to indicate the unit is running.
* Update System Operational Code to 78 (Running).
* Increment a counter for number of starts for each System.
* We are assuming when the Chiller is commanded to run that both Systems will activate.
* Not enough information in Vendor SOO to add details for running systems independently,
* switching to Free-Cooling Mode, etc.
*
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Running State...");
setPointValue(equipment, "Chiller Status", 1);
setPointValue(equipment, "Sys 1 Operational Code", 78);
setPointValue(equipment, "Sys 2 Operational Code", 78);
// Add one to the System 1 and 2 Starts counter
int Sys1_num_starts = getPointValue(equipment, "Sys 1 Starts");
int Sys2_num_starts = getPointValue(equipment, "Sys 2 Starts");
Sys1_num_starts++;
Sys2_num_starts++;
setPointValue(equipment, "Sys 1 Starts", Sys1_num_starts);
setPointValue(equipment, "Sys 2 Starts", Sys2_num_starts);
}
/**
* @brief Logic to execute once when exiting the running state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void RunningState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Running State...");
}

View File

@@ -0,0 +1,136 @@
/**
* @file State_Standby.cpp
* @brief Implementation of the StandbyState class.
* @author Robert J Davis
* @date 2025-10-13
*
* 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 sets a stable value for
* the analog readings and takes Fans and Compressors to 0 kW.
*/
template<>
StandbyState<ModbusRTU>::StandbyState() {
addStrategy("Supply Temp", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("Return Temp", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("Ambient Temp", new SingleValueStrategy(100.0f, 1.0f, 1000));
addStrategy("System CHW Out", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("System CHW In", new SingleValueStrategy(80.0f, 1.0f, 1000));
addStrategy("Sys 1 Condenser Temp", new SingleValueStrategy(124.0f, 1.0f, 1000));
addStrategy("Sys 2 Condenser Temp", new SingleValueStrategy(124.0f, 1.0f, 1000));
addStrategy("Sys 1 Oil Pressure", new SingleValueStrategy(420.0f, 1.0f, 1000));
addStrategy("Sys 2 Oil Pressure", new SingleValueStrategy(420.0f, 1.0f, 1000));
addStrategy("Sys 1 Suction Pressure", new SingleValueStrategy(70.0f, 1.0f, 1000));
addStrategy("Sys 2 Suction Pressure", new SingleValueStrategy(70.0f, 1.0f, 1000));
addStrategy("Sys 1 Discharge Pressure", new SingleValueStrategy(70.0f, 1.0f, 1000));
addStrategy("Sys 2 Discharge Pressure", new SingleValueStrategy(70.0f, 1.0f, 1000));
addStrategy("Sys 1 Compressor Pct FLA", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 2 Compressor Pct FLA", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Local Leaving Temp Setpoint", new SingleValueStrategy(70.0f, 0.0f, 1000));
addStrategy("Sys 1 Fan KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 2 Fan KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 1 Compressor KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
addStrategy("Sys 2 Compressor KW", new SingleValueStrategy(0.0f, 0.0f, 1000));
}
/**
* @brief Executes the standby state's logic for one update cycle.
*
* This method first checks if there are any active alarms --> FailState.
* Also checks for Free Cooling Mode (for ease of testing)
* If no alarms are active, checks for "Chiller Start Command" (Modscan, but will be from PLC)
* for a command to transition to the Running state. If no transition is requested, it applies
* the strategies defined for the standby state.
*
* @param equipment Pointer to the Equipment instance.
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
*/
template<>
State<ModbusRTU>* StandbyState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
// Update alarms states, Free Cooling mode, Freeze Protection Mode
updateAlarms(equipment);
updateFreeCooling(equipment);
std::vector<std::string> activeAlarmsDescriptions = {};
const std::vector<std::string> alarmDescriptions = {
"Sys 1 Alarm", "Sys 2 Alarm", "Sys 1 Fan Fault Alarm", "Sys 2 Fan Fault Alarm",
};
// Loop through alarms, create array of active alarms and send to FailState if any alarms are active
bool alarms_active = false;
for (const auto& desc : alarmDescriptions) {
Modbus_Point<ModbusRTU>* point = equipment->getModbus_Point(desc);
if (point->getValue() == 1) {
activeAlarmsDescriptions.push_back(desc);
alarms_active = true;
}
}
if (alarms_active) return new FailState<ModbusRTU>(activeAlarmsDescriptions);
// If no alarms active and Start Command = 1--> send to RunningState()
int Chiller_Enable = getPointValue(equipment, "Chiller Start Command"); // Modscan COIL 1
if (Chiller_Enable == 1){
return new RunningState<ModbusRTU>();
}
// Apply any strategies defined for the standby state
_applyStrategies(equipment);
return nullptr;
}
/**
* @brief Logic to execute once when entering the standby state.
* Sets the "Chiller Status" point to indicate the unit is not running.
* Updates Operational Codes for Sys 1 and Sys 2
* Ensure Chiller Start Command is reset to 0.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
// Logic to run when the equipment enters this state
Serial.println("Enter Standby State...");
setPointValue(equipment, "Chiller Start Command", 0);
setPointValue(equipment, "Chiller Status", 0);
setPointValue(equipment, "Sys 1 Operational Code", 77);
setPointValue(equipment, "Sys 2 Operational Code", 77);
}
/**
* @brief Logic to execute once when exiting the standby state.
* @param equipment Pointer to the Equipment instance.
*/
template<>
void StandbyState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
// Cleanup logic to run when the equipment leaves this state
Serial.println("Exit Standby State...");
}

View File

@@ -0,0 +1,119 @@
/**
* @file config.h
* @brief Main configuration file for the York Chiller (RTU) emulator.
* @author Robert J Davis
* @date 2025-10-12
*
* This file contains important configurations for the Modbus RTU communication
* and the specific register map for the emulated device.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <ModbusRTU.h>
#include "core.h"
#include "Equipment/Equipment.h"
#if defined(USE_MODBUS_IP)
/**
* @defgroup ModbusTCPConfig Modbus IP Configuration
* @brief Parameters for Modbus TCP communication.
* @{
*/
#include <ModbusIP_ESP8266.h>
const char *ssid = "wifi_name"; /**< @brief The SSID of the WiFi network. */
const char *password = "wifi_password"; /**< @brief The password for the WiFi network. */
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
ModbusIP mb;
#else
/**
* @defgroup ModbusRTUConfig Modbus RTU Configuration
* @brief Parameters for serial Modbus RTU communication.
* @{
*/
#include <ModbusRTU.h>
const int BAUDRATE = 19200; /**< @brief The serial communication speed in bits per second. */
const int RX_PIN = 17; /**< @brief The GPIO pin used for receiving data (RX). */
const int TX_PIN = 16; /**< @brief The GPIO pin used for transmitting data (TX). */
const int RST_PIN = 4; /**< @brief The GPIO pin connected to the RS485 driver's DE/RE pins for direction control. */
const int MODBUS_ID = 1; /**< @brief The unique slave ID for this device on the Modbus bus. */
/** @} */
/** @brief Global instance of the Modbus RTU server. */
ModbusRTU mb;
#endif
/**
* @brief The Modbus map for the Equipment device.
* This array defines all the Modbus points available on the emulated device.
* The `description` field is crucial as it's used to look up points within the application logic.
*/
modbusMap mb_map[] =
{
{COIL, 0, 0, "Chiller Start Command"}, // Use in Modscan - Hard IO in SCP, Used for Arduino simulation only
{COIL, 1, 0, "Sys 1 Alarm"}, // Use in Modscan - Hard IO in SCP, Used for Arduino simulation only
{COIL, 2, 0, "Sys 2 Alarm"}, // Use in Modscan - Hard IO in SCP, Used for Arduino simulation only
{COIL, 3, 0, "Sys 1 Fan Fault ON"}, // Use in Modscan - Used for Arduino simulation only
{COIL, 4, 0, "Sys 2 Fan Fault ON"}, // Use in Modscan - Used for Arduino simulation only
{COIL, 5, 0, "Free Cooling Mode ON"}, // Use in Modscan - Used for Arduino simulation only
{DI, 0, 0, "Sys 1 Fan Fault Alarm"},
{DI, 1, 0, "Sys 2 Fan Fault Alarm"},
{HR, 0, 0, "Chiller Status"}, // Use in Modscan - Hard IO in SCP, Used for Arduino simulation only
{HR, 1, 0, "Chiller Temp Setpoint"}, // Use in Modscan - Hard IO in SCP, Used for Arduino simulation only
{HR, 2, 0, "Supply Temp"}, // Use in Modscan - Hard IO in SCP (PICS?), Used for Arduino simulation only
{HR, 3, 0, "Return Temp"}, // Use in Modscan - Hard IO in SCP (PICS?), Used for Arduino simulation only
{HR, 4, 70, "System CHW Out"},
{HR, 5, 70, "System CHW In"},
{HR, 7, 0, "Sys 1 Condenser Temp"},
{HR, 9, 0, "Ambient Temp"},
{HR, 11, 0, "Sys 1 Oil Pressure"},
{HR, 12, 0, "Sys 1 Suction Pressure"},
{HR, 13, 0, "Sys 1 Discharge Pressure"},
{HR, 14, 0, "Sys 1 Compressor Pct FLA"},
{HR, 15, 0, "Sys 1 Run Hours"},
{HR, 16, 0, "Sys 1 Starts"},
{HR, 20, 0, "Sys 2 Oil Pressure"},
{HR, 21, 0, "Sys 2 Suction Pressure"},
{HR, 22, 0, "Sys 2 Discharge Pressure"},
{HR, 23, 0, "Sys 2 Compressor Pct FLA"},
{HR, 24, 0, "Sys 2 Run Hours"},
{HR, 25, 0, "Sys 2 Starts"},
{HR, 29, 77, "Sys 1 Operational Code"},
{HR, 30, 0, "Sys 1 Fault Code"},
{HR, 31, 77, "Sys 2 Operational Code"},
{HR, 32, 0, "Sys 2 Fault Code"},
{HR, 39, 0, "Local Leaving Temp Setpoint"},
{HR, 40, 0, "Sys 1 Fan KW"},
{HR, 41, 0, "Sys 1 Compressor KW"},
{HR, 42, 0, "Sys 2 Fan KW"},
{HR, 43, 0, "Sys 2 Compressor KW"},
{HR, 49, 0, "Sys 2 Condenser Temp"},
{HR, 50, 0, "Free Cooling Mode"},
{HR, 51, 0, "Free Cooling Valve"},
};
//Size of modbus map used in FOR cycles, automatically calculated.
/**
* @brief The total number of entries in the `mb_map` array.
* This is calculated at compile time and used for iterating over the map.
*/
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
/**
* @brief The main loop update interval in milliseconds.
*/
int interval = 250;
#endif // CONFIG_H

View File

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