What's Changed
Integration
-
Diagnostics say which backend an installation runs, and on what. The dump gained a
deploymentblock: the backend in use, the number of CUxD devices, and — on the openccu-loom backend, read from the CCU list the adapter already fetches — how many centrals the daemon mediates. Two questions an architecture review had filed as "not measurable without telemetry" are answered by a dump a user attaches to a bug report; nothing is collected or sent anywhere. The central count is omitted when the backend does not publish it, rather than defaulting to a zero that would read as "one CCU" -
Fix: switching backends no longer leaves every device behind and builds a second one beside it. Both backends compose the HA device identifier as
<address>@<central>-<interface>, but they disagree on the leading component: aiohomematic uses the Home Assistant instance name, the openccu-loom daemon its own CCU name (OttoDev-HmIP-RFagainstOtto-HmIP-RF, measured against daemon 0.68.1). Every device therefore re-keyed on a backend switch. The entities moved with it — their unique_ids are migrated — but thedevice_iddid not, and that is what an area, a custom device name and every device-based automation hang on. The old entry stayed behind holding whatever had not migrated with it.The identifier is now composed by the integration itself, from the instance name and the interface type both backends report separately. On the direct-CCU backend that is byte-identical to what aiohomematic produced, so nothing moves there — the migration is a no-op for every installation that never touched the loom backend. A registry keyed the daemon's way is rewritten before the platforms come up, where the target key is still free and the rename is plain. Where a switch already happened and both entries exist, the older one wins: the newer entry's entities and sub-devices move over to it and it is removed, so the
device_idthe automations use is the one that survives.Reported from a live installation, where the switch turned 190 device entries into more than 380 — every base device, every sub device and every schedule device got a twin, with the entities on the new one and whatever had not migrated left on the old.
The identifier also stopped being a routing key. Device actions, device triggers, the device-based services and the device-removal path used to parse the backend's interface id back out of it; they now find the device by its address and read the interface id off the device. Without that they would have failed silently on the openccu-loom backend the moment the identifier stopped carrying the daemon's id
-
Fix: system variable and program entities keep their history when their key moves onto the CCU id. Both backends used to key these two families on the slug of a renameable name, so a rename in the CCU WebUI re-keyed the entity and took its history, area and every automation with it. Both moved onto the id the CCU already carries — aiohomematic in
2026.8.8, openccu-loom in0.68.0— and without this pass the existing registry entries would have orphaned on that upgrade.The old key is reconstructed here rather than transported over the wire: the data point carries
legacy_name, and the old key was the slug of it. It runs deferred, 60 s after the start and immediately before the orphan cleanup, because it needs loaded hub data — which means the freshly keyed twins already exist. That collision is resolved rather than skipped: the twin is seconds old and holds nothing, the registry entry holds the history, so the twin gives way. But a live entity binds to its registry entry once, atasync_add_entities, so the historied entry the rename hands the key to has no entity behind it — which is why a migrating pass schedules one config entry reload: the entities re-bind within seconds, with their history, instead of only on the next restart. That reload is asked for once per entry and the record is kept inhass.data, which survives it; a second migrating pass logs a warning and leaves the re-bind to the next restart, because reaching it would mean the pass did not converge and reloading again would not converge either. Idempotent, and a no-op on an installation that never carried a slug key.The orphan cleanup that follows in the same pass now spares hub entries the migration has yet to take. Should it decline —
get_hub_data_points()raising, or a data point yielding no old key — the historied entry keeps its slug key, and a hub entry has no device address, so the sweep read it as an orphan and deleted it with its history, name and area. It now rebuilds the pre-id key for every live hub data point and keeps any entry still holding one. The next start retries the migration; deletion has no next start -
Fix: CUxD entities keep their history now that their key carries the central id. CUxD hands out the same synthetic addresses on every CCU, so two CCUs bridged into one Home Assistant declared byte-identical unique_ids for their CUxD data points and Home Assistant kept only the first. aiohomematic scopes the family by central id from
2026.8.7on, as the openccu-loom daemon always did — so every entity keyed before that adoption moves once, on both backends, because the loom client rebuilds through aiohomematic the keys the daemon does not stamp (custom data points, week profiles, the combined duration number, the device-update entity, event groups). Without the pass those entities orphan: they keep their history, area and customisations while the platform spawns freshly keyed duplicates beside them, and the orphan sweep eventually deletes the originals. Idempotent, and an installation without CUxD devices sees nothing happen -
Fix: a sub-device channel without a group master could make its device its own via device. With sub devices enabled, the via device was moved up to the Homematic device as soon as a channel reported itself as part of a multi-channel group — before the group master, which supplies the sub-device identifier, was resolved. Without a master the identifier stayed on the device, so device and via device were the same. HA used to ignore such a self-reference with a log line; since 2026.9 it raises a
HomeAssistantErrorthat no platform catches, which would take down the whole platform setup (device registry follow-up changes). The split is now conditional on the group master, so a channel without one simply stays on its device, below the central -
Fix: setup no longer retries forever against an incompatible openccu-loom daemon. Only an authentication failure had its own case, so a daemon speaking a contract this build cannot ended up on the generic "not ready" path and was retried indefinitely with nothing saying why. It is reported as a setup error now. openccu-loom backend only
Development
- Covers and sirens dispatch on aiohomematic's category protocols —
GarageDataPointProtocol,TiltCoverDataPointProtocol,CoverDataPointProtocol,SoundPlayerDataPointProtocol— instead of on the concrete custom data point classes. Both backends satisfy them structurally, so these two platforms no longer decide which entity a data point becomes from the per-backend class tuples inbackend_types.py. The one case the protocols do not carry stays explicit: an IP blind configured as a shutter presents no tilt and becomes a plain cover, read fromoperation_mode, which lives on the concrete class backend_types.pynames the loom twin it could not find instead of failing mute, and its docstring no longer claims a mapping the backend surface contract test disproves- Two test gaps closed. The hub key migration now runs against a real entity registry — two system variables whose names differ only in punctuation slug to one key, and the pass has to hand the history to one entity and leave the other alone — rather than against the key rebuild in isolation. And the hub singletons (alarm and service messages, inbox, connectivity, metrics, install mode), the per-device update data points, the event groups and the alarm panels are covered on spawn: they are announced through
DataPointsCreatedEventrather than enumerated in a platform's setup tail, which is why a full green suite said nothing when a change to the setup order dropped all of them mid-release - Two documentation findings from the implementation review of the architecture memo corrected in
docs/architecture-comparison-aiohomematic-vs-loom.md: three places still argued from a superseded "~14k LOC thin adapter" figure — including §1's central finding and the §7 evaluation matrix a backend decision gets read from — where the client is 27,312 LOC today (21,730 hand-maintained, 5,587 generated wire types), which drops the footprint score from 9 to 7; and the metaclass claimbackend_types.pydisproved in this repository is gone, so the two no longer assert opposite things about the same settled question - Two more test gaps closed. The CUxD registry pass had only its key arithmetic covered, never the registry walk — it runs on every start-up, on both backends, and touches every CUxD entity on a direct-CCU install; four cases now pin it, including a taken target key being skipped rather than raised. And
_pair's warning, the only signal that exists when this integration andopenccu-loom-clientdisagree on a type, is pinned by three cases so it cannot be removed again unnoticed - Beyond the entries above, the integration's own changes in this release are openccu-loom backend (Beta) work; its details stay out of scope for this changelog until it leaves Beta
Dependencies
Bump aiohomematic to 2026.8.8
The manifest still pinned 2026.8.7 while CI already ran 2026.8.8 — across the very release that adopts 2026.8.8's re-keying of system variables and programs. The registry migration added for it was therefore tested against a backend that had re-keyed and would have shipped against one that had not.
tests/test_dependency_pins.py now pins the two files together, the way the sister client repository does. Bite proof: restoring 2026.8.7 in the manifest fails it by name.
Bump openccu-loom-client to 2026.9.1
openccu-loom backend (Beta) only — on the direct-CCU backend the client is not loaded, so this bump has no runtime effect there.
- It raises the minimum daemon: the client is generated against daemon API
10.1.0, up from7.13.0in 2.10.0, and checks it at connect time, so an older daemon is rejected outright. Installations that cannot update the daemon should stay on 2.10.0. - CUxD entities re-key once; the migration that carries them is the integration entry above.
openccu-loom-typesis no longer a separate dependency — it is folded into the client — so this bump removes a package from the install rather than pinning one.
Bump aiohomematic to 2026.8.6
- One added field,
InboxDeviceData.awaiting_release, for the openccu-loom backend's onboarding states. It defaults to false and no direct-CCU path sets it
Bump aiohomematic-config to 2026.8.1
- Packaging and tooling only, no behaviour change: the package raises its own
aiohomematicrequirement to>=2026.8.5, is marked production/stable, and updates its development dependencies
Development dependencies
prek 0.5.1, pytest-homeassistant-custom-component-framework 1.0.50.