firmware-base/docs/modbus-rtu.md

301 lines
15 KiB
Markdown

# Modbus RTU Client Subsystem Documentation
## Overview
This document describes the classes, structures, and enums involved in the Modbus RTU Client functionality within the firmware. This subsystem is primarily managed by the `ModbusRTU` class and is responsible for communicating with downstream Modbus RTU slave devices over a serial (RS485) connection. It often works in conjunction with the Modbus TCP server (`ModbusManager`) to act as a TCP-to-RTU gateway.
The core tasks include queuing read/write operations, managing communication timing, handling retries and errors, caching slave data, and providing an interface to interact with the RTU devices.
## Key Classes
### `ModbusRTU`
* **Header:** `src/ModbusRTU.h`
* **Source:** `src/ModbusRTU.cpp`
* **Purpose:** The central class managing Modbus RTU client communication. It uses the `eModbus` library's `ModbusClientRTU` internally.
* **Responsibilities:**
* Initializes the RTU client and serial port.
* Manages a queue (`operationQueue`) of pending `ModbusOperation` requests.
* Handles the execution of operations, respecting timing intervals (`minOperationInterval`).
* Processes responses and errors from the `eModbus` library via callbacks (`onDataReceived`, `onErrorReceived`).
* Maintains a cache (`slaveData`) of the last known values for registers and coils on each slave device using `SlaveData` and `ModbusValueEntry`.
* Provides public methods (`readCoil`, `writeRegister`, etc.) to queue operations.
* Manages an optional filter chain (`ModbusOperationFilter`) to preprocess outgoing operations (e.g., prevent duplicates, rate limit).
* Provides status information (error counts, success rates).
* Handles adaptive timing adjustments if `ENABLE_ADAPTIVE_TIMEOUT` is defined.
### `BaseModbusDevice`
* **Header:** `src/ModbusTypes.h`
* **Source:** `src/ModbusTypes.cpp`
* **Purpose:** Represents the state and basic interaction logic for a single external Modbus RTU slave device being managed by `ModbusRTU`.
* **Responsibilities:**
* Stores the slave ID (`deviceId`).
* Tracks the device's communication state (`E_DeviceState`: IDLE, RUNNING, ERROR).
* Manages communication timing (timeout, sync interval) and error counts for the specific device.
* Holds collections (`inputRegisters`, `outputRegisters`) of `RegisterState` objects representing the data points on the slave.
* Provides methods to add/manage `RegisterState` objects (`addInputRegister`, `addOutputRegister`).
* Provides methods to interact with the device's registers via the `ModbusRTU` manager (`initialize`, `syncFromDevice`, `writeOutputs`).
* Provides local accessors for register values (`getInputRegisterValue`, `getOutputRegisterValue`, `setOutputRegisterValue`).
* Includes helper methods for state management (`printState`, `reset`, `runTestSequence`).
### `ModbusDeviceState`
* **Header:** `src/ModbusDeviceState.h`
* **Source:** `src/ModbusDeviceState.cpp`
* **Purpose:** A derived class of `BaseModbusDevice`. Intended for application-specific implementations or specializations of Modbus RTU slave devices.
* **Responsibilities:**
* Currently, mainly overrides `setState` to add specific logging.
* Its constructor can be used to add specific default registers required by the application logic using the `addInputRegister`/`addOutputRegister` methods inherited from `BaseModbusDevice`.
### `RegisterState`
* **Header:** `src/ModbusTypes.h`
* **Source:** `src/ModbusTypes.cpp`
* **Purpose:** Represents a single data point (Input Register, Holding Register, Coil, or Discrete Input) on an external Modbus RTU slave device.
* **Responsibilities:**
* Stores the register type (`RegType`), Modbus address, current value, min/max values (for validation), and priority.
* Provides methods (`readFromDevice`, `writeToDevice`) to queue corresponding read/write operations via the `ModbusRTU` manager.
* Includes helper methods for boolean conversion (`getBoolValue`, `setBoolValue`) and printing state (`printState`).
## Supporting Structures
### `ModbusOperation`
* **Header:** `src/ModbusTypes.h`
* **Purpose:** Represents a single Modbus read or write request waiting to be executed or currently in progress. Stored in the `ModbusRTU`'s `operationQueue`.
* **Key Members:**
* `timestamp`: When the operation was created.
* `token`: Unique identifier used by `eModbus` library for asynchronous handling.
* `address`, `value`, `quantity`: Modbus request parameters.
* `slaveId`: Target slave device.
* `type`: The type of operation (`E_MB_OpType`).
* `status`: Current status (`E_MB_OpStatus`: PENDING, SUCCESS, FAILED, RETRYING).
* `retries`: Number of times execution has been attempted.
* `flags`: Bit flags indicating state (USED, HIGH_PRIORITY, IN_PROGRESS, BROADCAST, SYNCHRONIZED).
### `ModbusValueEntry`
* **Header:** `src/ModbusTypes.h`
* **Purpose:** Represents a cached value for a single register or coil address on a specific slave. Used within the `SlaveData` structure.
* **Key Members:**
* `address`: The Modbus address.
* `value`: The last known value.
* `lastUpdate`: Timestamp of the last update.
* `synchronized`: Flag indicating if the cached value is believed to be in sync with the device.
* `used`: Flag indicating if this entry is actively used.
### `SlaveData`
* **Header:** `src/ModbusTypes.h`
* **Purpose:** Holds the cached data for a single Modbus RTU slave device managed by `ModbusRTU`. An array of `SlaveData` is stored in `ModbusRTU`.
* **Key Members:**
* `coils`: Fixed-size array of `ModbusValueEntry` for coils.
* `registers`: Fixed-size array of `ModbusValueEntry` for registers.
* `coilCount`, `registerCount`: Number of used entries in the arrays.
## Operation Filtering
### `ModbusOperationFilter` (Base Class)
* **Header:** `src/ModbusTypes.h`
* **Purpose:** Abstract base class for filters that can be chained together to process `ModbusOperation` requests before they are queued for execution by `ModbusRTU`.
* **Key Methods:**
* `filter(op)`: Pure virtual method; derived classes implement logic here. Returns `true` to allow the operation, `false` to drop it.
* `process(op)`: Processes the operation through the current filter and the rest of the chain.
* `setNext(filter*)`, `getNext()`: Manage the filter chain links.
* `notifyOperationExecuted(op)`, `notifyOperationCompleted(op)`: Optional methods for stateful filters.
* `getType()`: Returns the filter type (`E_FilterType`).
### Derived Filter Classes
* **Header:** `src/ModbusTypes.h`
* **Source:** `src/ModbusTypes.cpp`
* **Classes:**
* `DuplicateOperationFilter`: Prevents queuing an operation if an identical one (same slave, type, address) is already pending in the `ModbusRTU` queue. Requires a pointer to the `ModbusRTU` instance.
* `RateLimitFilter`: Enforces a minimum time interval between consecutive operations passing through it.
* `PriorityFilter`: Currently a placeholder; intended to potentially adjust operation priority flags (though filtering itself always returns true).
* `OperationLifecycleFilter`: Drops operations that have exceeded `MAX_RETRIES` or have timed out (`OPERATION_TIMEOUT`).
## Key Enums
* **`E_MB_OpType`**: (`src/ModbusTypes.h`) Defines the type of Modbus operation (READ_COIL, WRITE_REGISTER, etc.).
* **`E_MB_OpStatus`**: (`src/ModbusTypes.h`) Defines the status of a queued `ModbusOperation` (PENDING, SUCCESS, FAILED, RETRYING).
* **`MB_Error`**: (`src/ModbusTypes.h`) Comprehensive error codes, combining standard Modbus exceptions, internal queue/operation errors, and `eModbus` communication errors.
* **`E_FilterType`**: (`src/ModbusTypes.h`) Identifies the type of a `ModbusOperationFilter`.
* **`E_DeviceState`**: (`src/ModbusTypes.h`) State of a `BaseModbusDevice` (IDLE, RUNNING, ERROR).
* **`RegType`**: (`src/ModbusTypes.h`) Type of a `RegisterState` (INPUT, HOLDING, COIL, DISCRETE_INPUT).
* **`E_InitState`**: (`src/ModbusRTU.h`) Internal state for the `ModbusRTU` class initialization process.
## Key Constants
* **`MAX_MODBUS_SLAVES`**: (`src/ModbusTypes.h`) Max number of RTU slaves the client can manage.
* **`MAX_ADDRESSES_PER_SLAVE`**: (`src/ModbusTypes.h`) Size of the `ModbusValueEntry` arrays within `SlaveData`.
* **`MAX_PENDING_OPERATIONS`**: (`src/ModbusTypes.h`) Size of the `ModbusRTU` operation queue.
* **`MAX_RETRIES`**: (`src/ModbusTypes.h`) Default max retries for a failing operation.
* **`OPERATION_TIMEOUT`**: (`src/ModbusTypes.h`) Default timeout for an operation before being considered failed/expired by `OperationLifecycleFilter`.
* **`BAUDRATE`**: (`src/ModbusTypes.h`) Default serial baud rate for RS485.
* **`PRIORITY_*` Defines**: (`src/ModbusTypes.h`) Constants used for setting `RegisterState` priorities.
* **`OP_FLAG_*` Defines**: (`src/ModbusTypes.h`) Bit flags used within `ModbusOperation.flags`.
## Callback Functions
* **`ResponseCallback`**: (`src/ModbusTypes.h`) typedef `void (*ResponseCallback)(uint8_t slaveId)`. Can be set on `ModbusRTU` to trigger custom logic when *any* response (success or error) is received for a slave.
* **`OnRegisterChangeCallback`**: (`src/ModbusTypes.h`) typedef `void (*OnRegisterChangeCallback)(const ModbusOperation& op, uint16_t oldValue, uint16_t newValue)`. Set on `ModbusRTU` to be notified when a successful read operation results in a changed value in the cache.
* **`OnWriteCallback`**: (`src/ModbusTypes.h`) typedef `void (*OnWriteCallback)(const ModbusOperation& op)`. Set on `ModbusRTU` to be notified when a write operation completes successfully.
* **`OnErrorCallback`**: (`src/ModbusTypes.h`) typedef `void (*OnErrorCallback)(const ModbusOperation& op, int errorCode, const char* errorMessage)`. Set on `ModbusRTU` to be notified when an operation fails (either timeout, Modbus exception, or internal error).
## Mermaid Diagrams
### Core Class Relationships
```mermaid
classDiagram
class ModbusRTU {
+MB_Error begin(HardwareSerial&, uint32_t)
+MB_Error process()
+MB_Error readCoil(uint8_t, uint16_t)
+MB_Error writeRegister(uint8_t, uint16_t, uint16_t)
+bool getCoilValue(uint8_t, uint16_t, bool&)
+bool getRegisterValue(uint8_t, uint16_t, uint16_t&)
+bool isCoilSynchronized(uint8_t, uint16_t)
+bool isRegisterSynchronized(uint8_t, uint16_t)
+bool hasPendingOperations(uint8_t)
+void addFilter(ModbusOperationFilter*)
-SlaveData slaveData[MAX_MODBUS_SLAVES]
-ModbusOperation operationQueue[MAX_PENDING_OPERATIONS]
-ModbusOperationFilter* firstFilter
-MB_Error queueOperation(ModbusOperation, bool)
-void onDataReceived(ModbusMessage, uint32_t)
-void onErrorReceived(Error, uint32_t)
-ModbusValueEntry* findCoilEntry(...)
-ModbusValueEntry* createCoilEntry(...)
-ModbusValueEntry* findRegisterEntry(...)
-ModbusValueEntry* createRegisterEntry(...)
}
class BaseModbusDevice {
+uint8_t deviceId
+E_DeviceState state
+unsigned long syncInterval
+bool addInputRegister(uint16_t, uint8_t)
+bool addOutputRegister(uint16_t, uint16_t, uint8_t)
+void updateState(ModbusRTU&)
+bool initialize(ModbusRTU&)
+void syncFromDevice(ModbusRTU&)
+void writeOutputs(ModbusRTU&)
+void printState(ModbusRTU&)
+void reset()
+uint16_t getInputRegisterValue(uint16_t)
+uint16_t getOutputRegisterValue(uint16_t)
+void setOutputRegisterValue(uint16_t, uint16_t)
#RegisterState* inputRegisters[]
#RegisterState* outputRegisters[]
#int inputRegCount
#int outputRegCount
}
class ModbusDeviceState {
+ModbusDeviceState(uint8_t)
+void setState(E_DeviceState) override
}
class RegisterState {
+RegType type
+uint16_t address
+uint16_t value
+uint8_t priority
+MB_Error readFromDevice(ModbusRTU&, uint8_t)
+MB_Error writeToDevice(ModbusRTU&, uint8_t)
+void printState(ModbusRTU&, uint8_t)
+bool getBoolValue()
}
class ModbusOperation {
+unsigned long timestamp
+uint32_t token
+uint16_t address
+uint16_t value
+uint16_t quantity
+uint8_t slaveId
+E_MB_OpType type
+E_MB_OpStatus status
+uint8_t flags
}
class SlaveData {
#ModbusValueEntry coils[]
#ModbusValueEntry registers[]
#uint8_t coilCount
#uint8_t registerCount
}
class ModbusValueEntry {
+unsigned long lastUpdate
+uint16_t address
+uint16_t value
+bool synchronized
+bool used
}
ModbusRTU o-- "0..MAX_MODBUS_SLAVES" SlaveData : contains cache
ModbusRTU o-- "0..MAX_PENDING_OPERATIONS" ModbusOperation : queues
ModbusRTU ..> ModbusOperationFilter : uses chain
BaseModbusDevice o-- "0..MAX_INPUT_REGISTERS" RegisterState : manages inputs
BaseModbusDevice o-- "0..MAX_OUTPUT_REGISTERS" RegisterState : manages outputs
BaseModbusDevice ..> ModbusRTU : interacts via
ModbusDeviceState --|> BaseModbusDevice : inherits from
SlaveData o-- "0..MAX_ADDRESSES_PER_SLAVE" ModbusValueEntry : caches coil values
SlaveData o-- "0..MAX_ADDRESSES_PER_SLAVE" ModbusValueEntry : caches register values
RegisterState ..> ModbusRTU : interacts via
```
### Filter Chain Relationships
```mermaid
classDiagram
class ModbusOperationFilter {
<<Abstract>>
+process(ModbusOperation) bool
+setNext(ModbusOperationFilter*)
+getType() E_FilterType
#ModbusOperationFilter* nextFilter
+$ filter(ModbusOperation) bool
+$ notifyOperationExecuted(ModbusOperation) void
+$ notifyOperationCompleted(ModbusOperation) void
}
class DuplicateOperationFilter {
+DuplicateOperationFilter(ModbusRTU*)
+filter(ModbusOperation) bool
-ModbusRTU* modbusRTU
}
class RateLimitFilter {
+RateLimitFilter(ulong)
+filter(ModbusOperation) bool
-ulong minInterval
-ulong lastOperationTime
}
class PriorityFilter {
+filter(ModbusOperation) bool
+adjustPriority(ModbusOperation&) bool
}
class OperationLifecycleFilter {
+OperationLifecycleFilter(ulong, uint8_t)
+filter(ModbusOperation) bool
-ulong timeout
-uint8_t maxRetries
}
DuplicateOperationFilter --|> ModbusOperationFilter
RateLimitFilter --|> ModbusOperationFilter
PriorityFilter --|> ModbusOperationFilter
OperationLifecycleFilter --|> ModbusOperationFilter
```
---