# 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 `Equipment` class is our main "machine". * We have separate classes for each state: `State_Standby`, `State_Running`, `State_Fail`. * The `Equipment` object holds onto the *current* state object (e.g., an instance of `State_Running`). * The main `loop()` simply tells the current state object to `update()`. * All the logic for the running mode is contained entirely within `State_Running.cpp`. All the logic for standby is in `State_Standby.cpp`. * **Key Benefit:** To change how the machine behaves in "Running" mode, you only need to modify the `State_Running.cpp` file. You don't have to touch any other part of the system. To add a new "Maintenance" mode, you just create a new `State_Maintenance.cpp` file. This is much safer and easier than editing a giant `if/else` block. * **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 that `changeState()`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 `RAMP` block, a `PID` block, or a `SQUARE_WAVE_GENERATOR` block. 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: ```cpp // 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)); ``` * **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` (like `Modbus_Hreg` for 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`. * **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 the `Modbus_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 the `config.h` map. ## 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 the `Equipment` class, 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 the `Equipment` (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. 1. **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_map` array, defining its category (e.g., `HR_FLOAT`), address, initial value, and a unique description. 2. **To change how a point behaves in the "Running" state:** * Open `States/State_Running.cpp`. * In the `RunningState()` constructor, find or add an `addStrategy()` call for that point's description. * Assign it a new or different strategy (e.g., change `new RandomStrategy(...)` to `new SingleValueStrategy(...)`). 3. **To add a new operating mode (e.g., "Cleaning Cycle"):** * Create new files: `States/State_Cleaning.h` and `States/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.cpp` or `State_Standby.cpp`) to allow transitioning into your new `CleaningState`. ### Create a new Equipment type 1. **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. 2. **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 3. **Modify `platformio.ini` file** * 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'> ---