2.11.0b0
Pre-releaseWhat's Changed
Integration
-
The button blueprints only ask the CCU for direct links when something reads the answer. The lookup is by far the slowest step in these automations — a CCU round trip on every keypress, ahead of the action — and its result feeds nothing but the two direct-link options. It ran unconditionally all the same, so an installation that never enabled either option paid for it on every press, and the action could never be faster than the CCU replied. It now runs only when one of the options is on and the pressed button has an action configured; with both off there is no CCU call left in the action's way.
What that is worth depends entirely on the CCU. On one reporter's installation the integration delivered every keypress in about 6 ms and the seven measurable blueprint runs completed in 16–68 ms, while the same installation's RPC latency averaged 9.8 ms across 9571 calls with a maximum of 6.1 s — so the round trip is usually free and occasionally very expensive. A keypress arriving as a bundle (
PRESS_SHORT+PRESS_LONG+PRESS_LONG_RELEASE) used to cost one lookup each.Everything reading the answer moved into the same branch as the lookup, because variables set in a nested sequence do not escape it. That the
stopfor the skip option still ends the whole run from in there is asserted rather than assumed, as is the notification path. Reported as aiohomematic#3382, where the delay was attributed to Home Assistant 2026.9 — the lookup has been in these blueprints unchanged since 2026-04-14 and predates both that release and this one -
Fix: button presses reach automations again on Home Assistant 2026.9. Every
homematic.keypressandhomematic.impulseevent stopped being fired, so keypress blueprints, device triggers and any automation listening for a button lost their trigger — silently, with no trace to look at, while the event entity kept updating and the log showed only a DEBUG line per press:The EVENT could not be validated. ['channel_no'], required key not provided.CLICK_EVENT_SCHEMAwas derived fromBASE_EVENT_DATA_SCHEMAand undid the parts a click event does not have —cleanup_click_event_datafoldschannel_noandparameterintosubtypeandtypeand drops the originals — by extending it withvol.Removemarkers of the same names. That relies on aRemovemarker replacing theRequiredone it shadows, which holds for voluptuous, whereRemove("x") == Required("x")isTrue(measured on 0.13.1, 0.14.2, 0.15.0, 0.15.2 and 0.16.0). Home Assistant 2026.9 installs the probatio shim in voluptuous's place, and thereRemove("x") == Required("x")isFalseandRemovecarries its own__hash__, soextendkeeps both markers and the schema went on requiring thechannel_nothe cleanup had just removed (probatio 0.11.3). OnlyRemoveis affected — aRequiredreplaced by anOptionalstill resolves the way it does under voluptuous.The schema is now spelled out instead of derived, so it states what a click event is and depends on no marker-replacement semantics.
subtypevalidates throughvalidate_channel_no, which returns anintin range — blueprints compare the channel numerically. Reported as aiohomematic#3374 -
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 - The click event path is covered end to end now.
cleanup_click_event_dataandis_valid_eventeach had their own test, but nothing validated the cleanup's output againstCLICK_EVENT_SCHEMA, so a full green suite said nothing while every button press was being rejected. Three cases pin the schema against a payload shaped the wayControlUnit._on_device_triggerbuilds it, and one more asserts the press arrives on Home Assistant's event bus — the entity state and the bus event are independent consumers of the same trigger, and only the bus event drives automations - The second pair of duplicated dependency pins is guarded now. Five lint tools are pinned twice — as a requirement in
requirements_test_pre_commit.txtand as a hookrevin.pre-commit-config.yaml— and nothing tied the two together, becausemake upgrade-depsranpurover the requirements files and never touched the hook revisions. Both had already drifted: codespell ran atv2.4.2against a pinned2.4.3, python-typing-update atv0.7.3againstv0.8.1, so the hook disagreed with the tool the repository claimed to use.make upgrade-depsmoves both sides now, andtests/test_dependency_pins.pyfails by name when they part again — the same guard the manifest/CI pair already had, for the same reason - Dependabot covers the Python requirements as well, not only GitHub Actions. It skips what it cannot move correctly on its own: the four backend packages, which are ours and get bumped together with the manifest pin, the changelog entry and the code that needs them; and the five tools pinned twice, which
make upgrade-depsmoves on both sides at once. That leaves eight third-party test and lint dependencies to weekly grouped PRs - 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.9.1
- A
HmIP-HEATINGgroup no longer stays frozen at its restored state after a CCU restart (aiohomematic#3376). The climate entity gated its validity onSET_POINT_MODE, which the CCU sends only when the operating mode actually changes — whileACTUAL_TEMPERATUREandSET_POINT_TEMPERATUREkeep arriving. A heating group whose mode had not been touched since the restart therefore kept showing frozencurrent_temperatureandtarget_temperature, while its siblingsensor.*entities updated from the very same events. Groups onVirtualDeviceshave no second source to fall back on: the ReGa bulk fetch skips the mode data point and there is no per-parametergetValuefor a virtual group. Validity now follows temperature and setpoint, andmodeuses its existingAUTOfallback untilSET_POINT_MODEarrives. A group missingACTUAL_TEMPERATUREfrom the bulk result too still waits for its first event - A system variable whose declared type does not match its value is kept as text instead of being dropped (aiohomematic#3377).
SysVar.getAllreports a type per variable and it was trusted: a value list holding a single text entry returns that entry rather than its index,int()raised, the record was discarded, and no entity ever appeared — with anERRORon every 60 s scan (4075 of them in the submitted log, for one variable). Disabling the entity did not help, because that is resolved before the value is parsed. The variable now falls back toSTRINGand keeps its raw value; it also dropsextended_sysvar, so a type that could not be verified never yields a writable data point. The log is aWARNINGnow, once per variable per client, naming the declared type
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.3
openccu-loom backend (Beta) only — on the direct-CCU backend the client is not loaded, so this bump has no runtime effect there.
Three client releases land in this integration release; what follows is the net effect against 2.10.0, because the intermediate ones never shipped from here.
- A daemon version mismatch no longer refuses the connection. The client used to demand the same API major and at least the same minor as the version it was generated against, and 2026.9.1 would have rejected an older daemon outright. It reports the difference now and connects; the only condition that still refuses is a daemon missing a capability this integration declares it needs. This matters in both directions, and the second one bit on ordinary releases: HACS updates this integration before you update the daemon, which used to make the daemon older by a minor and fail setup for a contract that was fully present. The advice to stay on 2.10.0 when the daemon cannot be updated no longer applies.
- A daemon that adds a value no longer breaks decoding. Generated enums accept a value this build has not seen and carry it through as the raw string, instead of rejecting the whole response. Three daemon response shapes also stopped declaring themselves closed to unknown fields, so a field added later no longer does the same.
- When the two genuinely cannot work together, the config flow now says so with its own message rather than reporting a connection failure — see the integration entry above.
- CUxD entities re-key once; the migration that carries them is the integration entry above.
- A week profile reported its active profile one index too low. Regenerated wire bindings (daemon v0.73.0) carry the fix.
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
Against 2.10.0: mypy <=2.1.0 → <=2.3.1, prek 0.5.0 → 0.5.2, pylint 4.0.7 → 4.0.8,
pytest-homeassistant-custom-component-framework 1.0.47 → 1.0.52, ruff 0.16.4 → 0.16.6, and
aiohomematic-test-support 2026.8.5 → 2026.9.1 following the runtime pin. Tooling only, no packaged
behaviour changes.
The mypy bump is the first one Dependabot raised here: this release put the Python requirements under it,
having covered only GitHub Actions before.