Do not bind clusters whose entities are not created#783
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #783 +/- ##
=======================================
Coverage 97.41% 97.41%
=======================================
Files 50 50
Lines 10419 10419
=======================================
Hits 10150 10150
Misses 269 269 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR changes ZHA device entity discovery so that entities prevented via quirks v2 no longer contribute to aggregated cluster configuration, with the goal of avoiding binding/reporting setup for clusters tied only to those prevented entities.
Changes:
- Skip adding quirk-prevented entities to
_discovered_entitiesso they no longer driveaggregate_cluster_configs(binding/reporting/cluster hooks). - Add a regression test asserting a prevented entity no longer triggers a cluster bind while a non-prevented sibling entity still does.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
zha/zigbee/device.py |
Alters discovery flow so quirk-prevented entities don’t contribute to cluster binding/reporting aggregation. |
tests/test_device.py |
Adds a test to verify quirk-prevented entities no longer trigger cluster binding. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # A quirk-prevented entity must not drive cluster binding or | ||
| # reporting either, so it's kept out of `_discovered_entities` | ||
| # (the source for `aggregate_cluster_configs`) entirely. | ||
| if self._is_entity_removed_by_quirk(entity): | ||
| continue | ||
|
|
||
| self._discovered_entities.append(entity) | ||
|
|
| zigpy_dev = await zigpy_device_from_json( | ||
| zha_gateway.application_controller, | ||
| "tests/data/devices/innr-sp-240.json", | ||
| ) | ||
|
|
||
| level = zigpy_dev.endpoints[1].level | ||
| on_off = zigpy_dev.endpoints[1].on_off | ||
|
|
||
| with ( | ||
| patch.object(level, "bind", wraps=level.bind) as mock_level_bind, | ||
| patch.object(on_off, "bind", wraps=on_off.bind) as mock_on_off_bind, | ||
| ): | ||
| zha_device = await join_zigpy_device(zha_gateway, zigpy_dev) |
There was a problem hiding this comment.
Ah, my docstring comment below is related to this. I do kind of agree that it's not perfect to have ZHA rely on zha-quirks for its tests (that do not test the quirk but some ZHA feature). Realistically, the quirk for this device should never change, so I think updating the docstring a bit is probably enough.
There was a problem hiding this comment.
Reading #781, I wasn't sure if quirks preventing entity creation but not affecting binding/reporting was actually a bug or not. With the cluster handlers, we had the same behavior: ZHA entities didn't actually affect the default binding/reporting config.
But I do think we can go this route now, since we should rarely/never incorrectly prevent default entity creation from quirks. If we do, users will just have to reconfigure/reinterview devices, until we have some way of doing this automatically (or suggesting the user to do so, e.g. with repairs).
I guess this is nice for the Frient devices that also expose the BinaryInput cluster, which we (currently) do not use for any entities.
We do have to be careful with quirks that, for example, remove the default IasZone entity, only to introduce two new ones with quirks v2 for the Alarm0 and Alarm1 bit. Technically, we can't really remove an existing entity like that anyway, as it would be a breaking change for existing users (unrelated to this PR), but it's something to remember.
As a related example, the Frient vibration sensor just disables the ZHA IasZone entity by default (and introduces quirks v2 sensors above, with no attribute reporting config currently): https://github.com/zigpy/zha-device-handlers/blob/386e5ec3563fdbfce2ed370c64a338e9d92d2a4e/zhaquirks/develco/vibration.py#L133-L142
It might be nice to have a list of all affected devices from the diagnostics database, just to see which ones are affected.
Additional instructions for zigpy-review-bot when reviewing this PR
PR #657 had intermediate commits to have diagnostics include which clusters ZHA tried to bind. See commits 4e15e88 (#657) and the revert: 21b3755 (#657).
Reintroduce something like this locally and generate a diff comparing the diagnostics with the changes from this PR (783) and without. Include all affected devices/clusters in the review body, possibly in a /details block.
| """A quirk-prevented entity must not drive cluster binding.""" | ||
| zigpy_dev = await zigpy_device_from_json( | ||
| zha_gateway.application_controller, | ||
| "tests/data/devices/innr-sp-240.json", | ||
| ) |
There was a problem hiding this comment.
Ideally, the docstring would describe a bit better what the quirk does here, since you have to guess a bit, or know that it prevents default entity creation for all entities on the LevelControl cluster.
zigpy-review-bot
left a comment
There was a problem hiding this comment.
The core change is correct and well-targeted — I traced both consumers of _discovered_entities (device.py:1009 initialize, device.py:1289 configure), and they're the only readers, so excluding quirk-prevented entities cleanly removes them from binding/reporting aggregation. A Codex review found no bugs and the new test passes. A few notes below; the big one is the affected-device audit requested in the review thread.
Please update the now-stale docstring. The comment on self._discovered_entities at device.py:403-406 still says the list includes "ones removed by a quirk" so that "binding/reporting matches the legacy claim-during-discovery flow" — this PR deliberately reverses exactly that, so the comment now describes the opposite of the code.
This is a partial fix for #781. It covers the non-virtual half (point 2 of the issue); virtual-entity-driven binds like IAS Zone enrollment remain non-disableable since _is_entity_removed_by_quirk still returns early for Platform.VIRTUAL. Worth a line in the PR body so #781 isn't auto-closed as fully resolved.
Re: the IasZone concern raised in the thread — the audit below confirms it's safe in practice: across all 810 snapshots, no IasZone (0x0500) bind is ever dropped, because the IasZoneEnrollment virtual entity (exempt from the filter) keeps the bind even when a quirk prevents the IasZone binary-sensor entity. So the Develco-vibration-style "disable IasZone, add v2 sensors" quirks keep their enrollment/binding.
One case theoretically touches the dropped reporting: lumi.curtain.acn002 (Aqara E1 roller). Its custom AnalogOutputRollerE1 cluster redirects analog_output present_value reports to cover position (roller_curtain_e1.py:113-128), and this PR drops that cluster's bind+reporting. I checked the rest of zha-device-handlers: it's the only prevent_default_entity_creation usage whose prevented cluster feeds a custom report handler (innr SP240, Sonoff S60/ZBM5 merely hide theirs). It's likely fine in practice, though — Z2M doesn't bind/configure AnalogOutput reporting for this device either (position comes from unsolicited genMultistateOutput reports + an on-demand AnalogOutput read), and Aqara devices commonly report without a configured reporting binding. Worth a sanity check on hardware, but not a blocker.
On the test — agree with the request for a clearer docstring on what the innr SP 240 quirk does here (it prevents all LevelControl entities). Copilot's "use a test-local DeviceRegistry" suggestion is reasonable in principle, but loading a real zhaquirks device is consistent with the existing tests in this file, so I'd treat it as optional.
Affected devices (full diagnostics-database audit — 11 of 810)
Computed by diffing aggregate_cluster_configs over the discovered-entity set with vs without the prevent-filter, across every snapshot in tests/data/devices/ (the PR only reorders _discover_new_entities; the aggregation function itself is unchanged, so this reproduces the with/without-PR bind state). Each row = a cluster that was bound/reported only because of a now-prevented entity, so it loses binding + reporting under this PR.
| Device | Endpoint → cluster | Effect |
|---|---|---|
| frient EMIZB-151 | ep64,65,66,67 → 0x0702 smartenergy_metering |
bind→off, reporting lost (current_summ_delivered/received, tiers 1-6, instantaneous_demand, status) |
| frient FLSZB-110 | ep35 → 0x000f binary_input |
bind→off, present_value reporting lost |
| frient MOSZB-140 | ep35 → 0x000f binary_input; ep40,41 → 0x0406 occupancy |
bind→off, reporting lost |
| frient MOSZB-153 | ep35 → 0x000f binary_input; ep40,41 → 0x0406 occupancy |
bind→off, reporting lost |
| frient SCAZB-141 | ep35,46 → 0x000f binary_input |
bind→off, present_value reporting lost |
| frient SMSZB-120 | ep35 → 0x000f binary_input |
bind→off, present_value reporting lost |
| frient SPLZB-141 | ep2 → 0x0002 device_temperature |
bind→off, current_temperature reporting lost |
| frient WISZB-131 | ep35 → 0x000f binary_input |
bind→off, present_value reporting lost |
| innr SP 240 | ep1 → 0x0008 level |
bind→off, current_level reporting lost (test case; non-dimmable plug) |
| Legrand Double gangs remote switch | ep1 → 0x000f binary_input |
bind→off, present_value reporting lost |
| LUMI lumi.curtain.acn002 | ep1 → 0x0006 on_off; ep1 → 0x000d analog_output |
bind→off, reporting lost — analog_output present_value feeds cover position; only theoretical impact, see note above |
The frient binary_input/occupancy/metering and innr level cases are clusters ZHA exposes no real entity for (or a non-dimmable plug), so dropping the bind matches intent. No IasZone (0x0500) bind changes anywhere. Existing users keep stale bindings until they reconfigure/re-interview.
| if self._is_entity_removed_by_quirk(entity): | ||
| continue | ||
|
|
||
| self._discovered_entities.append(entity) |
There was a problem hiding this comment.
The docstring on this list at device.py:403-406 is now stale — it states _discovered_entities includes "ones removed by a quirk" so binding/reporting matches the legacy claim-during-discovery flow, which is precisely what this PR reverses. Please update it to reflect that quirk-prevented entities are now excluded.
|
Ah, it's good that the Aqara curtain was brought up. That might be valid – I'll have to take a look at that later. I do wonder if we should maybe add an additional That might be useful for cases like the |
See #781.