Files
firmware-base/playground/docs/networked-component.md

68 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Network-enabled Components Incremental Design Notes
The objective is to remove repetitive boiler-plate around Modbus exposure while staying 100 % compatible with the existing `NetworkValue` feature set and the current component hierarchy.
### 1. Helper mix-in: `ModbusBindable<N>`
* Template parameter **`N`** = compile-time count of Modbus fields a component wants to expose.
* Derives from `Component` (exactly like `ModbusDevice<N>` today) and pre-allocates the `MB_Registers` array plus view.
* Adds two convenience wrappers:
1. `registerField(id, name, fnCode, access)` returns reference to the freshly allocated `MB_Registers` entry so the caller can immediately call `.bind(myNetworkValue)`.
2. `bind(NetworkValue<T>& nv)` copies the addressing information and keeps a pointer so that later **`enable()`** / **`disable()`** can toggle `NetworkValueFeatureFlags::MODBUS` for the field in one place.
This reduces a typical component constructor to a short chain:
```cpp
using NVf = NetworkValue<int>;
_modbus.registerField(Temperature, "Temp", FN_READ_HOLD_REGISTER, MB_ACCESS_READ_ONLY)
.bind(_temperatureNV);
```
No manual index math, no local `_modbusBlocks[i] = …`.
### 2. Runtime toggle
Expose two helpers:
```cpp
void Component::enableNet() { SBI(nFlags, OBJECT_NET_CAPS::E_NCAPS_MODBUS); }
void Component::disableNet() { CBI(nFlags, OBJECT_NET_CAPS::E_NCAPS_MODBUS); }
```
`ModbusBindable` observes the flag in its `loop()` and calls `.enableFeature(MODBUS)` or `.disableFeature(MODBUS)` on every bound `NetworkValue` once when the state flips.
### 3. Stable logical identifiers
Today the only handle for a field is its **offset / absolute address** and a free-form name. To decouple user-code from register maps introduce:
1. A small `enum class` per component describing its logical fields e.g. `enum class OmronField : uint8_t { PV, SP, StatusLow, … }`.
2. Each `MB_Registers` gains a new member `uint8_t logicalId` holding the cast value of that enum.
3. Helpers `byId(OmronField f)` and `mb_tcp_write(logicalId, value)` eliminate hard-coded offsets in the business logic.
Address computation stays automatic inside the `registerField()` helper which receives the enum value and returns the concrete startAddress.
### 4. Optional syntactic sugar field builder
A single-line macro speeds up definition:
```cpp
#define NV_FIELD(enumId, nvRef, fn, acc) \
registerField(enumId, #enumId, fn, acc).bind(nvRef)
```
Example:
```cpp
NV_FIELD(OmronField::PV, _pv, FN_READ_HOLD_REGISTER, MB_ACCESS_READ_ONLY);
NV_FIELD(OmronField::SP, _sp, FN_WRITE_HOLD_REGISTER, MB_ACCESS_READ_WRITE);
```
### 5. Migration path
1. Keep `ModbusDevice<N>` working untouched.
2. Introduce `ModbusBindable<N>` side-by-side early adopters can switch gradually.
3. After all components migrate, retire the older mix-in.
### 6. Minimal runtime footprint
All helpers are `constexpr` / `inline`; no dynamic allocation, no RTTI, no `std` containers fits ESP32 & PlatformIO constraints.
### 7. Quick architecture overview
```mermaid
graph TD
Comp("Component") -->|mixin| MB["ModbusBindable<N>"]
MB --> NV("NetworkValue<T>")
MB --> Regs["MB_Registers[N]"]
Comp -- enable/disable --> MB
```
---
**Next step**: draft the actual `ModbusBindable` header and refactor one small component (`NetworkValueTest`) as proof-of-concept.