Skip to content

Fix prevent_default_entity_creation ignoring cluster_type#716

Draft
TheJulianJES wants to merge 2 commits into
zigpy:devfrom
TheJulianJES:tjj/prevent_entity_creation_cluster_type
Draft

Fix prevent_default_entity_creation ignoring cluster_type#716
TheJulianJES wants to merge 2 commits into
zigpy:devfrom
TheJulianJES:tjj/prevent_entity_creation_cluster_type

Conversation

@TheJulianJES
Copy link
Copy Markdown
Contributor

Proposed change

This fixes an issue where prevent_default_entity_creation ignored a cluster_type when provided.

Note: The zigpy metadata defaults to cluster_type=.SERVR, so this changes the behavior where cluster_type is currently omitted. We'll need to re-evaluate all quirks usage of this to see if a client and/or server entity should be hidden instead.

Additional information

Fixes:

Copy link
Copy Markdown
Collaborator

@zigpy-review-bot zigpy-review-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the change does what the title says: the predicate now also matches cluster_type, so a prevent_default_entity_creation(cluster_id=X) no longer collapses Server and Client cluster handlers together. The fix is one line and the parametrised test (test_quirks_v2_prevent_default_entities_cluster_type[None|Server|Client]) is well-shaped: it pins both branches of the new condition and asserts the QuirkBuilder's None -> Server normalisation. Runs locally on Py 3.14 (3 passed). Mypy diff is zero — the touched expression stays trivially typed.

CI failures are not unrelated — they're the behaviour change this PR caused

The two failing snapshots (tests/test_discover.py::test_devices_from_files[ewelink-snzb-04p.json] and [legrand-light-switch-with-neutral.json]) are direct consequences of the new strictness. Reproduced locally on this branch.

Walkthrough for ewelink-snzb-04p:

  • The SNZB-04P signature has output_clusters=[0x0003, 0x0006, 0x0019] — OnOff is on the client side.
  • zhaquirks/sonoff/snzb04p.py calls .prevent_default_entity_creation(endpoint_id=1, cluster_id=OnOff.cluster_id) with no cluster_type.
  • QuirkBuilder.prevent_default_entity_creation normalises that to cluster_type=ClusterType.Server (in zigpy quirks/v2/__init__.py).
  • Before this PR: meta.cluster_id == handler.cluster_id matched the OnOffClientClusterHandler (id 6) and the default Opening binary_sensor on 1-6 was removed.
  • After this PR: meta.cluster_type == Server no longer matches the client handler, so Opening reappears in the snapshot — which is why the diff shows an extra 'class_name': 'OnOffClientClusterHandler' / 'unique_id': '...-1-6' entry.

Same mechanism applies to the Legrand light switch with neutral quirk (.prevent_default_entity_creation(endpoint_id=1, cluster_id=OnOff.cluster_id, function=lambda entity: entity.device_class == "opening")).

So the PR is correct in isolation, but it silently changes the meaning of every existing prevent_default_entity_creation(cluster_id=...) call site in zha-device-handlers that targeted a client cluster — which you already flagged in the description. Marking this Draft is right: it shouldn't merge until either

  1. those quirks are updated to pass cluster_type=ClusterType.Client (and the snapshots regen'd), or
  2. the diagnostics device files are regen'd if the new default behaviour is genuinely what's intended for those devices.

The quirks I found that pass cluster_id without cluster_type and may need the same audit:

  • zhaquirks/sonoff/snzb04p.py (OnOff, client) — affects this PR's CI
  • zhaquirks/legrand/switch.py (OnOff, client) — affects this PR's CI
  • zhaquirks/legrand/connected_outled.py, dimmer.py, micromodule.py, shutter.py, wirelessswitch.py, wirelessshutter.py (BinaryInput, cluster 15 — these are input clusters on the affected endpoints, so server-side and unaffected, but worth a manual sanity-check while we're touching the surface)

Cross-PR: complementary with #763, not conflicting

Verified both at HEAD: #716's change is _is_entity_removed_by_quirk body, line 886 (adding cluster_type to the any(...) predicate inside the existing loop). #763's change is an early return on the function — if entity._from_quirks_v2: return False — placed several lines above the predicate loop. Different concerns, different lines. The fetched branches merge cleanly on zha/zigbee/device.py; the only conflict is the test-file imports both PRs added independently, trivial three-way resolution.

The two bugs are also semantically orthogonal: #763 stops prevent_default_entity_creation from clobbering v2 entities the same quirk just declared (intra-quirk false-positive); #716 stops a single rule's cluster filter from matching the wrong cluster side (cross-side false-positive). Either can land first; both should land.

Minor: dataclass-side defensive shape

PreventDefaultEntityCreationMetadata types cluster_type: ClusterType | None. The QuirkBuilder normalises None -> Server when cluster_id is set, but the dataclass is attrs.define(frozen=True) and a caller instantiating it directly (or future builder paths) could leave both cluster_id=X and cluster_type=None. In that case the new check handler.cluster.cluster_type == None would never match and the rule would silently match nothing.

Not worth blocking on — the only constructor today is QuirkBuilder.prevent_default_entity_creation, which always sets cluster_type. But a tiny defensiveness like

and (meta.cluster_type is None
     or cluster_handler.cluster.cluster_type == meta.cluster_type)

would make the predicate read as "match the type when one is specified, otherwise match either" and keep direct-instantiation paths in the metadata's documented | None shape. Up to you whether that's worth it; if you'd rather treat None as invalid for cluster_type once cluster_id is set, fixing it in the zigpy dataclass (__attrs_post_init__ or removing the | None) would be cleaner.

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.

2 participants