Skip to content

Optional cleanup of discovery configs for removed packs (2.3.0) - #27

Merged
basmeerman merged 1 commit into
mainfrom
feat/clean-orphaned-discovery
Sep 16, 2026
Merged

basmeerman merged 1 commit into
mainfrom
feat/clean-orphaned-discovery

Conversation

@basmeerman

Copy link
Copy Markdown
Owner

Why

Retained MQTT discovery messages outlive the bridge. 2.1.1 added per-entity removals, but they are emitted per BmsRunner — so a pack dropped from bms_ids has no runner, and its ~100 entities stay in Home Assistant as dead, unavailable entries that are re-processed on every restart. Changing bms_name_prefix orphans every pack the same way.

What changes

New option, off by default:

clean_orphaned_discovery: true

On startup the bridge reads the retained configs under its own discovery_prefix, and clears those whose device id matches <bms_name_prefix>_<slave id>_device_* for a slave id no longer in bms_ids. Each cleared topic is logged, plus a summary.

Design

The decision and the I/O are split, because this repo gates at 100 % branch coverage and run() is excluded as top-level glue. Putting the logic inside the MQTT loop would have made the part that decides what to delete untestable.

  • mqtt.orphan_removals(topics, *, settings) — pure, unit-tested. Leaves alone: packs still polled, other integrations, another jkbms2mqtt under a different discovery prefix, unknown components, malformed device ids, and already-cleared (empty) configs.
  • app._clean_orphaned_discovery() — subscribe, collect, publish, log. Under the existing # pragma: no cover - MQTT glue.

Ordering matters. aiomqtt hands out a single shared message queue (MessagesIterator over Client._queue), so draining it while the /set topics were subscribed could swallow a user's write. The cleanup therefore runs before those subscriptions, when no command can exist yet. Ending the collect window with wait_for is safe: Queue.get() doesn't drop an item when cancelled.

Why opt-in. Two bridges can share one broker. A pack this instance doesn't poll may legitimately belong to the other one, and clearing its configs would delete that instance's entities.

Test plan

  • 9 unit tests for orphan_removals (removed pack cleared; configured packs untouched; foreign integration, foreign prefix, unknown component, malformed ids all ignored; empty broker; renders as an empty retained payload)
  • Option defaults to false, present in config.yaml options + schema, round-trips into Settings
  • ruff, mypy, pytest — 572 passed, 100 % branch coverage
  • Dashboard drift check passes; generated artifacts byte-identical (bridge-side change only)
  • On a live broker: enable the option with a pack removed from bms_ids, confirm the log lists the cleared topics and the entities disappear from Home Assistant
  • Confirm a second bridge under a different discovery_prefix is untouched

Closes #25

🤖 Generated with Claude Code

Home Assistant learns about entities from retained MQTT discovery
messages, which survive on the broker until someone clears them. 2.1.1
added per-entity removals, but they are emitted per BmsRunner, so a pack
dropped from bms_ids has no runner and keeps ~100 dead entities in Home
Assistant, re-processed on every start. Changing bms_name_prefix orphans
every pack the same way.

New opt-in option `clean_orphaned_discovery` (default false). On startup
the bridge reads the retained configs under its discovery prefix and
clears those whose device id is ours (`<bms_name_prefix>_<id>_device_*`)
for an id no longer in bms_ids. Every cleared topic is logged.

Design:

- `mqtt.orphan_removals()` decides what gets deleted. Pure and
  unit-tested: foreign integrations, another bridge's discovery prefix,
  unknown components and malformed device ids are all left alone, as are
  the packs still being polled.
- `app._clean_orphaned_discovery()` does the broker conversation only,
  under the existing no-cover pragma for MQTT glue.
- It runs before the /set subscriptions: aiomqtt hands out one shared
  message queue, so draining it while command topics were subscribed
  could swallow a user's write. Until we subscribe, none can arrive.

Off by default because two bridges can share one broker, where a pack
this instance does not poll may legitimately belong to the other.

Add-on version 2.3.0.

Closes #25

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@basmeerman
basmeerman merged commit 367f7b0 into main Sep 16, 2026
4 checks passed
@basmeerman
basmeerman deleted the feat/clean-orphaned-discovery branch September 16, 2026 22:22
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.

Discovery configs of a pack removed from bms_ids are never cleared

1 participant