Documentation updated
This commit is contained in:
113
README.md
Normal file
113
README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# 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 `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 `ModbusPoint` (like `ModbusHreg` for a Holding Register).
|
||||
* If a point needs to be treated as a float, we "decorate" or "wrap" it with a `ModbusFloatDecorator`. 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 `ModbusScaleDecorator`.
|
||||
|
||||
* **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 `ModbusPointFactory`.
|
||||
|
||||
* **Files to see:**
|
||||
* `Categories/ModbusPoint.h`: The base for all points.
|
||||
* `Categories/ModbusPointDecorator.h`: The base "wrapper" class.
|
||||
* `Categories/ModbusFloatDecorator.h`: A specific wrapper that adds floating-point logic.
|
||||
* `Categories/ModbusPointFactory.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.
|
||||
* `/Categories`: Contains the classes for different types of Modbus points (`ModbusCoil`, `ModbusHreg`) and the Decorators (`ModbusFloatDecorator`).
|
||||
* `/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
|
||||
|
||||
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`.
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user