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.
-
Analogy: Think of a PLC program. Instead of having one massive ladder logic routine with dozens of branches checking
IF machine_is_running THEN... ELSE IF machine_is_in_standby THEN..., you create separate routines for each mode. -
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
-
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: