Replies: 1 comment 4 replies
|
Edit: more i think about it, less is like this idea. Final thoughts in comment #1418 (reply in thread) An alternative worth considering: instead of a separate Connections are config entries, created via the UI or imported from YAML. Import maps onto an existing config entry by transport identity (serial port, or host/port combo), so it's idempotent and never creates duplicates. Entities are not imported. Manual entities stay in YAML for now and bind to the connection. No need to build config flows for every platform's large schema, and the recent Every connection is a config entry, whether UI-created or YAML-imported, so any of them is referenceable by other integrations via a Upside: one Credit to @ThyMYthOS for pursuing this idea in home-assistant/core#174931 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This proposal is based on expansive research covering Modbus libraries, Home Assistant integrations and configurations for the Home Assistant Modbus integration.
1. Context
Modbus is a client/server protocol: the devices are servers, and a single client polls them. A network has room for just one polling client: strictly so on serial RTU, and effectively so on the TCP gateways that bridge to it. Two Home Assistant integrations that both want to talk to devices on the same Modbus network are out of luck, because there is no second client slot, so they collide.
The only way to share a Modbus network is to put one central client in front of it and route every integration's requests through it. Today the only way to do it is using YAML to configure the existing Modbus integration. GitHub is full of examples. These could all become integrations if we had a central point to share the Modbus network:
modbus_connectionis that central point.2. What
modbus_connectionisThe integration's interface to other integrations is a single function that returns a
ModbusUnit. In pseudo-code, to show how that interface works:(Pseudo-code: it illustrates the interface, not the final implementation.) If the connection isn't loaded yet or has gone away, it raises
ConnectionNotReady, which consumers turn intoConfigEntryNotReady.Where do those connections come from? The integration lets a user create Modbus connections from the UI, via a config flow. A connection on its own does nothing. It is just a managed, shared client to a Modbus network, sitting ready for integrations to lease units from.
One config entry is one connection. The config flow collects only transport:
It validates by actually opening the link, then holds the live connection for the entry's lifetime. On a drop it reloads to recreate, and on unload it closes.
3. The
modbus-connectionlibraryTo allow connection objects to be shared between
modbus_connection(the integration that owns the connection) and the device libraries that integrations use to talk to their hardware, we created a new generic, non-HA-specific Modbus helper library calledmodbus-connection. Both sides speak itsModbusConnectionandModbusUnittypes, so a connection opened by the integration can be handed straight to a device library that knows nothing about Home Assistant, liketrovis-modbus.It was crafted together with the Home Assistant Modbus community (
ModbusunderProjectson Discord), including community members making integrations for Stiebel Eltron, Trovis, Huawei, and Fronius, and the author of tmodbus, who reviewed the design and are happy with the result. It gives us connection sharing and a clean, backend-neutral abstraction (pymodbus or tmodbus behind one Protocol).4. How an integration consumes a connection
A Modbus network is divided into units, where a unit is a distinct device on the network, addressed by its unit ID. An integration that wants to talk to a device does so through that device's unit.
A consumer integration:
modbus_connectionentry (ConfigEntrySelector({"integration": "modbus_connection"})) and a unit ID, and declares"dependencies": ["modbus_connection"]in its manifest.What this means for the consumer:
async_get_unitreturns aModbusUnit, the handle for one device. No connection object and no lifecycle to manage.ConnectionNotReadytoConfigEntryNotReady. If the connection isn't ready, the consumer re-raisesConfigEntryNotReadyand HA retries with backoff.5. Guarantees and constraints
ModbusUnitexposes raw register and coil reads/writes (returninglist[int]orlist[bool]) and the full Modbus function-code set, with no typed or scaled helpers. Decoding lives in the device library (for exampletrovis-modbus), which consumes ourModbusUnitmodel.ModbusConnectionError,ModbusTimeoutError, andModbusExceptionError(with.exception_code), all underModbusError. Calls raise on failure; they never returnNoneor swallow errors.on_connection_lost. This will reload the config entry. If the connection is back, the config entry will set up normally. Ifmodbus_connectionindicates the connection as being unavailable, we raiseConfigEntryNotReadyand setup will automatically be retried.6. Renaming the legacy
modbusintegration to "Manual Modbus"This proposal leaves the existing YAML
modbusintegration untouched; sharing connections with it is out of scope. The blocker is structural: it is YAML-configured and binds register-reading tightly to the connection, so a connection can't be lifted out cleanly. Importing only the connection wouldn't help, because its entities would still live in YAML and still need their own way to read registers, which would mean rebuilding the entire generic register-to-entity builder (every platform, with addresses, data types, scaling, swaps) in the UI. That is near-impossible to do properly.Most users of the Modbus integration are not familiar with Modbus and instead copy and paste Modbus YAML configuration from GitHub. I would want all such cases to become integrations.
The future direction I want to propose is to rename it to "Manual Modbus" and let it become a consumer of shared connections too, keeping the roll-your-own register-to-entity workflow for power users, now riding on the same managed connections. This mirrors Zigbee's history: the old
zigbeeintegration let you wire up your own devices, andzhaarrived later as the actual Zigbee smart-home spec. Likewise,modbus_connectionplus purpose-built integrations is the "real" path; a renamed Manual Modbus would be the do-it-yourself path layered on top.All reactions