Repository navigation
Base ZGP device support - #868
Conversation
3300e9f to
60919c2
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #868 +/- ##
==========================================
+ Coverage 97.23% 97.56% +0.32%
==========================================
Files 61 61
Lines 10704 10944 +240
==========================================
+ Hits 10408 10677 +269
+ Misses 296 267 -29 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
8ea88ea to
929c063
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The shared device lifecycle refactor warrants human review because quirk-provided non-ZCL entity initialization and recommissioning remain untested.
1 open issue
What changed in this PR
Adds base Green Power device support to ZHA, complementing zigpy’s support and enabling quirk-provided entities without automatic ZCL discovery.
Changes:
- Separates shared device behavior from Zigbee-specific functionality.
- Adds Green Power quirk matching, device wrapping, and recommissioning support.
- Adds tests for Green Power properties, diagnostics, matching, and lifecycle handling.
| File | Description |
|---|---|
zha/zigbee/group.py |
Uses typed Zigbee device lookups for group operations. |
zha/zigbee/device.py |
Introduces shared and Green Power device classes while preserving the Device alias. |
zha/quirks.py |
Adds Green Power matching and registry support. |
zha/application/helpers.py |
Uses the shared active-coordinator availability check. |
zha/application/gateway.py |
Supports multiple device types during creation and recommissioning. |
tests/test_green_power_device.py |
Tests Green Power devices and quirk resolution. |
Review effort: Balanced
# Conflicts: # zha/application/gateway.py
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Nice split: BaseDevice / ZigbeeDevice / GreenPowerDevice is clean, the Zigbee-only behaviour moves over unchanged, and the GP quirk registry mirrors the Zigbee one. I checked out the head and ran the full suite (1487 passed), mypy zha/ (clean) and ruff check. Nothing blocking in the library itself; the main open question is the Home Assistant side, then a few small points.
Verified (5 checks)
- Full
pytest testson head 2277c5f: 1487 passed.mypy zha/andruff check zha tests: clean. _device_factoryagrees withBaseDevice.newfor every non-coordinator case (quirk factory, unquirked GPD, plain Zigbee), so the reinterview "same factory → rebuild in place" decision matches whatnewwould have built.GreenPowerDeviceMatch.matches: the IEEE prefix is compared most-significant-byte first (EUI64.serialize()is LSB-first, reversed here), checked against zigpy'ssynthetic_ieee.- The moved
ZigbeeDevicemethods match the oldDeviceones, andDevice = ZigbeeDevicekeeps existing imports andisinstancechecks working. - The remaining
.is_coordinatorusers in the repo (discovery.py,group.py) go through group members, which are alwaysZigbeeDevice.
Points to address
- Home Assistant's use of
gateway.devices. The lib now exposes GPDs through the samedevicesdict that HA iterates assuming Zigbee devices. For exampleZHADeviceProxy.zha_device_inforeadsself.device.device.endpoints.items()unconditionally (HAzha/helpers.py:431), andwebsocket_get_groupable_devicesreadsdevice.device.is_groupableon every device (HAzha/websocket_api.py:261), whichBaseDevicedoesn't have. Not reachable today since zigpy 2.3.0 can't commission a GPD yet, but once one is registered the device list / groupable / bindable websocket calls would raiseAttributeError. Is there (or will there be) a companion HA Core change, and does it need to land before GPDs can appear? Gateway.get_devicetyping (gateway.py:722). It is still annotatedDevice | None, but_devicesnow also holdsGreenPowerDevice, so a caller doing.endpointsorasync_get_clusteron the result type-checks and raisesAttributeErrorfor a GPD's IEEE. mypy doesn't catch it becausezha.application.gatewayhasarg-type/return-value/assignmentdisabled in the pyproject overrides; with that block removed, mypy reports exactly these new errors:get_device, the calls into_async_device_joined/_async_device_rejoined(gateway.py:820,:839, which takeDevice), andcoordinator_zha_device = self.get_or_create_device(...)(gateway.py:303). SuggestBaseDevice | Noneforget_deviceandBaseDevicefor the two helpers (inline comment ondevices). Thezigpy.device.ZigbeeDeviceannotations ondevice_removed,device_left,async_device_initializedetc. could be loosened toBaseDeviceat the same time, since zigpy now callsdevice_removedwith a GPD.- GPD availability after a re-announce.
_async_device_rejoinedends withzha_device.available = False("async_initializewill be called later"), while for a GPD_check_availableis a no-op andhandle_message/async_update_deviceonly see Zigbee packets. If zigpy announces an already-known GPD throughdevice_initialized(rather thandevice_reinterviewed, which the PR assumes), it would stay unavailable until restart. Worth confirming how zigpy's commissioning will announce a known GPD.
Smaller points
- GP registry order:
registerdoesinsert(0, entry)(quirks.py:256), so the most recently registered matching entry wins, same as the Zigbee registry. The docstrings ("matched in registration order", "first registered entry") read as the opposite; I confirmed with two overlapping entries that the later one wins. The existing Zigbeematch_entrydocstring has the same wording, so this is only worth fixing if you touch it. helpers.py:551: the availability loop used to skip any device whose node descriptor says coordinator and now skips only the active coordinator. A stale former-coordinator entry (e.g. after a radio swap, if it is still ingateway.devices) would now go through_check_availableand can be marked unavailable. Fine if intended; just confirming.GreenPowerDevice.get_diagnostics_jsonwriteslast_seenas a float /Nonewhere the Zigbee version writes an ISO string, and omitsactive_coordinator; probably fine for a new device type, but the shapes differ.- The PR body still says it separates the base platform entity types from ZCL by adding an intermediate subclass; that landed in #919, and this diff touches no platforms. A refreshed description would make the release notes accurate.
|
Regarding the Home Assistant comment: there is a corresponding branch to integrate this into HA Core. There's no way to make this compatible with ZHA Core. |
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Re-review at e2f1809: the points from my earlier review are addressed, and I have nothing blocking.
Verified (5 checks)
- Full
pytest testson head e2f1809: 1488 passed (one new test).mypy zha/andruff check/ruff format --check: clean, with thegateway/group/platformsoverride entries shrunk accordingly. get_devicenow returnsBaseDevice | None, anddevice_removed/device_initialized/async_device_initialized/_async_device_joined/_async_device_rejoinedtakeBaseDevice. The gateway typing gap from before is closed.- The availability loop is back to
if not dev.is_coordinator:BaseDevice.is_coordinatorreturnsFalse,ZigbeeDevicereads the node descriptor.test_check_available_skips_stale_coordinatorcovers a former coordinator staying available. GreenPowerDevice.get_diagnostics_jsonnow writeslast_seenas an ISO string and includesactive_coordinator, matching the Zigbee shape (asserted in the test).- The GP registry docstrings now say the most recently registered match wins, which agrees with
insert(0, entry).
Remaining notes
- GPD availability after a re-announce (carried over from the first review):
_async_device_rejoinedends withavailable = False, and nothing re-marks a GPD available if zigpy announces a known GPD throughdevice_initializedrather thandevice_reinterviewed. Not reachable until zigpy can commission a GPD, so only worth confirming once that lands. - PR description is still out of date: the second paragraph says this PR separates the base platform entity types from ZCL with an intermediate subclass, which landed in #919, and this diff touches no platform classes. Updating it would keep the release notes accurate.
- Thanks for the note on the Home Assistant side; that answers my earlier question.
There was a problem hiding this comment.
Third pass, again at e2f1809, this time with the companion work read: puddly/zgp-backbone on your zigpy fork, zigpy/zha-device-handlers#5273, and puddly/zha-gp on your Core fork. That settles my remaining question from last round, and makes the earlier generic "what about Home Assistant?" note concrete. Nothing blocking; three things I think are worth fixing before this lands, and the first is the only place the split looks unfinished.
Verified (8 checks)
- Full
pytest testsat e2f1809: 1488 passed.mypy zha/: clean.ruff checkandruff format --check: clean. - The override debt shrinks rather than moving around:
zha.zigbee.devicegoes from 48 ignored errors to 18,zha.application.gatewayfrom 10 to 2,zha.quirksdrops out, repo total 245 to 185, and no module grew. The new total-count header intools/regenerate_mypy_ignores.pymakes that checkable at a glance. - The re-announce question from the last two rounds is settled by the zigpy branch.
GreenPowerManager._finalize_commissioningannounces a new GPD withlistener_event("device_joined", device)followed bydevice_initialized(device), and one that is already registered throughgp_device_recommissioned, which ends inlistener_event("device_reinterviewed", new_device). So a known GPD never comes back throughdevice_initialized,_async_device_rejoinedis unreachable for one, and the rebuild re-runsGreenPowerDevice._init_from_zigpy_device, which sets_available = Trueagain. Dropping that point. - Same branch:
ControllerApplication.removepops aGreenPowerDeviceand firesdevice_removed, which this PR's handler now accepts as aBaseDevice. I exercised the other GPD paths ingateway.pyagainst a real device — new join, re-commission with and without a quirk change,device_removed,async_remove_device,async_reinterview_device— and they all behave, including the factory-change branch that nothing tests. - Quirks 5273 lines up with the registry API: every symbol it imports exists,
GreenPowerQuirkFactory.__call__(zigpy_device, gateway)matches howBaseDevice.newcallszha_device_factory, and the factory living on a module-level registry entry keeps_device_factory'siscomparison stable across a re-commission. GreenPowerDeviceMatch.matchesbyte order checked empirically:synthetic_ieee(src_id)issrc_id.to_bytes(4, "little")plus a four-zero marker, sobytes(reversed(ieee.serialize()))gives00:00:00:00:<SrcID MSB-first>and a vendor prefix such as the04:cd:15in 5273's builder test can never collide with a SrcID-addressed GPD. The SrcID-or-IEEE semantics and the per-fieldis not Nonechecks are right._first_in_clusterand the endpoint loop moving tonon_zdo_endpointsis equivalent to the oldep_id == 0filter, and all tenNotImplementedErrorstubs left onBaseDeviceare overridden onGreenPowerDevice, so none is reachable.- Findings 1 and 2 below were reproduced in the worktree against a real
GreenPowerDevice(mypy for the first,AttributeErrorfor the second); finding 3 is an argued hazard from reading the registry and the zhaquirks load order, not something I ran.
PlatformEntity is still typed to Device, so a Green Power entity cannot be typed
zha/application/platforms/__init__.py:532 takes device: Device, :567 stores self._device: Device, and :610 returns Device — all still ZigbeeDevice after the alias. The device hierarchy and the gateway were re-typed; the entity base was not.
The consequence that matters: on a Green Power entity, reveal_type(entity.device) is ZigbeeDevice, so entity.device.async_get_cluster(...) and entity.device.endpoints pass mypy and raise AttributeError at runtime. Every GP quirk entity gets the ZCL API offered to it by the type checker.
The other direction is narrower than it first looks, and worth stating precisely: passing a GreenPowerDevice to PlatformEntity.__init__ is an arg-type error (incompatible type "GreenPowerDevice"; expected "ZigbeeDevice"), but 5273's entities do not trip it, because they derive from BaseEvent, whose __init__(self, *args: Any, **kwargs: Any) erases the argument type. I checked both shapes: the forwarding one is clean, a GP entity deriving from PlatformEntity directly errors. So today it is latent, and it lands on the first GP quirk that wants something other than an event entity.
Neither shows up as a red CI check either: zha's mypy hook is scoped files: ^zha/ so tests/ is never checked, and in zha-device-handlers mypy runs without zha in scope, so PlatformEntity collapses to Any there. It is a gap in the typing model rather than a build break — but it is the one place where the new hierarchy stops halfway. Making PlatformEntity generic over the device type, or widening it to BaseDevice and keeping ZclPlatformEntity.device -> Device, would finish it.
Two Home Assistant endpoints fan out over every device and the Core branch does not cover them
puddly/zha-gp (one WIP commit, 95a0d00f) narrows four call sites: the endpoint loop in ZHADeviceProxy.zha_device_info, async_cluster_exists, async_get_actions and websocket_device_clusters. I walked the rest of the integration for the places that touch every device rather than one the user picked. There are four such scans, and the branch covers two of them — the device list (via zha_device_info) and the alarm-options check in zha/configuration (via async_cluster_exists). The two it does not cover are:
zha/devices/groupablefiltersdevice_proxies.values()ondevice.device.is_groupable, which moved toZigbeeDevice, so the "Add group" page raisesAttributeError: 'GreenPowerDevice' object has no attribute 'is_groupable'.zha/devices/bindablefilters the same dict throughasync_is_bindable_target, which lives in this repo (zha/application/helpers.py:233) and reachestarget_zha_device.async_get_std_clusters()for any target whose nwk is not 0x0000 — a GPD's nwk is derived from its SrcID, so it falls through and the binding page fails withAttributeError: 'GreenPowerDevice' object has no attribute 'async_get_std_clusters'.
One commissioned GPD breaks both of those pages for every device, not just for the GPD. That second one and get_matched_clusters (helpers.py:127) are still annotated Device while Core feeds them values straight out of gateway.devices, which this PR widened to BaseDevice — the same shape as the get_device typing you already fixed, one layer out. Narrowing inside the two helpers would fix the bindable list and the GPD-as-target case of async_binding_operation from the library side; a GPD as the source of a bind still needs a Core guard, because the handler goes on to read source_device.device.zdo and zigpy's GreenPowerDevice has no zdo. An is_groupable returning False on BaseDevice would cover the groupable list the same way. Whether those defaults belong here or as isinstance checks in Core is your call, but the fan-out pair is what needs an answer before a GPD can be commissioned.
The remaining Zigbee-only Core call sites only fire when a request is aimed at the GPD itself — zha/devices/clusters/attributes, .../commands, .../attributes/value, zha/groups/bind and unbind, and the set_zigbee_cluster_attribute / issue_zigbee_cluster_command services — so they are unhandled tracebacks rather than broken pages. ZHADeviceProxy.__init__ and async_rebind_device (Core helpers.py:314, :323) are annotated Device too; that one is annotation-only, since the proxy itself only uses BaseDevice-safe attributes.
The Green Power registry has no specificity tiers, so import order decides which quirk wins
match_entry just above it walks explicit tiers — (manufacturer, model), then (manufacturer, None), then (None, model), then wildcard — so an entry naming both manufacturer and model beats one naming only one of them, whatever order they registered in, and registration order only breaks ties inside a tier. match_green_power_entry (quirks.py:319) is a plain first-match over _gp_registry, and the only ordering input is insert(0, entry) at :257. zhaquirks imports via pkgutil.walk_packages, which yields modules in sorted order, so among overlapping GP quirks the alphabetically-last module wins.
Concretely: GreenPowerQuirkBuilder.applies_to invites a quirk that declares only device_id=..., and device IDs such as OnOffSwitch are shared across vendors. Such a generic quirk placed in a late-sorting module silently takes precedence over a vendor-specific one — 5273's EnOcean quirk matches on src_id_range(0x01550000, 0x0155FFFF), so any GPD in that block that also reports a matching device ID would be decided by filename. Nothing logs that a more specific entry was passed over. test_green_power_quirk_resolution happens to register generic-then-specific, which is the lucky order; nothing covers the other one.
Ranking candidates before returning — an entry with an identity criterion (src_id_ranges or ieee_prefixes) over one without, then by number of specified fields — would make built-in ordering independent of filenames. Note that it changes the custom-quirk story: today a custom quirk wins purely because it registers last, and under a ranking it would need an explicit precedence rule to keep beating a more specific built-in. Easier to settle now than once quirks depend on the current behaviour.
Smaller
Group.async_add_members/async_remove_members(group.py:298,:316) callget_zigbee_device, whoseassert isinstance(device, Device)(gateway.py:752) is the only guard. Core'szha/group/members/addandzha/group/members/removepass the request's members straight through without going via_is_groupable_member, so a GPD IEEE in that payload surfaces as a bareAssertionErrorwith no message; underpython -Oit becomesAttributeErroron the missingasync_add_endpoint_to_groupinstead. AZHAException, or skipping non-Zigbee members the way_is_groupable_memberdoes, would be easier to act on.Group.members(group.py:195) has the same shape.- A GPD is available forever:
_init_from_zigpy_deviceforces_available = Trueand_check_availableis a no-op. That is a reasonable default for a device with no heartbeat, but zigpy does maintainlast_seenfor a GPD — it is set on every accepted GPDF and persisted next to the frame counter — so the data for a consider-unavailable heuristic exists and is deliberately unused. Worth a sentence in the PR either way, since it is the kind of thing the next person will try to "fix". - Related, and the reason I noticed:
GreenPowerDevice.get_diagnostics_json(device.py:2060) isself.device._last_seen.isoformat(), but zigpy'sBaseDevice._last_seenisdatetime | Noneand the restore path only assigns it when the stored value is greater than zero, soNoneis inside the contract. On a GPD that has never been heard from, "Download diagnostics" raisesAttributeError: 'NoneType' object has no attribute 'isoformat';test_green_power_device_infoonly passes because it assignslast_seenfirst. Commissioning does always set it, so I could not produce a real path there — but the sameNonemakesdevice_inforeport alast_seenof now (time.localtime(None)is the current time), which is a quieter wrong answer. Note the publiclast_seenis a float, so the fix isdatetime.fromtimestamp(self.last_seen, UTC)with aNonebranch rather thanself.last_seen.isoformat(). The Zigbee version at:1798has the same expression, if you would rather do both at once. - With no endpoint and no cluster in it, a GP entity's base unique ID is just the IEEE, so two entities of one quirk on the same platform collide unless their
unique_id_suffixvalues differ. 5273's PTM 216Z sets one per button, andGreenPowerQuirkBuilder.entity()does not require it; when it is missing the extra entity disappears into_add_pending_entities(device.py:918, branch at:935) with no log. Hard to warn on as the code stands — that branch is load-bearing on the normal path, sinceasync_configureandasync_initializeboth call_discover_new_entitiesand only the second drains the queue, so every device reaches it with a duplicate. Not a regression, just newly easy to hit; requiring the suffix in the builder is probably the cheaper end to fix. is_coordinatoris acached_propertyonBaseDevicebut is not inBaseDevice._ZIGPY_CACHED_PROPERTIES;ZigbeeDevicehad to add it to its own tuple precisely because it overrides the property. Harmless while the base returns a constant, but it is the one gap in an otherwise exhaustive scheme._device_factory's docstring (gateway.py:201) says it returns the classDevice.newcreates, but omitsnew's first branch, theCoordinatorDevicecheck. I walked the coordinator re-interview combinations and none produces a wrong object, since the IEEE is identical on both sides of the comparison — so just the docstring.
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Delta review of e36ab67..7b29ead on top of my last pass at e2f1809. Nearly everything I raised is addressed; one new bug in the specificity ranking, nothing else.
Verified (6 checks)
- Full
pytest testsat 7b29ead: 1490 passed.mypy zha/clean,ruff checkclean. PlatformEntityis now typed toBaseDevice, withZclPlatformEntity.deviceandRSSISensor.devicenarrowed back toDevice, which closes the typing gap I described.BaseDevice.is_groupable(False) andasync_get_std_clusters()({}) cover the groupable and bindable fan-out pages from the library side.async_is_bindable_targetreturnsFalseearly for a source with no clusters, andget_matched_clusters/async_is_bindable_targetare typed forBaseDevice.test_green_power_device_not_zigbeeexercises each combination.get_zigbee_deviceraisesZHAException("... is not a Zigbee device")instead of asserting.is_coordinatoris now inBaseDevice._ZIGPY_CACHED_PROPERTIES, and the_device_factorydocstring is corrected.- GP quirk ranking: identity criterion first, then field count, and the stable sort keeps the newest entry ahead among equals. The new test covers both registration orders.
specificity counts a device_id of 0 as unspecified
specificity uses map(bool, (self.device_id, self.manufacturer_id, self.model_id)), but matches (just above) treats these fields as set whenever they are is not None. DeviceID.SimpleGenericOneStateSwitch is 0, a valid Green Power device ID, and a manufacturer or model ID of 0 is equally legal. So GreenPowerDeviceMatch(device_id=0) ranks (False, 0), the same as a wildcard GreenPowerDeviceMatch().
I checked it: GreenPowerDeviceMatch(device_id=0).specificity == GreenPowerDeviceMatch().specificity == (False, 0), and device_id=0, manufacturer_id=0 is also (False, 0). A quirk for a one-state switch therefore ties with a catch-all and falls back to registration order against it. That is the filename-order dependence this ranking was added to remove, just for the one device ID that is 0.
Counting is not None fields, as matches does, fixes it. A test with device_id=DeviceID.SimpleGenericOneStateSwitch registered before a wildcard entry would pin it, since the existing test only uses OnOffSwitch (2).
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Delta review of 7b29ead..744d324: the specificity bug from my last review is fixed, and I have nothing further. Approving.
Verified (4 checks)
specificitynow countsis not Nonefields, matchingmatches, and returns a single int (10 * has_identity + num_fields). The only consumer is the sort key inDeviceRegistry, which works the same with an int. Identity still outranks any field count, since at most 3 fields can be set.- The new assertion pins
GreenPowerDeviceMatch(device_id=0).specificity > GreenPowerDeviceMatch().specificity. tests/test_green_power_device.py: 14 passed at 744d324.mypy zha/quirks.pyandruff checkare clean.- All five review threads are already resolved, so there was nothing to resolve or reply to.
|
@zigpy-review-bot Review everything again, not just delta. After that, possibly also push extra/new branch that improves test coverage and link it in comment, so we can see whether to add here, or possibly in a future PR. Do everything with review rounds. |
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Fresh full re-review at 744d324, as asked. The refactor holds up, but there is one real bug in the new GPD code and a stale generated count. Nothing here blocks merging if you'd rather fix them in a follow-up.
Needs a fix
GreenPowerDevice.get_diagnostics_jsonraisesAttributeErrorfor a GPD that has nolast_seenyet (inline comment). The pyproject override this PR regenerates hides it, and the header count# Total ignored errors: 182 across 20 modulesis stale as a result: re-runningpython -m tools.regenerate_mypy_ignoresafter the fix should match it. Currently it gives 184 andunion-attr15 forzha.zigbee.device.
Worth knowing
async_is_bindable_targetnow returnsFalsebefore reaching thetarget.nwk == 0x0000branch when the source has no std clusters. A Zigbee device whose endpoints are all non-ZHA/ZLL profile is therefore no longer offered the coordinator as a bind target. That looks right, but it is a behaviour change outside the GP scope and the PR body doesn't mention it.BaseDevicedefaultsasync_get_std_clusters()andis_groupable, butasync_get_clusters()stays onZigbeeDeviceonly. HA Core'sasync_cluster_existscalls it for every non-coordinator device ingateway.devices, so a GPD would raise there unless the Core side guards it. Areturn {}default onBaseDevicewould close that on the library side.
Coverage
codecov/patch is failing. I pushed a branch with extra tests so you can decide whether to take them here or in a later PR: https://github.com/zigpy/zha/tree/zigpy-bot/zgp-test-coverage (one commit on top of this PR's head, tests only, tests/test_green_power_device.py).
It covers the GPD gateway lifecycle (join, leave, removal, re-commissioning that swaps the resolved factory), BaseDevice defaults, bindability of cluster-less sources and the remaining quirk-registry branches (zha/quirks.py goes to 100%). It also adds a strict xfail for the last_seen bug above, so it will tell you to drop the marker once that is fixed. The full suite passes on the branch (1504 passed, 1 xfailed).
Verified
- Full suite at 744d324: 1490 passed.
mypy zha/andruff check/ruff format --checkare clean. - zigpy 2.4.0 (pinned, lock unchanged) already ships
GreenPowerDevice,non_zdo_endpointsandGreenPowerManager, so no bump is needed. - The
device.pysplit intoBaseDevice/ZigbeeDeviceis behaviour-preserving: each moved method was compared against its old body, the cached-property invalidation split is correct, and theDevice = ZigbeeDevicealias keeps HA Core and zhaquirks imports working. - Registry ordering is the newest entry among those with the highest specificity, and
specificitycountsis not Nonefields exactly likematches.purge_custom_quirks,removeand__iter__cover the GP registry. - GPD join, leave, removal and re-commissioning paths were driven by hand and behave like the Zigbee ones.
- The mypy override debt shrinks overall (
device48→18,gateway10→2,helpers16→7), and no module grew.
This comment was marked as resolved.
This comment was marked as resolved.
Cover the GPD gateway lifecycle (join, leave, removal, re-commissioning), BaseDevice defaults, bindability of sources without clusters and the remaining quirk registry branches. Adds a strict xfail for GreenPowerDevice.get_diagnostics_json on a GPD that has never been seen.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Active-coordinator availability check, ZDO endpoint skip in default entity discovery and GPD diagnostics entity serialization.

Counterpart of zigpy/zigpy#1874. ZGP devices do not participate in automatic entity discovery at all, as no known devices emit useful capability information.
A basic quirk matching system is added for ZGP devices. An interrelated PR for zha-device-handlers will use it to create event entities.
Part of OpenHomeFoundation/roadmap#195