polymech - fw latest | web ui
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# Modbus Logic Language (mb-lang)
|
||||
|
||||
This document describes the design and usage of the simple logic engine configurable via Modbus TCP.
|
||||
|
||||
## Purpose
|
||||
|
||||
The Modbus Logic Engine allows users to define simple conditional automation rules directly by writing to specific Modbus holding registers. This enables basic automation sequences like "if sensor value X exceeds Y, then turn on relay Z" without modifying the core firmware code.
|
||||
|
||||
## Architecture
|
||||
|
||||
A dedicated component, `ModbusLogicEngine`, runs within the firmware.
|
||||
- It exposes a block of Modbus Holding Registers for configuration and status.
|
||||
- It periodically evaluates the enabled rules.
|
||||
- It can read the state of other Modbus registers/coils.
|
||||
- It can perform actions like writing to Modbus registers/coils or calling pre-defined methods on other firmware components.
|
||||
- It updates status registers after each rule evaluation/action attempt.
|
||||
|
||||
## Configuration and Status
|
||||
|
||||
The engine supports a fixed number of logic rules, defined by `MAX_LOGIC_RULES` (e.g., 8). Each rule is configured and monitored using a block of `REGISTERS_PER_RULE` (now 13) consecutive Holding Registers.
|
||||
|
||||
- **Base Address:** `MODBUS_LOGIC_RULES_START` (Defined in `config-modbus.h`)
|
||||
- **Rule N Address:** `MODBUS_LOGIC_RULES_START + (N * REGISTERS_PER_RULE)` where `N` is the rule index (0 to `MAX_LOGIC_RULES - 1`).
|
||||
|
||||
### Register Map per Rule (N)
|
||||
|
||||
| Offset | Register Name | Address Offset | R/W | Description | Notes |
|
||||
| :----- | :---------------------------- | :-------------------------------- | :-- | :--------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- |
|
||||
| +0 | `Rule_N_Enabled` | `Base + (N*13) + 0` | R/W | **Enable/Disable Rule:** 0 = Disabled, 1 = Enabled | Rules are ignored if disabled. |
|
||||
| +1 | `Rule_N_Cond_Src_Type` | `Base + (N*13) + 1` | R/W | **Condition Source Type:** 0 = Holding Register, 1 = Coil | Specifies what type of Modbus item the condition reads. |
|
||||
| +2 | `Rule_N_Cond_Src_Addr` | `Base + (N*13) + 2` | R/W | **Condition Source Address:** Modbus address of the register/coil to read for the condition. | The address of the data point to check. |
|
||||
| +3 | `Rule_N_Cond_Operator` | `Base + (N*13) + 3` | R/W | **Condition Operator:** 0=`==`, 1=`!=`, 2=`<`, 3=`<=`, 4=`>`, 5=`>=` | The comparison to perform. |
|
||||
| +4 | `Rule_N_Cond_Value` | `Base + (N*13) + 4` | R/W | **Condition Value:** The value to compare the source against. | For Coils, use 0 for OFF and 1 for ON. |
|
||||
| +5 | `Rule_N_Action_Type` | `Base + (N*13) + 5` | R/W | **Action Type:** 0=None, 1=Write Holding Reg, 2=Write Coil, 3=Call Component Method | Specifies what to do if the condition is true. |
|
||||
| +6 | `Rule_N_Action_Target` | `Base + (N*13) + 6` | R/W | **Action Target:** Modbus Address (for Write Reg/Coil) or Component ID (for Call Method) | Specifies *what* to act upon (Register Address, Coil Address, Component ID). |
|
||||
| +7 | `Rule_N_Action_Param1` | `Base + (N*13) + 7` | R/W | **Action Parameter 1:** Value (for Write Reg), ON/OFF (for Write Coil, 0=OFF, 1=ON), Method ID (for Call) | Specifies *how* to act upon the target. |
|
||||
| +8 | `Rule_N_Action_Param2` | `Base + (N*13) + 8` | R/W | **Action Parameter 2:** Argument 1 for `Call Component Method` | First argument for the component method. Ignored for Write actions. |
|
||||
| +9 | `Rule_N_Action_Param3` | `Base + (N*13) + 9` | R/W | **Action Parameter 3:** Argument 2 for `Call Component Method` | Second argument for the component method. Ignored for Write actions. |
|
||||
| +10 | `Rule_N_Last_Status` | `Base + (N*13) + 10` | R | **Last Action Status:** 0=Idle/OK, 1=Err Cond Read, 2=Err Action Write/Call, 3=Invalid Action Params | Reports the outcome of the last trigger attempt. (See Status Codes below) |
|
||||
| +11 | `Rule_N_Last_Trigger_Timestamp` | `Base + (N*13) + 11` | R | **Last Trigger Timestamp:** System time (e.g., seconds since boot) when rule last triggered. | 0 if never triggered. Wraps eventually. |
|
||||
| +12 | `Rule_N_Trigger_Count` | `Base + (N*13) + 12` | R | **Trigger Count:** Number of times this rule's action has been successfully triggered. | Wraps eventually. Can be reset by writing 0 via Modbus. |
|
||||
|
||||
*Note: `Base` refers to `MODBUS_LOGIC_RULES_START`. R=Read-Only, W=Writeable (from Modbus perspective). Status registers (+10 to +12) are updated internally but the count (+12) can potentially be reset via write.*
|
||||
|
||||
### Status Codes (`Rule_N_Last_Status`)
|
||||
|
||||
| Code | Meaning |
|
||||
| :--- | :-------------------------- |
|
||||
| 0 | Idle / Action OK |
|
||||
| 1 | Error Reading Condition Src |
|
||||
| 2 | Error Performing Action |
|
||||
| 3 | Invalid Action Parameters |
|
||||
| 4 | Component Method Call Failed|
|
||||
| ... | (Other specific errors TBD) |
|
||||
|
||||
## Actions
|
||||
|
||||
### 1. Write Holding Register
|
||||
|
||||
- **Action Type:** 1
|
||||
- **Action Target:** Modbus address of the Holding Register.
|
||||
- **Action Parameter 1:** Value to write.
|
||||
- **Action Parameters 2 & 3:** Ignored.
|
||||
|
||||
### 2. Write Coil
|
||||
|
||||
- **Action Type:** 2
|
||||
- **Action Target:** Modbus address of the Coil.
|
||||
- **Action Parameter 1:** Value to write (0 for OFF, 1 for ON). Other non-zero values may also be interpreted as ON.
|
||||
- **Action Parameters 2 & 3:** Ignored.
|
||||
|
||||
### 3. Call Component Method
|
||||
|
||||
- **Action Type:** 3
|
||||
- **Action Target:** The numeric `Component ID`.
|
||||
- **Action Parameter 1:** The numeric `Method ID`.
|
||||
- **Action Parameter 2:** The first integer argument (`arg1`).
|
||||
- **Action Parameter 3:** The second integer argument (`arg2`).
|
||||
|
||||
**Important:** Only specific, pre-registered methods can be called. Available Component/Method IDs need separate documentation.
|
||||
|
||||
## Execution Flow
|
||||
|
||||
1. The `ModbusLogicEngine` loops periodically.
|
||||
2. For each rule `N` from 0 to `MAX_LOGIC_RULES - 1`:
|
||||
a. Read `Rule_N_Enabled`. If 0, skip.
|
||||
b. Read condition parameters.
|
||||
c. Attempt to read the current value from `Cond_Src_Addr`.
|
||||
d. If read fails, update `Rule_N_Last_Status` (e.g., to 1) and skip to the next rule.
|
||||
e. Evaluate the condition.
|
||||
f. If the condition is TRUE:
|
||||
i. Read action parameters.
|
||||
ii. Attempt to perform the specified action.
|
||||
iii. Update `Rule_N_Last_Status` based on action success (0) or failure (e.g., 2, 3, 4).
|
||||
iv. If action was successful, increment `Rule_N_Trigger_Count` and update `Rule_N_Last_Trigger_Timestamp`.
|
||||
g. If the condition is FALSE, potentially reset `Rule_N_Last_Status` to 0 (Idle), unless it holds an error state.
|
||||
|
||||
## Example Scenarios
|
||||
|
||||
*(Addresses updated for REGISTERS_PER_RULE = 13)*
|
||||
|
||||
### Example 1: Turn on Relay 5 if Register 200 >= 100
|
||||
|
||||
Assume:
|
||||
- `MODBUS_LOGIC_RULES_START` = 1000
|
||||
- Rule Index `N = 0` (`Base = 1000`)
|
||||
- Relay 5 is mapped to Coil address 5
|
||||
- Register 200
|
||||
|
||||
Write:
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :---- | :------------------------ |
|
||||
| 1000 | 1 | Rule 0: Enabled |
|
||||
| 1001 | 0 | Cond Src Type: Reg |
|
||||
| 1002 | 200 | Cond Src Addr: 200 |
|
||||
| 1003 | 5 | Cond Operator: >= (5) |
|
||||
| 1004 | 100 | Cond Value: 100 |
|
||||
| 1005 | 2 | Action Type: Write Coil |
|
||||
| 1006 | 5 | Action Target: Coil Addr 5|
|
||||
| 1007 | 1 | Action Param 1: Value ON |
|
||||
| 1008 | 0 | Action Param 2: (Ignored) |
|
||||
| 1009 | 0 | Action Param 3: (Ignored) |
|
||||
|
||||
Read Status (after trigger):
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :------------ | :-------------------------- |
|
||||
| 1010 | 0 | Last Status: OK |
|
||||
| 1011 | e.g., 12345 | Last Trigger: Timestamp |
|
||||
| 1012 | e.g., 1 | Trigger Count: 1 |
|
||||
|
||||
### Example 2: Call `resetCounter()` Method on Component `StatsTracker` if Coil 10 is ON
|
||||
|
||||
Assume:
|
||||
- Rule Index `N = 1` (`Base = 1000 + 13 = 1013`)
|
||||
- Coil 10
|
||||
- Component `StatsTracker` ID = 5
|
||||
- Method `resetCounter` ID = 1 (takes no args)
|
||||
|
||||
Write:
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :---- | :---------------------------- |
|
||||
| 1013 | 1 | Rule 1: Enabled |
|
||||
| 1014 | 1 | Cond Src Type: Coil |
|
||||
| 1015 | 10 | Cond Src Addr: Coil 10 |
|
||||
| 1016 | 0 | Cond Operator: == (0) |
|
||||
| 1017 | 1 | Cond Value: ON (1) |
|
||||
| 1018 | 3 | Action Type: Call Method |
|
||||
| 1019 | 5 | Action Target: Component ID 5 |
|
||||
| 1020 | 1 | Action Param 1: Method ID 1 |
|
||||
| 1021 | 0 | Action Param 2: Arg1 = 0 |
|
||||
| 1022 | 0 | Action Param 3: Arg2 = 0 |
|
||||
|
||||
Read Status (after trigger):
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :------------ | :-------------------------- |
|
||||
| 1023 | 0 | Last Status: OK |
|
||||
| 1024 | e.g., 12360 | Last Trigger: Timestamp |
|
||||
| 1025 | e.g., 1 | Trigger Count: 1 |
|
||||
|
||||
## Limitations & Considerations
|
||||
|
||||
- **Complexity:** Only simple, single conditions per rule. No `AND`/`OR`/`ELSE`.
|
||||
- **Execution Order:** Rules evaluated sequentially.
|
||||
- **Performance:** Rule evaluation takes time. Consider impact and execution frequency.
|
||||
- **Timestamp:** The `Last_Trigger_Timestamp` resolution and potential for wrapping depend on the firmware implementation (e.g., `millis()` overflow, using seconds since boot).
|
||||
- **Error Handling:** Status codes provide basic feedback. More detailed logging might be needed for complex debugging.
|
||||
- **Method Availability:** Callable methods are fixed in firmware.
|
||||
- **Concurrency:** Actions (especially method calls) might take time. The engine design needs to consider if rule evaluation should block or if actions run asynchronously (current design implies synchronous execution within the loop).
|
||||
|
||||
|
||||
## Limitations & Considerations
|
||||
|
||||
- **Complexity:** Only simple, single conditions per rule are supported. No `AND`/`OR` or `ELSE` logic.
|
||||
- **Execution Order:** Rules are evaluated sequentially. Be mindful of potential interactions if multiple rules modify the same target.
|
||||
- **Performance:** Reading Modbus values and executing actions takes time. Complex rules or a large number of rules might impact overall system performance. Rule execution frequency should be considered.
|
||||
- **Error Handling:** The current design doesn't explicitly define Modbus registers for rule execution status or errors. This could be added later.
|
||||
- **Method Availability:** The list of callable methods (`Component ID`, `Method ID`) is fixed in the firmware and needs to be documented for users.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Comparison: Modbus Logic Engine (MB_SCRIPT) vs. PLC Ladder Logic
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
This document compares the custom Modbus Logic Engine (`MB_SCRIPT`) implemented in this firmware with traditional PLC (Programmable Logic Controller) programming languages, primarily focusing on Ladder Logic (LD).
|
||||
|
||||
* **Modbus Logic Engine (MB_SCRIPT):** A feature enabling simple, rule-based automation directly within the ESP32 firmware. Rules are configured and monitored via Modbus registers. It allows defining conditions based on Modbus values and triggering actions like writing Modbus values or calling internal C++ functions.
|
||||
* **PLC Ladder Logic (LD):** A graphical programming language widely used in industrial automation. It mimics the appearance of electrical relay logic diagrams, making it intuitive for electricians and technicians. It runs on dedicated PLC hardware in a cyclic scan execution model.
|
||||
|
||||
## 2. Core Concepts Comparison
|
||||
|
||||
| Feature | PLC Ladder Logic (Typical) | Modbus Logic Engine (MB_SCRIPT) |
|
||||
| :---------------------- | :------------------------------------------------------------- | :------------------------------------------------------------------ |
|
||||
| **Programming Model** | Graphical (Relay Logic Diagram), Textual (ST, FBD often avail.) | Rule-Based (IF condition THEN action), Configuration via Registers |
|
||||
| **Representation** | Rungs, Contacts (NO/NC), Coils, Function Blocks (Timers, Cnt) | Blocks of Holding Registers defining rules |
|
||||
| **Execution Model** | Cyclic Scan (Read Inputs -> Solve Logic -> Write Outputs) | Iterative `loop()` checks rules sequentially based on interval |
|
||||
| **Data Addressing** | Standardized I/O points (%I, %Q), Memory (%M), Data Regs (%MW) | Modbus Addresses (Coils/Registers) managed by firmware components |
|
||||
| **Built-in Functions** | Timers (TON/TOF), Counters (CTU/CTD), Math, Compare, Move | Basic Comparisons (==, !=, <, >...), Write Coil/Reg, Call Method |
|
||||
| **Complex Logic** | Subroutines, Jumps, Structured Text (ST), Function Blocks | Requires registering custom C++ methods (`CallableMethod`) |
|
||||
| **Configuration Tool** | Dedicated PLC IDE (e.g., TIA Portal, RSLogix) | Standard Modbus Client/Master Software |
|
||||
| **Deployment** | Download program binary to PLC hardware | Write configuration values to Modbus registers |
|
||||
| **Debugging/Monitor** | Online monitoring in IDE, Forcing I/O, Status LEDs | Reading Status registers via Modbus, Serial Logs (Debug/Receipt Flags) |
|
||||
| **Hardware** | Dedicated Industrial PLC Hardware | Runs on the ESP32 microcontroller within the firmware |
|
||||
| **Extensibility** | Adding I/O modules, using advanced function blocks, libraries | Adding/Registering C++ methods, potentially modifying engine code |
|
||||
| **Target User** | Automation Engineers, Technicians, Electricians | Firmware developers, System integrators familiar with Modbus |
|
||||
|
||||
## 3. Detailed Comparison
|
||||
|
||||
### 3.1. Programming Model & Representation
|
||||
|
||||
* **Ladder Logic:** Visual and intuitive for those familiar with electrical schematics. Logic flows left-to-right across rungs. Standard symbols for contacts, coils, timers, etc., are well-understood in the industry.
|
||||
* **MB_SCRIPT:** Abstract. Logic is defined by setting numerical values in specific Modbus registers according to predefined offsets (`ModbusLogicEngineOffsets`). Requires understanding the specific register map and enum values (`E_RegType`, `ConditionOperator`, `CommandType`, `MB_Error`). Less visual and more data-entry focused.
|
||||
|
||||
### 3.2. Execution
|
||||
|
||||
* **Ladder Logic:** Typically deterministic cyclic scan. The PLC reads all inputs, executes the entire ladder program from top to bottom, and then updates all outputs in a predictable cycle time.
|
||||
* **MB_SCRIPT:** Executes within the ESP32's main `loop()`. Rules are checked sequentially within the `ModbusLogicEngine::loop()` call, which runs periodically based on `loopInterval`. Execution time can be influenced by other tasks running on the ESP32. It's event-driven based on Modbus value changes *observed* during rule evaluation, but the evaluation itself is interval-based.
|
||||
|
||||
### 3.3. Data Handling
|
||||
|
||||
* **Ladder Logic:** Uses well-defined memory areas for inputs, outputs, internal bits, timers, counters, and data registers, accessed via standardized addressing.
|
||||
* **MB_SCRIPT:** Relies entirely on the existing Modbus address space managed by other firmware components (`ModbusManager`). Condition sources and action targets are arbitrary Modbus addresses. Internal rule state (status, timestamp, count) is exposed via dedicated status registers within the rule's block. Complex data manipulation requires C++ functions.
|
||||
|
||||
### 3.4. Capabilities & Functionality
|
||||
|
||||
* **Ladder Logic:** Offers standard, pre-built function blocks for common automation tasks like timing delays, counting events, basic arithmetic, and data manipulation. More advanced PLCs include PID loops, communication protocols, motion control, etc.
|
||||
* **MB_SCRIPT:** Provides fundamental building blocks: compare Modbus values and trigger simple actions (write Modbus value, call internal function). It lacks built-in timers, counters, or complex math operations within the rule definition itself. Such functionality must be implemented in C++ and exposed via the `CALL_COMPONENT_METHOD` command.
|
||||
|
||||
### 3.5. Configuration & Deployment
|
||||
|
||||
* **Ladder Logic:** Requires specialized, often vendor-specific, programming software. The compiled logic is downloaded as a binary program to the PLC. Configuration is done offline in the IDE.
|
||||
* **MB_SCRIPT:** Configured "online" by writing values to Modbus registers using any standard Modbus master tool. No separate compilation or download step for the logic itself is needed (only for the firmware containing the engine). This allows dynamic reconfiguration without reflashing firmware (though registered methods are fixed in firmware).
|
||||
|
||||
### 3.6. Debugging & Monitoring
|
||||
|
||||
* **Ladder Logic:** IDEs provide powerful online monitoring tools, allowing visualization of rung state, live value tracing, and forcing of inputs/outputs for testing. PLCs often have hardware status LEDs.
|
||||
* **MB_SCRIPT:** Debugging relies on reading the `LAST_STATUS`, `LAST_TRIGGER_TS`, and `TRIGGER_COUNT` registers via Modbus. More detail requires enabling the `RULE_FLAG_DEBUG` and `RULE_FLAG_RECEIPT` flags and monitoring the device's serial output (`npm run build:monitor`). Less interactive than typical PLC debugging.
|
||||
|
||||
## 4. Strengths & Weaknesses
|
||||
|
||||
**MB_SCRIPT:**
|
||||
|
||||
* **Strengths:**
|
||||
* Simple rule structure, easy to understand if the register map is known.
|
||||
* Configuration via standard Modbus tools - no specialized IDE needed.
|
||||
* Runs directly on the ESP32, reducing need for external micro-controllers for simple logic.
|
||||
* Extensible via C++ (`CALL_COMPONENT_METHOD`) for complex custom actions.
|
||||
* Logic configuration can potentially be updated without firmware reflash.
|
||||
* **Weaknesses:**
|
||||
* Limited built-in functions (no timers, counters, complex math within rules).
|
||||
* Logic representation is abstract (register values) and not visual.
|
||||
* Debugging relies heavily on Modbus reads and serial logs.
|
||||
* Sequential rule execution might have timing implications compared to cyclic scan.
|
||||
* Scalability limited by `MAX_LOGIC_RULES` and ESP32 resources.
|
||||
* Complex logic requires C++ development and method registration.
|
||||
|
||||
**PLC Ladder Logic:**
|
||||
|
||||
* **Strengths:**
|
||||
* Visual and intuitive programming paradigm (especially LD).
|
||||
* Industry standard, widely understood by technicians and engineers.
|
||||
* Rich set of built-in standard functions (timers, counters, etc.).
|
||||
* Mature, powerful IDEs with excellent online monitoring and debugging capabilities.
|
||||
* Deterministic execution (typically).
|
||||
* Highly scalable with modular hardware.
|
||||
* **Weaknesses:**
|
||||
* Requires dedicated, often expensive, PLC hardware.
|
||||
* Requires specialized, often expensive, vendor-specific IDE software.
|
||||
* Less straightforward to integrate directly with custom C++ functions or device-specific hardware features compared to code running *on* the device.
|
||||
* Configuration/updates typically require offline editing and download.
|
||||
|
||||
## 5. Use Cases
|
||||
|
||||
* **MB_SCRIPT is suitable for:**
|
||||
* Simple, reactive logic based directly on Modbus values on the device.
|
||||
* Triggering predefined C++ functions based on Modbus conditions.
|
||||
* Implementing basic interlocks or sequences configurable via Modbus.
|
||||
* Situations where adding a full PLC is overkill or impractical.
|
||||
* Environments where configuration via standard Modbus tools is preferred.
|
||||
* **PLC Ladder Logic is suitable for:**
|
||||
* Complex industrial automation and control sequences.
|
||||
* Applications requiring standard timing, counting, and process control functions.
|
||||
* Systems needing robust, deterministic execution.
|
||||
* Environments where technicians are primarily familiar with Ladder Logic.
|
||||
* Large-scale systems with extensive I/O requirements.
|
||||
|
||||
## 6. Conclusion
|
||||
|
||||
The Modbus Logic Engine (`MB_SCRIPT`) provides a useful, lightweight mechanism for implementing simple, Modbus-driven automation rules directly on the ESP32 firmware. It leverages the existing Modbus infrastructure for configuration and interaction but relies on C++ for complex actions. It is not a replacement for a full PLC system using Ladder Logic, which offers a more comprehensive, standardized, and visually intuitive environment with richer built-in capabilities tailored for industrial control, albeit with the requirement of dedicated hardware and software. The choice depends on the complexity of the required logic, the target environment, user expertise, and integration needs.
|
||||
@@ -0,0 +1,240 @@
|
||||
# Modbus Logic Engine (MB_SCRIPT) Design
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The Modbus Logic Engine (`ModbusLogicEngine` class, enabled by `#define ENABLE_MB_SCRIPT`) provides a way to implement simple automation rules directly on the device, configured and monitored via Modbus. It allows users to define rules based on the state of Modbus registers or coils (conditions) and trigger actions like writing to other registers/coils or calling internal component methods.
|
||||
|
||||
This enables reactive logic without requiring an external controller polling and writing values constantly.
|
||||
|
||||
## 2. Modbus Register Layout
|
||||
|
||||
Each logic rule occupies a contiguous block of Modbus holding registers. The number of rules is defined by `MAX_LOGIC_RULES` (default 8) and the number of registers per rule by `LOGIC_ENGINE_REGISTERS_PER_RULE` (currently 13). The starting address for the first rule is defined by `MODBUS_LOGIC_RULES_START`.
|
||||
|
||||
**Register Layout per Rule:**
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Rule N
|
||||
Reg0[Offset 0: ENABLED (0/1)]
|
||||
Reg1[Offset 1: COND_SRC_TYPE (E_RegType)]
|
||||
Reg2[Offset 2: COND_SRC_ADDR]
|
||||
Reg3[Offset 3: COND_OPERATOR (ConditionOperator)]
|
||||
Reg4[Offset 4: COND_VALUE]
|
||||
Reg5[Offset 5: COMMAND_TYPE (CommandType)]
|
||||
Reg6[Offset 6: COMMAND_TARGET (Addr/CompID)]
|
||||
Reg7[Offset 7: COMMAND_PARAM1 (Value/MethodID)]
|
||||
Reg8[Offset 8: COMMAND_PARAM2 (Arg1)]
|
||||
Reg9[Offset 9: FLAGS (Debug/Receipt)]
|
||||
Reg10[Offset 10: LAST_STATUS (MB_Error)]
|
||||
Reg11[Offset 11: LAST_TRIGGER_TS (Lower 16bit)]
|
||||
Reg12[Offset 12: TRIGGER_COUNT]
|
||||
end
|
||||
|
||||
style Reg0 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg1 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg2 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg3 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg4 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg5 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg6 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg7 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg8 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg9 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg10 fill:#ccf,stroke:#333,stroke-width:2px
|
||||
style Reg11 fill:#ccf,stroke:#333,stroke-width:2px
|
||||
style Reg12 fill:#ccf,stroke:#333,stroke-width:2px
|
||||
|
||||
```
|
||||
|
||||
**Register Descriptions (Offsets defined in `ModbusLogicEngineOffsets`):**
|
||||
|
||||
* **Configuration Registers (Read/Write via Modbus):**
|
||||
* `ENABLED` (0): `0` = Disabled, `1` = Enabled.
|
||||
* `COND_SRC_TYPE` (1): Type of the Modbus entity to check for the condition. Uses `RegisterState::E_RegType` enum values (e.g., `REG_HOLDING = 3`, `REG_COIL = 2`).
|
||||
* `COND_SRC_ADDR` (2): Modbus address of the register/coil for the condition.
|
||||
* `COND_OPERATOR` (3): Comparison operator to use. Uses `ConditionOperator` enum values (e.g., `EQUAL = 0`, `NOT_EQUAL = 1`, ...).
|
||||
* `COND_VALUE` (4): The value to compare the source register/coil against.
|
||||
* `COMMAND_TYPE` (5): The action to perform if the condition is met. Uses `CommandType` enum values (e.g., `WRITE_COIL = 2`, `WRITE_HOLDING_REGISTER = 3`, `CALL_COMPONENT_METHOD = 100`).
|
||||
* `COMMAND_TARGET` (6): Target of the command.
|
||||
* For `WRITE_*`: The Modbus address to write to.
|
||||
* For `CALL_COMPONENT_METHOD`: The `Component::id` of the target component.
|
||||
* `COMMAND_PARAM1` (7): First parameter for the command.
|
||||
* For `WRITE_*`: The value to write.
|
||||
* For `CALL_COMPONENT_METHOD`: The `methodId` registered with `ModbusLogicEngine::registerMethod`.
|
||||
* `COMMAND_PARAM2` (8): Second parameter for the command.
|
||||
* For `WRITE_*`: Unused.
|
||||
* For `CALL_COMPONENT_METHOD`: The first argument (`arg1`) passed to the registered method.
|
||||
* `FLAGS` (9): Bit flags for rule behavior (See Section 7).
|
||||
|
||||
* **Status Registers (Read-Only via Modbus, except TRIGGER_COUNT reset):**
|
||||
* `LAST_STATUS` (10): Result of the last evaluation/action attempt for this rule. Uses `MB_Error` enum values (e.g., `Success = 0`, `IllegalDataAddress = 2`, `ServerDeviceFailure = 4`).
|
||||
* `LAST_TRIGGER_TS` (11): Timestamp (lower 16 bits of `millis()/1000`) of the last successful trigger.
|
||||
* `TRIGGER_COUNT` (12): Counter of successful triggers. Can be reset by writing `0`.
|
||||
|
||||
## 3. Data Structures
|
||||
|
||||
* **`LogicRule` Struct:**
|
||||
* Holds the internal representation of a single rule.
|
||||
* `config[9]`: An array storing the 9 configuration registers (Offset 0 to 8) read from/written to Modbus.
|
||||
* `lastStatus` (type `RuleStatus`/`MB_Error`): Internal state variable reflecting the last status.
|
||||
* `lastTriggerTimestamp` (type `uint32_t`): Internal state variable for the full timestamp.
|
||||
* `triggerCount` (type `uint16_t`): Internal state variable for the trigger count.
|
||||
* Helper methods (`isEnabled()`, `getCondSourceType()`, `getCommandType()`, `getFlags()`, etc.) provide convenient access to the values stored in the `config` array.
|
||||
* **`std::vector<LogicRule> rules`:** Member variable in `ModbusLogicEngine` holding all configured rules.
|
||||
* **`std::map<uint32_t, CallableMethod> callableMethods`:** Member variable storing methods registered via `registerMethod`, keyed by `(componentId << 16) | methodId`.
|
||||
|
||||
## 4. Core Logic Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ModbusClient
|
||||
participant ModbusManager
|
||||
participant ModbusLogicEngine as MLE
|
||||
participant TargetComponent
|
||||
|
||||
Note over ModbusClient, MLE: Initialization
|
||||
PHApp->>MLE: constructor(app)
|
||||
PHApp->>MLE: setup()
|
||||
MLE->>MLE: rules.resize(MAX_LOGIC_RULES)
|
||||
Note over MLE: Methods registered via registerMethod()
|
||||
PHApp->>MLE: registerMethod(compID, methodID, function)
|
||||
MLE->>MLE: callableMethods.insert(...)
|
||||
|
||||
loop Application Loop
|
||||
PHApp->>MLE: loop()
|
||||
alt Not Initialized or Interval Not Met
|
||||
MLE-->>PHApp: return E_OK
|
||||
else Rule Evaluation
|
||||
MLE->>MLE: For each rule in rules vector
|
||||
alt Rule Enabled?
|
||||
MLE->>MLE: evaluateCondition(rule)
|
||||
Note over MLE: Reads condition source
|
||||
MLE->>ModbusManager: findComponentForAddress(condAddr)
|
||||
ModbusManager-->>MLE: TargetComponent*
|
||||
MLE->>TargetComponent: readNetworkValue(condAddr)
|
||||
TargetComponent-->>MLE: currentValue (or error)
|
||||
Note over MLE: Performs comparison
|
||||
alt Condition Met?
|
||||
MLE->>MLE: performAction(rule)
|
||||
opt CommandType == WRITE_*
|
||||
Note over MLE: Performs write action
|
||||
MLE->>ModbusManager: findComponentForAddress(targetAddr)
|
||||
ModbusManager-->>MLE: TargetComponent*
|
||||
MLE->>TargetComponent: writeNetworkValue(targetAddr, value)
|
||||
TargetComponent-->>MLE: E_OK (or error)
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::Success / ServerDeviceFailure)
|
||||
opt CommandType == CALL_COMPONENT_METHOD
|
||||
Note over MLE: Performs method call
|
||||
MLE->>MLE: callableMethods.find(key)
|
||||
alt Method Found?
|
||||
MLE->>TargetComponent: Execute registered std::function(arg1)
|
||||
TargetComponent-->>MLE: E_OK (or error)
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::Success / OpExecutionFailed)
|
||||
else Method Not Found
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::IllegalDataAddress)
|
||||
end
|
||||
end
|
||||
MLE->>MLE: update timestamp & trigger count
|
||||
else Condition Not Met
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::Success) if not error
|
||||
end
|
||||
else Rule Disabled
|
||||
MLE->>MLE: Skip rule
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Note over ModbusClient, MLE: Configuration/Status Access
|
||||
ModbusClient->>ModbusManager: Read/Write Request (Holding Registers)
|
||||
ModbusManager->>MLE: readNetworkValue(addr) / writeNetworkValue(addr, val)
|
||||
MLE->>MLE: getRuleInfoFromAddress(addr)
|
||||
alt Read Request
|
||||
MLE->>MLE: Access rule.config[] or internal status
|
||||
MLE-->>ModbusManager: value / status
|
||||
else Write Request
|
||||
MLE->>MLE: rule.setConfigValue(offset, val) / rule.triggerCount = 0
|
||||
MLE-->>ModbusManager: E_OK / error
|
||||
end
|
||||
ModbusManager-->>ModbusClient: Response
|
||||
|
||||
```
|
||||
|
||||
* **`setup()`:** Initializes the `rules` vector based on `MAX_LOGIC_RULES`.
|
||||
* **`loop()`:** Called periodically by the main application loop (`PHApp`).
|
||||
* Checks if initialized and if the evaluation `loopInterval` has passed.
|
||||
* Iterates through each `LogicRule` in the `rules` vector.
|
||||
* If a rule `isEnabled()`, it calls `evaluateCondition()`.
|
||||
* If `evaluateCondition()` returns `true` (condition met), it calls `performAction()`.
|
||||
* **`evaluateCondition()`:**
|
||||
* Retrieves condition parameters (source type, address, operator, value) from the rule.
|
||||
* Calls `readConditionSourceValue()` to get the current value of the source register/coil.
|
||||
* Performs the comparison based on the `condOperator`.
|
||||
* Updates `rule.lastStatus` (e.g., `MB_Error::IllegalDataAddress` on read failure, `MB_Error::IllegalDataValue` on invalid operator).
|
||||
* Returns `true` if the condition is met, `false` otherwise (including errors).
|
||||
* **`performAction()`:**
|
||||
* Retrieves command parameters (type, target, params) from the rule.
|
||||
* Based on `commandType`:
|
||||
* Calls `performWriteAction()` for `WRITE_*` commands.
|
||||
* Calls `performCallAction()` for `CALL_COMPONENT_METHOD`.
|
||||
* Updates `rule.lastStatus` based on the success/failure of the action (e.g., `MB_Error::Success`, `MB_Error::ServerDeviceFailure`, `MB_Error::OpExecutionFailed`, `MB_Error::IllegalFunction`).
|
||||
* If successful, updates `rule.lastTriggerTimestamp` and increments `rule.triggerCount`.
|
||||
* Returns `true` on success, `false` on failure.
|
||||
|
||||
## 5. Interaction with ModbusManager
|
||||
|
||||
The `ModbusLogicEngine` relies on the `ModbusManager` (accessed via the `PHApp* app` pointer) to interact with other components' Modbus values.
|
||||
|
||||
* **`readConditionSourceValue()`:**
|
||||
* Takes the source type (`RegisterState::E_RegType`) and address.
|
||||
* Uses `app->modbusManager->findComponentForAddress(address)` to find the component responsible for that address.
|
||||
* Calls the target component's `readNetworkValue(address)` method.
|
||||
* Returns the value or indicates failure.
|
||||
* **`performWriteAction()`:**
|
||||
* Takes the command type (`WRITE_COIL` or `WRITE_HOLDING_REGISTER`), target address, and value.
|
||||
* Uses `app->modbusManager->findComponentForAddress(address)` to find the target component.
|
||||
* Calls the target component's `writeNetworkValue(address, value)` method.
|
||||
* Returns `true` if the write call returns `E_OK`, `false` otherwise.
|
||||
|
||||
## 6. Method Calling (`CALL_COMPONENT_METHOD`)
|
||||
|
||||
This command type allows rules to trigger C++ methods within other components.
|
||||
|
||||
* **Registration:** Components (like `PHApp` or custom components) that want to expose methods to the Logic Engine must call `ModbusLogicEngine::registerMethod(componentId, methodId, method)`.
|
||||
* `componentId`: The unique `Component::id` of the component exposing the method.
|
||||
* `methodId`: A unique ID (within that component) for the specific method being exposed.
|
||||
* `method`: A `std::function<short(short, short)>` object wrapping the actual C++ method (often created using `std::bind`). The function should accept two `short` arguments and return `E_OK` (or another error code).
|
||||
* The engine stores this registration in the `callableMethods` map.
|
||||
* **Configuration:** A rule is configured with `COMMAND_TYPE = CALL_COMPONENT_METHOD`.
|
||||
* `COMMAND_TARGET` is set to the `componentId`.
|
||||
* `COMMAND_PARAM1` is set to the `methodId`.
|
||||
* `COMMAND_PARAM2` is set to the value to be passed as the first argument (`arg1`) to the registered C++ method.
|
||||
* **Execution (`performCallAction()`):**
|
||||
* Combines `componentId` (from Target) and `methodId` (from Param1) into a key.
|
||||
* Looks up the key in the `callableMethods` map.
|
||||
* If found, executes the stored `std::function`, passing `param2` (as `arg1`) and a dummy `0` (as `arg2`) to the bound C++ method.
|
||||
* Updates status based on the return value of the C++ method (`MB_Error::Success` if `E_OK`, `MB_Error::OpExecutionFailed` otherwise) or `MB_Error::IllegalDataAddress` if the method was not found in the map.
|
||||
|
||||
*Note: This `std::function`-based registration is internal to the `ModbusLogicEngine` and separate from the `Bridge` mechanism used for serial commands.*
|
||||
|
||||
## 7. Flags (`FLAGS` Register - Offset 9)
|
||||
|
||||
The `FLAGS` register allows modifying rule behavior using bitmasks:
|
||||
|
||||
* **`RULE_FLAG_DEBUG` (Bit 0 / Value 1):** If set, enables verbose logging (`Log.verboseln`) during the evaluation and action phases for this specific rule, showing details like addresses, values, and outcomes.
|
||||
* **`RULE_FLAG_RECEIPT` (Bit 1 / Value 2):** If set, logs an informational message (`Log.infoln`) whenever the rule's action is executed successfully.
|
||||
|
||||
These flags can be combined (e.g., value `3` enables both).
|
||||
|
||||
## 8. Modbus Interface (`read/writeNetworkValue`)
|
||||
|
||||
* The `ModbusLogicEngine` implements `readNetworkValue` and `writeNetworkValue` as required by the `Component` base class.
|
||||
* These methods are called by `ModbusManager` when a Modbus client reads/writes registers within the engine's address range (`MODBUS_LOGIC_RULES_START` onwards).
|
||||
* They calculate the `ruleIndex` and `offset` from the requested Modbus address.
|
||||
* **Read:**
|
||||
* If the offset corresponds to a configuration register (0-8), it returns the value from `rule.config[offset]`.
|
||||
* If the offset is `FLAGS` (9), it returns the flag value via `getFlags()`.
|
||||
* If the offset corresponds to a status register (10-12), it returns the value from the internal state variables (`rule.lastStatus`, `rule.lastTriggerTimestamp & 0xFFFF`, `rule.triggerCount`).
|
||||
* **Write:**
|
||||
* If the offset corresponds to a configuration register (0-9, including FLAGS), it updates `rule.config[offset]` using `rule.setConfigValue()`.
|
||||
* If the offset is `TRIGGER_COUNT` (12) and the value is `0`, it resets `rule.triggerCount`.
|
||||
* Writes to read-only status registers (10, 11) are disallowed and return an error.
|
||||
@@ -0,0 +1,197 @@
|
||||
# Modbus Logic Engine (MB_SCRIPT) Functional Testing Strategy
|
||||
|
||||
## 1. Objective
|
||||
|
||||
To verify the functional correctness of the `ModbusLogicEngine` component (enabled by the `ENABLE_MB_SCRIPT` define). This includes:
|
||||
* Correct configuration of logic rules via Modbus.
|
||||
* Accurate evaluation of rule conditions based on Modbus register/coil values.
|
||||
* Proper execution of defined actions (writing registers/coils, calling component methods).
|
||||
* Correct reporting of rule status, trigger timestamps, and trigger counts via Modbus.
|
||||
* Handling of various error conditions.
|
||||
|
||||
## 2. Scope
|
||||
|
||||
This strategy focuses on black-box functional testing from the perspective of a Modbus client interacting with the device. It does not cover unit testing of individual `ModbusLogicEngine` methods or performance/stress testing (which may have separate test plans).
|
||||
|
||||
## 3. Approach
|
||||
|
||||
Testing will primarily utilize the existing `npm` scripts which wrap Python helper scripts to interact with the device over Modbus TCP.
|
||||
|
||||
* **Configuration:** Rules will be configured by writing to the specific Modbus holding registers allocated to the Logic Engine (`MODBUS_LOGIC_RULES_START` and subsequent offsets defined in `src/ModbusLogicEngine.h`).
|
||||
* **Triggering:** Rule conditions will be triggered by writing appropriate values to the source Modbus holding registers or coils specified in the rule's condition.
|
||||
* **Verification:**
|
||||
* Action outcomes will be verified by reading the target Modbus holding registers or coils.
|
||||
* Rule status, timestamps, and trigger counts will be verified by reading the corresponding status registers for the rule.
|
||||
* Internal behavior and potential errors can be monitored using device logs (`npm run build:monitor` or `npm run debug:serial`).
|
||||
* **No New Scripts:** This strategy aims to use only the pre-existing `npm` scripts for Modbus interaction.
|
||||
|
||||
## 4. Tools
|
||||
|
||||
* **Modbus Read/Write:**
|
||||
* `npm run modbus:read:holding -- --address <addr>`
|
||||
* `npm run modbus:write:holding -- --address <addr> --value <val>`
|
||||
* `npm run modbus:read:coil -- --address <addr>`
|
||||
* `npm run modbus:write:coil -- --address <addr> --value <0|1>`
|
||||
* **Logging:**
|
||||
* `npm run build:monitor` (Live serial monitoring)
|
||||
* `npm run debug:serial` (Fetch buffered logs via REST API)
|
||||
* **Reference:**
|
||||
* `src/ModbusLogicEngine.h` (for register offsets, enums)
|
||||
* `src/config-modbus.h` (for `MODBUS_LOGIC_RULES_START` address)
|
||||
|
||||
## 5. Prerequisites
|
||||
|
||||
* Firmware compiled with `#define ENABLE_MB_SCRIPT` in `config.h` and flashed to the ESP32.
|
||||
* Device connected to the network and accessible via its IP address or mDNS name (`modbus-esp32.local` by default).
|
||||
* Python environment configured correctly to run the scripts in the `scripts/` directory.
|
||||
* Knowledge of the Modbus register mapping for the Logic Engine (starting address + offsets).
|
||||
|
||||
## 6. Test Cases
|
||||
|
||||
Let `RULE_START = MODBUS_LOGIC_RULES_START`.
|
||||
Let `RULE_0_BASE = RULE_START`.
|
||||
Let `RULE_1_BASE = RULE_START + LOGIC_ENGINE_REGISTERS_PER_RULE`.
|
||||
Offsets are defined in `ModbusLogicEngineOffsets`.
|
||||
|
||||
*(Note: Choose suitable, otherwise unused Modbus addresses for source/target registers/coils for testing)*
|
||||
|
||||
**TC 1: Basic Rule - Write Holding Register**
|
||||
1. **Configure:**
|
||||
* Write `1` to `RULE_0_BASE + ENABLED`.
|
||||
* Write `3` (REG_HOLDING) to `RULE_0_BASE + COND_SRC_TYPE`.
|
||||
* Write `2000` to `RULE_0_BASE + COND_SRC_ADDR`.
|
||||
* Write `0` (EQUAL) to `RULE_0_BASE + COND_OPERATOR`.
|
||||
* Write `123` to `RULE_0_BASE + COND_VALUE`.
|
||||
* Write `3` (WRITE_HOLDING_REGISTER) to `RULE_0_BASE + COMMAND_TYPE`.
|
||||
* Write `2001` to `RULE_0_BASE + COMMAND_TARGET`.
|
||||
* Write `456` to `RULE_0_BASE + COMMAND_PARAM1` (Value to write).
|
||||
* Write `0` to `RULE_0_BASE + COMMAND_PARAM2` (Unused).
|
||||
* Write `0` to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to Modbus address `2000`.
|
||||
3. **Verify:**
|
||||
* Read address `2001`. Expected: `456`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
* Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `1`.
|
||||
|
||||
**TC 2: Condition Operator - Not Equal**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `1` (NOT_EQUAL) to `RULE_0_BASE + COND_OPERATOR`.
|
||||
* Write `123` to `RULE_0_BASE + COND_VALUE`.
|
||||
2. **Trigger 1:** Write `123` to address `2000`.
|
||||
3. **Verify 1:** Read address `2001`. Expected: *Unchanged* from previous state (action should *not* run).
|
||||
4. **Trigger 2:** Write `124` to address `2000`.
|
||||
5. **Verify 2:**
|
||||
* Read address `2001`. Expected: `456`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
* Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: Incremented.
|
||||
* *(Repeat for other operators: `<`, `<=`, `>`, `>=`)*
|
||||
|
||||
**TC 3: Source Type - Coil**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `2` (REG_COIL) to `RULE_0_BASE + COND_SRC_TYPE`.
|
||||
* Write `100` (Test Coil Addr) to `RULE_0_BASE + COND_SRC_ADDR`.
|
||||
* Write `1` to `RULE_0_BASE + COND_VALUE` (Condition is Coil ON).
|
||||
2. **Trigger:** Write `1` to Coil address `100`.
|
||||
3. **Verify:**
|
||||
* Read address `2001`. Expected: `456`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
|
||||
**TC 4: Action Type - Write Coil**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `2` (WRITE_COIL) to `RULE_0_BASE + COMMAND_TYPE`.
|
||||
* Write `101` to `RULE_0_BASE + COMMAND_TARGET`.
|
||||
* Write `1` to `RULE_0_BASE + COMMAND_PARAM1` (Value: ON).
|
||||
* Write `0` to `RULE_0_BASE + COMMAND_PARAM2` (Unused).
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Read Coil address `101`. Expected: `1`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
|
||||
**TC 5: Action Type - Call Component Method (Requires Setup)**
|
||||
* **Prerequisite:** A method must be registered with the `ModbusLogicEngine` that has a verifiable side-effect readable via Modbus. Example: A simple method in `PHApp` that increments a counter stored in a Modbus register (`e.g., address 3000`).
|
||||
```cpp
|
||||
// In PHApp.h (or a test component)
|
||||
short testMethod(short p1, short p2) {
|
||||
testMethodCounter += p1; // Use p1 (arg1 from rule)
|
||||
Log.infoln("Test Method Called! Arg1=%d, Counter: %d", p1, testMethodCounter);
|
||||
return E_OK;
|
||||
}
|
||||
uint16_t testMethodCounter = 0;
|
||||
|
||||
// In PHApp::setup() or where ModbusLogicEngine is initialized
|
||||
logicEngine->registerMethod(this->id, 1, // Use app ID and method ID 1
|
||||
std::bind(&PHApp::testMethod, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
// In PHApp::readNetworkValue()
|
||||
if (address == 3000) return testMethodCounter;
|
||||
```
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `100` (CALL_COMPONENT_METHOD) to `RULE_0_BASE + COMMAND_TYPE`.
|
||||
* Write `app->id` to `RULE_0_BASE + COMMAND_TARGET` (Component ID).
|
||||
* Write `1` to `RULE_0_BASE + COMMAND_PARAM1` (Method ID).
|
||||
* Write `5` to `RULE_0_BASE + COMMAND_PARAM2` (Argument 1).
|
||||
2. **Initial Read:** Read address `3000`. Note the value (e.g., `X`).
|
||||
3. **Trigger:** Write `123` to address `2000`.
|
||||
4. **Verify:**
|
||||
* Read address `3000`. Expected: `X + 5`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
* Check logs (`build:monitor`) for "Test Method Called! Arg1=5".
|
||||
|
||||
**TC 6: Rule Enable/Disable**
|
||||
1. **Configure:** Configure rule as in TC 1.
|
||||
2. **Disable:** Write `0` to `RULE_0_BASE + ENABLED`.
|
||||
3. **Trigger:** Write `123` to address `2000`.
|
||||
4. **Verify (Disabled):** Read address `2001`. Expected: *Unchanged*. Trigger count should *not* increment.
|
||||
5. **Enable:** Write `1` to `RULE_0_BASE + ENABLED`.
|
||||
6. **Trigger:** Write `123` to address `2000`.
|
||||
7. **Verify (Enabled):** Read address `2001`. Expected: `456`. Trigger count *should* increment.
|
||||
|
||||
**TC 7: Error - Invalid Condition Source Address**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `9999` (Invalid/Unregistered address) to `RULE_0_BASE + COND_SRC_ADDR`.
|
||||
2. **Trigger:** Let the engine loop run.
|
||||
3. **Verify:** Read `RULE_0_BASE + LAST_STATUS`. Expected: `2` (MB_Error::IllegalDataAddress).
|
||||
|
||||
**TC 8: Error - Invalid Action Target Address (Write)**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `9998` (Invalid/Unregistered address) to `RULE_0_BASE + COMMAND_TARGET`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:** Read `RULE_0_BASE + LAST_STATUS`. Expected: `4` (MB_Error::ServerDeviceFailure).
|
||||
|
||||
**TC 9: Error - Invalid Action Target Method (Call)**
|
||||
1. **Configure:** Similar to TC 5, but:
|
||||
* Write `99` (Non-existent Method ID) to `RULE_0_BASE + COMMAND_PARAM1`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:** Read `RULE_0_BASE + LAST_STATUS`. Expected: `2` (MB_Error::IllegalDataAddress).
|
||||
|
||||
**TC 10: Status/Counter Reset**
|
||||
1. **Configure & Trigger:** Perform TC 1.
|
||||
2. **Verify Count:** Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `1` (or current count).
|
||||
3. **Reset Counter:** Write `0` to `RULE_0_BASE + TRIGGER_COUNT`.
|
||||
4. **Verify Reset:** Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `0`.
|
||||
5. **Trigger Again:** Write `123` to address `2000`.
|
||||
6. **Verify Increment:** Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `1`.
|
||||
|
||||
**TC 11: Debug Flag**
|
||||
1. **Configure:** Configure as in TC 1, but:
|
||||
* Write `RULE_FLAG_DEBUG` (value 1) to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Check logs (`build:monitor`). Expected: Verbose logs like "MLE Eval [0]: ...", "MLE Action [0]: ...".
|
||||
* Read address `2001`. Expected: `456`.
|
||||
|
||||
**TC 12: Receipt Flag**
|
||||
1. **Configure:** Configure as in TC 1, but:
|
||||
* Write `RULE_FLAG_RECEIPT` (value 2) to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Check logs (`build:monitor`). Expected: Info log "MLE: Rule 0 action successful.".
|
||||
* Read address `2001`. Expected: `456`.
|
||||
|
||||
**TC 13: Debug + Receipt Flags**
|
||||
1. **Configure:** Configure as in TC 1, but:
|
||||
* Write `RULE_FLAG_DEBUG | RULE_FLAG_RECEIPT` (value 3) to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Check logs (`build:monitor`). Expected: Both verbose debug logs and the receipt log.
|
||||
* Read address `2001`. Expected: `456`.
|
||||
Reference in New Issue
Block a user