You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
homie: type the Device injection point with MqttDeviceTransport
Follow-up to #14, on top of #12's MqttTransport base. Adds a Device-side
structural type and mirrors #12's owned/injected client split onto Device, so
"the SDK never starts or stops a client it did not build" is enforced by the
types rather than a convention.
- transport.py: MqttDeviceTransport(MqttTransport) adds is_connected + is_running
(the members the device publish path reads); omits start/stop/publish_and_flush,
which are owned-only. Exported from ebus_sdk.
- Device.mqttc is retyped Optional[MqttDeviceTransport]; the SDK-built client is
kept on a separate Device._owned_client (concrete MqttClient), and every
owned-only call (start/stop/publish_and_flush, incl. Property.start_mqtt_client)
routes through it. get_mqtt_client() returns the transport type.
+4 tests (MqttDeviceTransport protocol + the _owned_client handle). Full suite
and ruff green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,7 @@ All notable changes to `ebus-sdk` are recorded here. Format follows [Keep a Chan
6
6
7
7
### Added
8
8
9
+
-`MqttDeviceTransport`: the structural (`Protocol`) type for a client injected into a root `Device`, re-exported from `ebus_sdk`. It derives from the shared `MqttTransport` base (`publish` + `subscribe`) and adds `is_connected` + `is_running`, the members the device publish path reads; it omits `start` / `stop` / `publish_and_flush`, which are owned-only. `Device`'s `mqttc=` parameter is now annotated with it instead of the concrete `MqttClient` (parallel to `Controller`'s `MqttControllerTransport`), so a consumer injecting its own client type-checks once `ebus-mqtt-client` ships `py.typed` (today `MqttClient` resolves to `Any`, so injection already type-checks; the protocol keeps it working past that release). The SDK keeps the client it builds on a separate `Device._owned_client` handle, so the owned-only lifecycle methods resolve on the concrete type and "the SDK never starts or stops a client it did not build" is enforced by the types rather than a convention. Additive.
9
10
- `Device` bring-your-own-transport: `Device(..., mqttc=<client>)` accepts a pre-built MQTT client instead of constructing one from `mqtt_cfg`, so a host that already owns its MQTT connection (e.g. a Home Assistant integration, whose MQTT integration is `single_config_entry` and forbids background threads) can publish an eBus device tree over its own transport and event loop. This is the producer-side mirror of the `Controller` seam added in 0.13.0. An injected client is used as-is: the SDK never `start()`s or `stop()`s it, and `stop()` publishes a final retained `$state=disconnected` through it and returns without flushing or closing (non-blocking on the caller's loop). Root-only, and mutually exclusive with `mqtt_cfg=` / `parent=`. Property publishing now gates on connectivity (`is_connected()`) rather than only the SDK-owned run flag, so a caller-driven client that never calls the SDK's `start()` still publishes property values (owned behavior is unchanged: there, connected implies running). Because an injected client bypasses the SDK's connect path (where the LWT, the reconnect republish, and the disconnect hook are wired), the caller wires the Homie-correctness pieces itself using `Device.will()` and `Device.refresh_tree()` (below); `on_disconnect=` is inert for an injected client (documented, and warned at construction). Additive: the default (no `mqttc`) path constructs, starts, owns, and stops a client exactly as before. Thanks to @cayossarian (GH #7). (#14)
10
11
-`Device.will()`: returns the tree root's Last Will and Testament descriptor (topic ending in `/$state`, payload `lost`), exposed so a bring-your-own-transport caller can set it on their client before connecting: the will rides the MQTT CONNECT packet, so the SDK cannot add it to a client it is merely handed. The SDK uses it for any client it builds. `Device.refresh_tree()` (which republishes the whole tree) is documented as the companion on-connect hook a BYO caller wires so the retained tree re-announces after a reconnect. Additive. (#13)
11
12
-`Controller.resync()`: the tree-rooted discovery bookkeeping reset, extracted from the owned-client on-connect handler and made public so a bring-your-own-transport tree-rooted controller can re-walk the tree from a broker reconnect it drives itself (an injected client bypasses the SDK's on-connect, where the reset is otherwise wired). A no-op in wildcard and single-device modes. Additive. (#13)
0 commit comments