Skip to content

Stable entity ids: read-only twin + tier-gated control (2.4.0) - #28

Merged
basmeerman merged 1 commit into
mainfrom
feat/stable-ids-with-control-entities
Sep 16, 2026
Merged

basmeerman merged 1 commit into
mainfrom
feat/stable-ids-with-control-entities

Conversation

@basmeerman

Copy link
Copy Markdown
Owner

Why

A setting changed component with its write tier — sensor ↔ number, binary_sensor ↔ switch. The domain is part of the entity id, so each toggle was a delete plus a register: every hand-written dashboard, automation, template and statistics series referencing the old id broke, silently. Worse, HA restores deleted registry rows by unique_id, so on a 6-pack install enabling both tiers flipped 204 entities and brought 156 pre-2.2.0 ids back from the dead, needing a second rename_entities.py --apply to repair.

What changes

Each setting is published as two entities:

Entity Exists
Read-only twin sensor.bms_1_maximum_charge_current always
Control number.bms_1_maximum_charge_current_control only while its write tier is on

Packed bits likewise: binary_sensor.bms_1_smart_sleep + switch.bms_1_smart_sleep_control.

The twin's id is stable for the life of the install. The control appears when you enable a tier and disappears when you disable it — and nothing else moves. Both share the existing control/<param> state topic; the control's command_topic is the existing control/<param>/set, so the write path HA → MQTT → RS485 is unchanged, as is the write executor's tier gating.

Why two entities rather than one

MQTT discovery cannot express a read-only number or switch: command_topic is vol.Required in MQTT_RW_SCHEMA, which both platforms extend. I checked that specifically, including that MQTT_ENTITY_COMMON_SCHEMA does not later downgrade it — voluptuous extend() overrides keys, and the vol.Optional(CONF_COMMAND_TOPIC) elsewhere belongs to DEVICE_DISCOVERY_SCHEMA. HA also has no per-entity read-only flag; discussion #13 is open with nothing shipped, and its author notes "unavailable" is not a substitute because it hides the value.

Upgrade

The control topic older versions published under the setting's own object_id is now always cleared, so those entities vanish on first start instead of lingering or being restored by unique_id.

Hand-written dashboards referencing a setting's number.* / switch.* id need pointing at either the stable read-only id (to display) or the new …_control id (to change).

Test plan

  • ruff, mypy, pytest — 575 passed, 100 % branch coverage
  • Drift check across all four tier combinations: 100 / 111 / 123 / 134 entities (+11 basic, +23 safety)
  • With both tiers off the generated dashboard is byte-identical to 2.3.0
  • Tests pin: twin published whatever the tier, control only when on, legacy topic always cleared, both twins of a hidden unverified entity removed
  • On HA: enable a tier, confirm the control appears and writes reach the BMS while the twin keeps its id
  • On HA: disable it again, confirm the control disappears and nothing else changes

Closes #23

🤖 Generated with Claude Code

A setting used to change component with its write tier: sensor when off,
number when on. The domain is part of the entity id, so Home Assistant
treated every toggle as a different entity — deleting one, registering
another. Dashboards, automations, templates and statistics referencing
the old id broke silently, and HA's registry restored obsolete ids by
unique_id: enabling both tiers on a 6-pack install flipped 204 entities
and resurrected 156 pre-2.2.0 ids.

Each setting is now two entities:

- a read-only sensor / binary_sensor, published whatever the tier, whose
  id is stable for the life of the install;
- a number / switch control with its own object_id and name
  (`<object_id>_control`, "<name> control"), published only while the
  setting's write tier is on.

Both share the existing control/<param> state topic, and the control's
command_topic is the existing control/<param>/set — the write path from
Home Assistant through MQTT to RS485 is unchanged, as is the write
executor's tier gating.

MQTT discovery cannot express a read-only number or switch:
command_topic is Required in MQTT_RW_SCHEMA for both, and HA has no
per-entity read-only flag (home-assistant/discussions#13, still open).
Two entities are therefore the only way to keep an id stable across a
tier toggle.

The control topic older versions published under the setting's own
object_id is now always cleared, so those entities disappear on upgrade
instead of lingering or being restored.

Dashboard, entity-drift check and the rename tool all follow: the
generated dashboard points at the control when a tier is on and at the
twin otherwise, and with both tiers off its output is byte-identical to
2.3.0.

Add-on version 2.4.0.

Closes #23

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@basmeerman
basmeerman merged commit 7b117dd into main Sep 16, 2026
4 checks passed
@basmeerman
basmeerman deleted the feat/stable-ids-with-control-entities branch September 16, 2026 22:49
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.

Toggling a write tier changes entity domains, which drops and recreates entities (and can resurrect stale ids)

1 participant