Skip to content

build(deps): bump ebus-sdk from 0.1.5 to 0.19.0 - #39

Merged
cayossarian merged 3 commits into
mainfrom
dependabot/pip/ebus-sdk-0.17.0
Aug 19, 2026
Merged

build(deps): bump ebus-sdk from 0.1.5 to 0.19.0#39
cayossarian merged 3 commits into
mainfrom
dependabot/pip/ebus-sdk-0.17.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 7, 2026

Copy link
Copy Markdown
Contributor

Bumps ebus-sdk from 0.1.5 to 0.19.0.

Release notes

Sourced from ebus-sdk's releases.

v0.19.0

Added

  • Controller.is_tree_complete(root_id) and Controller.set_on_tree_ready_callback(): a first-class answer to "has the declared tree fully described itself?". $state=ready is a per-device signal meaning "my own $description is current" and never promised anything about descendants, so a consumer needing a whole-tree gate previously had to hand-roll one, and hand-rolling it is how you end up with a one-shot barrier that stops reconciling and silently misses a device commissioned later. Both are built as reconciling predicates instead: is_tree_complete() walks the declared tree on demand and flips back to False when a device declares a new child, and the callback is edge-triggered but re-arms, firing again for each settled shape. Completeness is about description, not liveness: a declared child that is lost has still told you what it is, so use get_effective_state() for liveness. A declared cycle terminates rather than hanging. (#37)

Changed

  • Tooling: the ruff lint selection is now declared explicitly (select = ["E4", "E7", "E9", "F"]) rather than inherited from ruff's defaults, and CI moves from ruff 0.15.21 to 0.16.1. Ruff 0.16 widened its default selection (UP, LOG, BLE, I, RUF and more) and began formatting Python code blocks embedded in Markdown, so an unchanged codebase reported 0 or 381 violations depending only on which ruff you happened to run, and four docs files showed phantom format diffs locally that CI never saw. Pinning the set decouples "what this project lints for" from "what version of ruff is installed"; extend-exclude = ["*.md"] keeps prose out of both check and format. Verified clean on both 0.16.1 and 0.15.21, with no source changes. Widening the rule set (the 381) is now a deliberate act rather than an upgrade side-effect. (#39)

  • Device.refresh_tree() is now explicitly best-effort: a descendant whose republish raises is logged (reason=deviceRefreshTreeChildFailed) and skipped, and the cascade continues. Property.publish_value() reaches the MQTT client without wrapping it and Node.publish() had no guard, so a bring-your-own-transport client that raised on one property aborted the walk from wherever it failed, taking out every later sibling, every ancestor's $state, and therefore the whole tree's reconnect. One sick device could keep an entire enclosure off the broker. Node.publish() now contains a raising property at the same grain. The policy is stated in the refresh_tree() docstring rather than left implicit; the exception is not re-raised. (#36)

  • Device.remove_node() and Device.delete_node() now cross-reference each other in their docstrings. They sit adjacent and differ in one consequential way (remove_node() drops the node from the schema but leaves its properties' retained value topics on the broker; delete_node() clears them too), and neither said so, which made picking the wrong one a silent way to strand retained topics. Docs only, no behaviour change. (#38)

Fixed

  • Node.delete_property() now republishes $description, as its mirror Node.add_property() always has. Deleting a property cleared the retained value topic but left the device in ready with a $description that still named the property, and nothing corrected it afterwards, so the broker held a self-contradicting device indefinitely. The two halves of the same API disagreed about whether mutating a node's property set is a structural change; it is. Deletions batch inside device.state_transition() exactly as additions do, so N deletions still collapse to one $description publish. (#35)

v0.18.1

Added

  • https://github.com/electrification-bus/python-sdk/blob/HEAD/doc/consuming-a-homie-tree.md: the controller/subscriber-side guide, and the counterpart to https://github.com/electrification-bus/python-sdk/blob/HEAD/doc/building-a-proxy.md. It states the asymmetry the convention relies on and that this repo had never written down: a producer SHOULD minimize $state and $description transitions (quality-of-implementation, best-effort), while a consumer MUST react to every one of them, unconditionally (correctness). Covers what $state=ready does and does not promise (it means "my own $description is current", never "my children are present", and no producer can make the latter true since children are commissioned out of band), and the three ways consumers get this wrong: the one-shot barrier that stops reconciling after startup, awaiting a $description that the content-hash suppression never sends, and inferring publish order across retained messages. Linked from the README.

Fixed

  • Device.refresh_tree() published a device's own $state before recursing to its children, so a device announced ready while the children its $description names had published nothing. on_connect calls refresh_tree() for SDK-owned clients on both the initial connect and every reconnect, so this was the normal path rather than an edge case. Description and nodes are now published first, then descendants, then this device's own state. That matches the add-child path (where a child publishes itself fully before its parent re-announces), and it makes a reconnect one atomic commit: after an ungraceful drop the LWT leaves the root retained as lost, Homie 5 makes every child of a lost root lost too, so a single final publish flips the whole tree at once. The set of messages is unchanged; only their order is (verified on a 37-device tree: 74 publishes and 74 unique topics before and after, identical topic sets). Note this narrows a producer-side window and is not a guarantee consumers may build on: $state=ready still means "my own $description is current", never "my children are present". Thanks to @​cayossarian. (#31)

v0.18.0

Fixed

  • ebus_default_override: the customizer's state-of-charge entry was keyed battery, which is not an eBus capability and never has been. State of charge lives in energy.ebus.capability.soc, so a conformant device typing its node that way resolved to the key soc, missed the table, and reached Home Assistant with neither a device_class nor a state_class on its SoC: precisely the ambiguous bare percent the entry exists to resolve. The entry is re-keyed soc and its property ids corrected to the ones the capability actually defines. state-of-charge, power and temperature are dropped: none is a property of any eBus capability, a battery's electrical power is meter/active-power (already covered), and eBus does not model pack temperature. Note this removes the battery key rather than aliasing it, so a publisher that copied the old example's non-conformant energy.ebus.capability.battery node type should move to soc. Thanks to @​cayossarian. (#27)
  • soc's energy properties are no longer emitted as accumulating registers. soe, total-energy-storage and loadup-headroom are reservoir levels that FALL on discharge, but unit inference sees Wh/kWh and says energy + total_increasing, under which Home Assistant reads every discharge as a meter reset and back-fills the drop as freshly consumed energy, corrupting the Energy dashboard's long-term statistics. They now carry HA's energy_storage device class with state_class: measurement, its level counterpart (same units, min/max/mean rather than a sum). info/nameplate-capacity gets the same correction, being a constant rather than a register. (#27)
  • Two meter entries, imported-active-energy and exported-active-energy, named properties the meter capability has never defined at any version; they are removed. They were behaviorally inert (a Wh property already infers energy + total_increasing), but they were table-versus-catalog drift of the same kind as the battery key. The four cumulative reactive/apparent registers (imported-reactive-energy, exported-reactive-energy, apparent-energy-imported, apparent-energy-exported) now carry state_class: total_increasing; Home Assistant has no energy device_class for varh/VAh, so they deliberately still emit without one, but the missing state class meant HA kept no long-term statistics for them at all. The per-phase power-factor-{a,b,c} forms are added alongside the system-level power-factor already present, being likewise unitless and so invisible to inference. (#27)

Changed

  • Dependency floor: ebus-mqtt-client>=0.4.0 (was >=0.3.0), and the bring-your-own-transport section of the README now documents the loop-native path it adds. The injection seam answers WHICH connection a producer publishes through, but paho's network loop still has to be pumped somewhere, and by default that is a background thread: the exact thing the seam's motivating host (Home Assistant) forbids. 0.4.0's MqttClient.asyncio_driver() pumps that loop on the caller's asyncio loop instead, so the README can now point at an answer where it previously only named the problem. The SDK does not import the driver and needs nothing from 0.4.0 at runtime (the full test suite passes unchanged against it); the floor rises because a consumer following the documented guidance needs it present. The driver module loads lazily and imports only the standard library plus paho, so a thread-mode consumer or a constrained build never loads it.
  • .ebus-spec.json re-synced against the specification (HEAD 922b9f8), which it had drifted 49 commits behind: framework 0.5 -> 0.7, capabilities/info 0.1 -> 0.2, capabilities/meter 0.1 -> 0.2, devices/utility-meter 0.3 -> 0.6, registries/capability-types 0.11 -> 0.19, registries/device-types 0.4 -> 0.5. The supports list is unchanged: no framework feature was added or removed across 0.5 -> 0.7. Two substantive corrections beyond the version numbers: grid, status, demand and power-quality are now pinned directly, because utility-meter 0.6 split them out of the utility-meter data model into standalone catalogs and the old notes rationale ("covered by pinning utility-meter 0.3") therefore no longer held; and soc 0.1 is pinned, the customizer having first-class knowledge of it. None of the version bumps required SDK code changes: each is additive-MAY or prose. device-types 0.5 is upstream catching up to its own data models: auditing this repo against the catalogs turned up four device-type identifiers the specification's data models declare but its registry had never listed, including energy.ebus.device.utility-meter, the type this repo's reference publisher emits. Reported by @​cayossarian, who ran the specification's drift-report.py against us. (#27)

Added

  • tests/test_catalog_drift.py: a regression harness for the class of defect above, rather than just its instance. The customizer table restates specification facts (capability names, property ids) and nothing checked that it still matched, which is why an invented key survived: the SDK's own tests asserted the table against itself by constructing the node type the table named. The check walks _CAPABILITY_META against the machine-readable capabilities/*.json catalogs in a sibling specification checkout and reports every mismatched name at once. It reads the specification's HEAD rather than the synced_commit pinned in .ebus-spec.json (the catalogs postdate that pin), and it SKIPS cleanly when no checkout is present, which is the case in CI today; EBUS_SPEC_DIR points it elsewhere. Proposed by @​cayossarian. (#27)

v0.17.0

Added

  • Device(async_loop=...): set the consumer's asyncio event loop once on the root instead of on every settable Property. It propagates to every property in the tree via add_node() / Node.add_property() (like QoS) and is inherited by child devices, so inbound /set callbacks for a tree with N settable properties no longer configure the loop N times. The per-Property async_loop argument still works and is preserved when no device-level loop is set. (#15)

Changed

  • Dependency floor: ebus-mqtt-client>=0.3.0 (was >=0.2.0). 0.3.0 ships the PEP 561 py.typed marker, so a downstream type checker resolves the re-exported MqttClient to the concrete class instead of Any, completing the transport Protocol typing (MqttTransport / MqttControllerTransport / MqttDeviceTransport) added in 0.16.0. The bump requires the typed transport rather than any new runtime API: 0.3.0 adds nothing the SDK's runtime needs beyond 0.2.0, and the test suite passes unchanged against it.

... (truncated)

Changelog

Sourced from ebus-sdk's changelog.

[0.19.0] — 2026-08-07

Added

  • Controller.is_tree_complete(root_id) and Controller.set_on_tree_ready_callback(): a first-class answer to "has the declared tree fully described itself?". $state=ready is a per-device signal meaning "my own $description is current" and never promised anything about descendants, so a consumer needing a whole-tree gate previously had to hand-roll one, and hand-rolling it is how you end up with a one-shot barrier that stops reconciling and silently misses a device commissioned later. Both are built as reconciling predicates instead: is_tree_complete() walks the declared tree on demand and flips back to False when a device declares a new child, and the callback is edge-triggered but re-arms, firing again for each settled shape. Completeness is about description, not liveness: a declared child that is lost has still told you what it is, so use get_effective_state() for liveness. A declared cycle terminates rather than hanging. (#37)

Changed

  • Tooling: the ruff lint selection is now declared explicitly (select = ["E4", "E7", "E9", "F"]) rather than inherited from ruff's defaults, and CI moves from ruff 0.15.21 to 0.16.1. Ruff 0.16 widened its default selection (UP, LOG, BLE, I, RUF and more) and began formatting Python code blocks embedded in Markdown, so an unchanged codebase reported 0 or 381 violations depending only on which ruff you happened to run, and four docs files showed phantom format diffs locally that CI never saw. Pinning the set decouples "what this project lints for" from "what version of ruff is installed"; extend-exclude = ["*.md"] keeps prose out of both check and format. Verified clean on both 0.16.1 and 0.15.21, with no source changes. Widening the rule set (the 381) is now a deliberate act rather than an upgrade side-effect. (#39)

  • Device.refresh_tree() is now explicitly best-effort: a descendant whose republish raises is logged (reason=deviceRefreshTreeChildFailed) and skipped, and the cascade continues. Property.publish_value() reaches the MQTT client without wrapping it and Node.publish() had no guard, so a bring-your-own-transport client that raised on one property aborted the walk from wherever it failed, taking out every later sibling, every ancestor's $state, and therefore the whole tree's reconnect. One sick device could keep an entire enclosure off the broker. Node.publish() now contains a raising property at the same grain. The policy is stated in the refresh_tree() docstring rather than left implicit; the exception is not re-raised. (#36)

  • Device.remove_node() and Device.delete_node() now cross-reference each other in their docstrings. They sit adjacent and differ in one consequential way (remove_node() drops the node from the schema but leaves its properties' retained value topics on the broker; delete_node() clears them too), and neither said so, which made picking the wrong one a silent way to strand retained topics. Docs only, no behaviour change. (#38)

Fixed

  • Node.delete_property() now republishes $description, as its mirror Node.add_property() always has. Deleting a property cleared the retained value topic but left the device in ready with a $description that still named the property, and nothing corrected it afterwards, so the broker held a self-contradicting device indefinitely. The two halves of the same API disagreed about whether mutating a node's property set is a structural change; it is. Deletions batch inside device.state_transition() exactly as additions do, so N deletions still collapse to one $description publish. (#35)

[0.18.1] — 2026-08-07

Added

  • https://github.com/electrification-bus/python-sdk/blob/main/doc/consuming-a-homie-tree.md: the controller/subscriber-side guide, and the counterpart to https://github.com/electrification-bus/python-sdk/blob/main/doc/building-a-proxy.md. It states the asymmetry the convention relies on and that this repo had never written down: a producer SHOULD minimize $state and $description transitions (quality-of-implementation, best-effort), while a consumer MUST react to every one of them, unconditionally (correctness). Covers what $state=ready does and does not promise (it means "my own $description is current", never "my children are present", and no producer can make the latter true since children are commissioned out of band), and the three ways consumers get this wrong: the one-shot barrier that stops reconciling after startup, awaiting a $description that the content-hash suppression never sends, and inferring publish order across retained messages. Linked from the README.

Fixed

  • Device.refresh_tree() published a device's own $state before recursing to its children, so a device announced ready while the children its $description names had published nothing. on_connect calls refresh_tree() for SDK-owned clients on both the initial connect and every reconnect, so this was the normal path rather than an edge case. Description and nodes are now published first, then descendants, then this device's own state. That matches the add-child path (where a child publishes itself fully before its parent re-announces), and it makes a reconnect one atomic commit: after an ungraceful drop the LWT leaves the root retained as lost, Homie 5 makes every child of a lost root lost too, so a single final publish flips the whole tree at once. The set of messages is unchanged; only their order is (verified on a 37-device tree: 74 publishes and 74 unique topics before and after, identical topic sets). Note this narrows a producer-side window and is not a guarantee consumers may build on: $state=ready still means "my own $description is current", never "my children are present". Thanks to @​cayossarian. (#31)

[0.18.0] — 2026-08-05

Fixed

  • ebus_default_override: the customizer's state-of-charge entry was keyed battery, which is not an eBus capability and never has been. State of charge lives in energy.ebus.capability.soc, so a conformant device typing its node that way resolved to the key soc, missed the table, and reached Home Assistant with neither a device_class nor a state_class on its SoC: precisely the ambiguous bare percent the entry exists to resolve. The entry is re-keyed soc and its property ids corrected to the ones the capability actually defines. state-of-charge, power and temperature are dropped: none is a property of any eBus capability, a battery's electrical power is meter/active-power (already covered), and eBus does not model pack temperature. Note this removes the battery key rather than aliasing it, so a publisher that copied the old example's non-conformant energy.ebus.capability.battery node type should move to soc. Thanks to @​cayossarian. (#27)
  • soc's energy properties are no longer emitted as accumulating registers. soe, total-energy-storage and loadup-headroom are reservoir levels that FALL on discharge, but unit inference sees Wh/kWh and says energy + total_increasing, under which Home Assistant reads every discharge as a meter reset and back-fills the drop as freshly consumed energy, corrupting the Energy dashboard's long-term statistics. They now carry HA's energy_storage device class with state_class: measurement, its level counterpart (same units, min/max/mean rather than a sum). info/nameplate-capacity gets the same correction, being a constant rather than a register. (#27)
  • Two meter entries, imported-active-energy and exported-active-energy, named properties the meter capability has never defined at any version; they are removed. They were behaviorally inert (a Wh property already infers energy + total_increasing), but they were table-versus-catalog drift of the same kind as the battery key. The four cumulative reactive/apparent registers (imported-reactive-energy, exported-reactive-energy, apparent-energy-imported, apparent-energy-exported) now carry state_class: total_increasing; Home Assistant has no energy device_class for varh/VAh, so they deliberately still emit without one, but the missing state class meant HA kept no long-term statistics for them at all. The per-phase power-factor-{a,b,c} forms are added alongside the system-level power-factor already present, being likewise unitless and so invisible to inference. (#27)

Changed

  • Dependency floor: ebus-mqtt-client>=0.4.0 (was >=0.3.0), and the bring-your-own-transport section of the README now documents the loop-native path it adds. The injection seam answers WHICH connection a producer publishes through, but paho's network loop still has to be pumped somewhere, and by default that is a background thread: the exact thing the seam's motivating host (Home Assistant) forbids. 0.4.0's MqttClient.asyncio_driver() pumps that loop on the caller's asyncio loop instead, so the README can now point at an answer where it previously only named the problem. The SDK does not import the driver and needs nothing from 0.4.0 at runtime (the full test suite passes unchanged against it); the floor rises because a consumer following the documented guidance needs it present. The driver module loads lazily and imports only the standard library plus paho, so a thread-mode consumer or a constrained build never loads it.
  • .ebus-spec.json re-synced against the specification (HEAD 922b9f8), which it had drifted 49 commits behind: framework 0.5 -> 0.7, capabilities/info 0.1 -> 0.2, capabilities/meter 0.1 -> 0.2, devices/utility-meter 0.3 -> 0.6, registries/capability-types 0.11 -> 0.19, registries/device-types 0.4 -> 0.5. The supports list is unchanged: no framework feature was added or removed across 0.5 -> 0.7. Two substantive corrections beyond the version numbers: grid, status, demand and power-quality are now pinned directly, because utility-meter 0.6 split them out of the utility-meter data model into standalone catalogs and the old notes rationale ("covered by pinning utility-meter 0.3") therefore no longer held; and soc 0.1 is pinned, the customizer having first-class knowledge of it. None of the version bumps required SDK code changes: each is additive-MAY or prose. device-types 0.5 is upstream catching up to its own data models: auditing this repo against the catalogs turned up four device-type identifiers the specification's data models declare but its registry had never listed, including energy.ebus.device.utility-meter, the type this repo's reference publisher emits. Reported by @​cayossarian, who ran the specification's drift-report.py against us. (#27)

Added

  • tests/test_catalog_drift.py: a regression harness for the class of defect above, rather than just its instance. The customizer table restates specification facts (capability names, property ids) and nothing checked that it still matched, which is why an invented key survived: the SDK's own tests asserted the table against itself by constructing the node type the table named. The check walks _CAPABILITY_META against the machine-readable capabilities/*.json catalogs in a sibling specification checkout and reports every mismatched name at once. It reads the specification's HEAD rather than the synced_commit pinned in .ebus-spec.json (the catalogs postdate that pin), and it SKIPS cleanly when no checkout is present, which is the case in CI today; EBUS_SPEC_DIR points it elsewhere. Proposed by @​cayossarian. (#27)

[0.17.0] — 2026-08-02

Added

  • Device(async_loop=...): set the consumer's asyncio event loop once on the root instead of on every settable Property. It propagates to every property in the tree via add_node() / Node.add_property() (like QoS) and is inherited by child devices, so inbound /set callbacks for a tree with N settable properties no longer configure the loop N times. The per-Property async_loop argument still works and is preserved when no device-level loop is set. (#15)

... (truncated)

Commits

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file python Pull requests that update python code labels Aug 7, 2026
Bumps [ebus-sdk](https://github.com/electrification-bus/python-sdk) from 0.1.5 to 0.19.0.
- [Release notes](https://github.com/electrification-bus/python-sdk/releases)
- [Changelog](https://github.com/electrification-bus/python-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/electrification-bus/python-sdk/commits/v0.19.0)

---
updated-dependencies:
- dependency-name: ebus-sdk
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot changed the title build(deps): bump ebus-sdk from 0.1.5 to 0.17.0 build(deps): bump ebus-sdk from 0.1.5 to 0.19.0 Aug 12, 2026
@dependabot
dependabot Bot force-pushed the dependabot/pip/ebus-sdk-0.17.0 branch from fa50615 to 9d4369d Compare August 12, 2026 04:02
@cayossarian
cayossarian merged commit 8fafeea into main Aug 19, 2026
2 checks passed
@dependabot
dependabot Bot deleted the dependabot/pip/ebus-sdk-0.17.0 branch August 19, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant