Skip to content

2.11.0b0

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 04 Sep 11:59
d12c643

What'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 stop for 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.keypress and homematic.impulse event 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_SCHEMA was derived from BASE_EVENT_DATA_SCHEMA and undid the parts a click event does not have — cleanup_click_event_data folds channel_no and parameter into subtype and type and drops the originals — by extending it with vol.Remove markers of the same names. That relies on a Remove marker replacing the Required one it shadows, which holds for voluptuous, where Remove("x") == Required("x") is True (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 there Remove("x") == Required("x") is False and Remove carries its own __hash__, so extend keeps both markers and the schema went on requiring the channel_no the cleanup had just removed (probatio 0.11.3). Only Remove is affected — a Required replaced by an Optional still 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. subtype validates through validate_channel_no, which returns an int in range — blueprints compare the channel numerically. Reported as aiohomematic#3374

  • Diagnostics say which backend an installation runs, and on what. The dump gained a deployment block: 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-RF against Otto-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 the device_id did 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_id the 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 in 0.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, at async_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 in hass.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.7 on, 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 HomeAssistantError that 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 in backend_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 from operation_mode, which lives on the concrete class
  • backend_types.py names 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 DataPointsCreatedEvent rather 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 claim backend_types.py disproved 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 and openccu-loom-client disagree 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_data and is_valid_event each had their own test, but nothing validated the cleanup's output against CLICK_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 way ControlUnit._on_device_trigger builds 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.txt and as a hook rev in .pre-commit-config.yaml — and nothing tied the two together, because make upgrade-deps ran pur over the requirements files and never touched the hook revisions. Both had already drifted: codespell ran at v2.4.2 against a pinned 2.4.3, python-typing-update at v0.7.3 against v0.8.1, so the hook disagreed with the tool the repository claimed to use. make upgrade-deps moves both sides now, and tests/test_dependency_pins.py fails 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-deps moves 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-HEATING group no longer stays frozen at its restored state after a CCU restart (aiohomematic#3376). The climate entity gated its validity on SET_POINT_MODE, which the CCU sends only when the operating mode actually changes — while ACTUAL_TEMPERATURE and SET_POINT_TEMPERATURE keep arriving. A heating group whose mode had not been touched since the restart therefore kept showing frozen current_temperature and target_temperature, while its sibling sensor.* entities updated from the very same events. Groups on VirtualDevices have no second source to fall back on: the ReGa bulk fetch skips the mode data point and there is no per-parameter getValue for a virtual group. Validity now follows temperature and setpoint, and mode uses its existing AUTO fallback until SET_POINT_MODE arrives. A group missing ACTUAL_TEMPERATURE from 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.getAll reports 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 an ERROR on 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 to STRING and keeps its raw value; it also drops extended_sysvar, so a type that could not be verified never yields a writable data point. The log is a WARNING now, 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-types is 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 aiohomematic requirement 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.00.5.2, pylint 4.0.74.0.8,
pytest-homeassistant-custom-component-framework 1.0.471.0.52, ruff 0.16.40.16.6, and
aiohomematic-test-support 2026.8.52026.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.