Skip to content

feat: publish a device tree under any Homie 5 domain - #62

Merged
dcj merged 1 commit into
mainfrom
feat/device-homie-domain
Aug 20, 2026
Merged

feat: publish a device tree under any Homie 5 domain#62
dcj merged 1 commit into
mainfrom
feat/device-homie-domain

Conversation

@dcj

@dcj dcj commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Closes #61.

Device gains homie_domain=, defaulting to ebus. Ten publisher-side topic constructions now resolve it instead of the module constant.

What the domain now covers

  • property value topics, and /set subscription topics
  • $state and $description
  • the retraction topics delete() and delete_all_from_mqtt() clear
  • will() and the LWT installed on an SDK-owned client, which stay identical to each other by construction

Verified by listing every topic construction in homie.py: the nine Device / Property sites all resolve the domain now, and the ones left on the constant are Controller (already configurable) and the homie version field, which is not a topic.

The inbound /set check

Property._settable_callback compared the received topic's domain against EBUS_HOMIE_DOMAIN. Left alone, a device published under homie would have subscribed to the correct /set topic and then silently rejected every command that arrived, which is a worse failure than not supporting the domain at all. It now compares against the tree's own domain, with a test in each direction: accepted on the tree's domain, ignored on a foreign one.

It belongs to the tree

Like the MQTT connection and the QoS. A child under a different domain would sit outside its own root's subtree, and the root's Last Will (a single retained publish on the root's $state) could not cover it.

So only a root stores it, descendants read it through the new Device.homie_domain(), and a child passing its own gets a ValueError, matching the existing parent= plus mqtt_cfg= rule. It is refused even when the value would have matched (test_a_child_of_a_default_domain_root_is_also_refused), because the rule is structural rather than a value check, and a silently-dropped domain would surface as topics on the wrong prefix rather than as an error.

Compatibility

The default is unchanged, EBUS_HOMIE_DOMAIN stays exported, and a publisher that never mentions the parameter is byte-identical on the wire. test_default_domain_is_ebus and test_explicit_ebus_is_the_same_as_omitting_it pin that.

The Device docstring's homie_domains config for future use, not currently supported by this code stub is replaced with what to actually do.

One test-double fix

_make_wired_property in test_homie_device.py builds a MagicMock device, which returned a MagicMock from homie_domain() and broke three settable-callback tests. The double now answers it. Fixing the double rather than making production code defensive about mocks.

Out of scope

Multi-domain on one Controller. Today one Controller monitors one domain, so watching both trees means two of them. Noted in #61 as a separate question.

Verification

  • 653 tests pass (636 before, 17 new)
  • ruff check and ruff format --check clean
  • markdownlint clean on CHANGELOG.md and README.md
  • README gains a "Publishing under a different Homie domain" section beside Device Trees, since the domain is per-tree

🤖 Generated with Claude Code

The SDK could CONSUME any Homie 5 tree and PRODUCE only an eBus one.
Controller has always taken homie_domain=, uses it for subscriptions,
set_property and $broadcast, and even parses the domain back out of a
received topic. Device had no such parameter: every topic it derived came
from the EBUS_HOMIE_DOMAIN constant at ten sites across Device, Node and
Property, plus the Last Will. Its own docstring carried the stub
"homie_domains config for future use, not currently supported by this
code", which is now replaced by what to actually do.

Nothing about eBus changes. Energy devices keep publishing under `ebus`,
which the specification mandates and which remains the default, so a
publisher that never mentions the parameter is byte-identical on the
wire. What this buys is that the same SDK can also publish non-energy
devices under the standard `homie` domain: the difference between an eBus
library and a Homie 5 library that defaults to eBus.

The domain covers everything a tree derives: property values, /set
subscriptions, $state, $description, the retraction topics delete() and
delete_all_from_mqtt() clear, and both will() and the LWT installed on an
owned client.

Inbound /set validation had to follow. Property._settable_callback
compared the received domain against EBUS_HOMIE_DOMAIN, so a device
published under `homie` would have subscribed to the right topic and then
silently rejected every command that arrived.

It is a property of the TREE, not of a device, exactly like the
connection and the QoS: a child under a different domain would sit
outside its own root's subtree, and the root's Last Will (one retained
publish on the root's $state) could not cover it. Only a root stores it,
descendants read it through the new Device.homie_domain(), and a child
passing its own is refused with a ValueError as a child passing its own
mqtt_cfg= already is. Refused even when the value would have matched:
the rule is structural, and a silently-dropped domain surfaces as topics
on the wrong prefix rather than as an error.

One test-double fix: _make_wired_property builds a MagicMock device,
which returned a MagicMock from homie_domain() and broke three
settable-callback tests. The double now answers it, rather than the
production code being made defensive about mocks.

Closes #61

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dcj
dcj merged commit 726d23f into main Aug 20, 2026
5 checks passed
@dcj
dcj deleted the feat/device-homie-domain branch August 20, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Device is hardcoded to the ebus domain, so the SDK can consume any Homie 5 tree but publish only an eBus one

1 participant