CDR EPMS devices update, ATS, Brakers, PDU, UPS, tested
Industrial Equipment Emulator
This project is an Arduino-based emulator for an industrial equipment. It simulates the behavior of a real-world device (like a Computer Room Air Handler - CRAH) and communicates over Wi-Fi using the Modbus IP protocol.
The primary goal is to provide a flexible and extensible virtual device for testing, development, and training purposes without needing physical hardware.
Core Concepts for Automation Professionals
This software is built using a few key Object-Oriented Programming (OOP) concepts that make it powerful and easy to modify. If you think in terms of control systems, these concepts will feel very familiar.
1. The State Pattern: "What is the machine's current operating mode?"
In industrial automation, a machine has different operating modes: Standby, Running, Alarm/Fault, Manual Override, etc. The machine behaves differently in each mode.
The State Pattern organizes the code to mirror these real-world machine modes.
-
How it works here:
- The
Equipmentclass is our main "machine". - We have separate classes for each state:
State_Standby,State_Running,State_Fail. - The
Equipmentobject holds onto the current state object (e.g., an instance ofState_Running). - The main
loop()simply tells the current state object toupdate(). - All the logic for the running mode is contained entirely within
State_Running.cpp. All the logic for standby is inState_Standby.cpp.
- The
-
Key Benefit: To change how the machine behaves in "Running" mode, you only need to modify the
State_Running.cppfile. You don't have to touch any other part of the system. To add a new "Maintenance" mode, you just create a newState_Maintenance.cppfile. This is much safer and easier than editing a giantif/elseblock. -
Files to see:
States/State.h: The "template" for all state classes.States/State_Running.cpp: Defines all behavior when the unit is running.States/State_Standby.cpp: Defines all behavior when the unit is in standby.Equipment/Equipment.cpp: The main machine thatchangeState()s between different modes.
2. The Strategy Pattern: "How should this specific value behave?"
Within a single operating mode (like "Running"), different components might have different behaviors. For example, one fan's speed might be constant, another might ramp up and down, and a sensor reading might fluctuate randomly to simulate real-world conditions.
The Strategy Pattern lets us define these individual behaviors as interchangeable "algorithms" or "strategies".
-
Analogy: Think of a function block in a PLC. You might have a
RAMPblock, aPIDblock, or aSQUARE_WAVE_GENERATORblock. The Strategy Pattern lets us create these as software objects. We can then "assign" a behavior strategy to a specific Modbus point. -
How it works here:
- We have a family of "Strategy" classes:
RampStrategy,SawStrategy,RandomStrategy,SquareStrategy, etc. - Inside a state file like
State_Running.cpp, we assign these strategies to specific Modbus points. For example:// From State_Running.cpp // Assign a ramp behavior to Fan #5 addStrategy("Speed EC Fan #5", new RampStrategy(100.0f, 1.5f, 2000)); // Assign a random behavior to Fan #4 addStrategy("Speed EC Fan #4", new RandomStrategy(1000)); // Assign a sawtooth wave behavior to Fan #3 addStrategy("Speed EC Fan #3", new SawStrategy(50.0f, 100.0f, 5.0f, 750));
- We have a family of "Strategy" classes:
-
Key Benefit: This makes the emulator incredibly dynamic. You can easily change the behavior of any point just by swapping out its strategy. Want Fan #4 to have a square wave pattern instead of random? Just change one line in
State_Running.cpp. You don't have to rewrite any core logic. -
Files to see:
Strategies/Strategy_Behavior.h: The "template" for all behavior strategies.Strategies/Strategy_Ramp.h,Strategies/Strategy_Saw.h, etc.: The specific, reusable behavior algorithms.States/State_Running.cpp: Where strategies are assigned to Modbus points for that state.
3. The Decorator Pattern: "How do we handle special data types?"
Modbus registers are fundamentally just 16-bit integers. However, in the real world, we use these integers to represent many different data types: booleans (coils), scaled integers (e.g., value * 10), 32-bit long integers, and 32-bit floating-point numbers.
The Decorator Pattern lets us "wrap" a basic Modbus point to add this extra functionality for handling data types without creating a whole new class for every possible combination.
-
Analogy: Think of a basic 4-20mA analog input card. That's your base object. Now, you add a "scaling block" in your PLC to convert the raw 4-20mA signal into a temperature in Celsius. That scaling block is a "Decorator". It doesn't change the input card, it just wraps its output to make it more useful.
-
How it works here:
- We start with a basic
Modbus_Point(likeModbus_Hregfor a Holding Register). - If a point needs to be treated as a float, we "decorate" or "wrap" it with a
Modbus_FloatDecorator. This decorator knows how to take two 16-bit registers and combine them into a single 32-bit float value, and vice-versa. - If a point needs to be scaled, we can wrap it with a
Modbus_ScaleDecorator.
- We start with a basic
-
Key Benefit: This keeps our code clean and avoids an explosion of classes. We don't need
ModbusFloatHoldingRegister,ModbusScaledHoldingRegister,ModbusLongInputRegister, etc. We have our basic point types (Coil,Hreg,Ireg) and we simply "decorate" them with the data handling logic they need. This is all handled automatically by theModbus_PointFactory. -
Files to see:
Modbus_Points/Modbus_Point.h: The base for all points.Modbus_Points/Modbus_PointDecorator.h: The base "wrapper" class.Modbus_Points/Modbus_FloatDecorator.h: A specific wrapper that adds floating-point logic.Modbus_Points/Modbus_PointFactory.cpp: The factory that automatically creates and decorates points based on theconfig.hmap.
Project Structure
BaseEmulator.ino: The main entry point of the Arduino program. It handles Wi-Fi setup, initializes the Modbus server, and runs the main loop.config.h: The central configuration file. This is where you define all the Modbus points for the device. You set the register type, address, and description here./Equipment: Contains theEquipmentclass, which represents the overall state machine./Modbus_Points: Contains the classes for different types of Modbus points (Modbus_Coil,Modbus_Hreg) and the Decorators (Modbus_FloatDecorator)./States: Contains the different operating modes for theEquipment(e.g.,State_Running)./Strategies: Contains the reusable behavior algorithms for Modbus points (e.g.,Strategy_Ramp).
How to Modify or Extend the Emulator
Use Equipment library
Look for the equipment type you want to use in the src folder, equipments are organized in BMS or EPMS type, there are subfolders for specify types.
-
To add or change a Modbus point:
- Open
config.h. - Set Wifi parameters to communicate to the network.
- Add a new line to the
mb_maparray, defining its category (e.g.,HR_FLOAT), address, initial value, and a unique description.
- Open
-
To change how a point behaves in the "Running" state:
- Open
States/State_Running.cpp. - In the
RunningState()constructor, find or add anaddStrategy()call for that point's description. - Assign it a new or different strategy (e.g., change
new RandomStrategy(...)tonew SingleValueStrategy(...)).
- Open
-
To add a new operating mode (e.g., "Cleaning Cycle"):
- Create new files:
States/State_Cleaning.handStates/State_Cleaning.cpp. - Implement the logic for that mode, including adding strategies for how points should behave.
- Update the state-switching logic (e.g., in
State_Running.cpporState_Standby.cpp) to allow transitioning into your newCleaningState.
- Create new files:
Create a new Equipment type
-
Copy the base
- Copy one of the base folders in
src, depending on the communication you need RTU or TCP, both configurations works with Firebeetle 2 ESP32.
- Copy one of the base folders in
-
Save it into the correct folder
- Save that folder into the correct type location.
- Use a standar name, using this stucture Type_Manufacturer_Series_Protocol Example: VFD_ABB_ACH580_RTU
-
Modify
platformio.inifile- Copy a base configuration depending it's RTU or TCP
- Paste it within the same file
- Change the name of the configuration, preferrably using the same folder name
- Add the folder to the configuration +<`folder_name'>