3.4 KiB
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 likeModbusDevice<N>today) and pre-allocates theMB_Registersarray plus view. - Adds two convenience wrappers:
registerField(id, name, fnCode, access)– returns reference to the freshly allocatedMB_Registersentry so the caller can immediately call.bind(myNetworkValue).bind(NetworkValue<T>& nv)– copies the addressing information and keeps a pointer so that laterenable()/disable()can toggleNetworkValueFeatureFlags::MODBUSfor the field in one place.
This reduces a typical component constructor to a short chain:
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:
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:
- A small
enum classper component describing its logical fields – e.g.enum class OmronField : uint8_t { PV, SP, StatusLow, … }. - Each
MB_Registersgains a new memberuint8_t logicalIdholding the cast value of that enum. - Helpers
byId(OmronField f)andmb_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:
#define NV_FIELD(enumId, nvRef, fn, acc) \
registerField(enumId, #enumId, fn, acc).bind(nvRef)
Example:
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
- Keep
ModbusDevice<N>working untouched. - Introduce
ModbusBindable<N>side-by-side – early adopters can switch gradually. - 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
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.