Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
57b2ac0
Make `Device` work with the new `ZigbeeDevice` base
puddly Aug 17, 2026
579b528
Base ZGP device
puddly Aug 17, 2026
96c997d
GP quirk registry
puddly Aug 17, 2026
925c453
Fix after rebase
puddly Oct 2, 2026
9ff1810
Make ZHA's `BaseDevice` generic of zigpy's base device
puddly Oct 2, 2026
613b2d9
Override `async_configure` for `GreenPowerDevice`
puddly Oct 20, 2026
929c063
Type gateway device methods for `BaseDevice`
puddly Oct 20, 2026
0265fe1
Increase coverage
puddly Oct 20, 2026
79d22cf
Manually build a ZGP device until zhaquirks is released
puddly Oct 23, 2026
2277c5f
Merge branch 'dev' into puddly/zgp-split
puddly Oct 23, 2026
6a40aac
Include total ignore count in tool output
puddly Oct 23, 2026
1c45c21
Clean up typing
puddly Oct 23, 2026
b76c9cd
Clean up typing on device
puddly Oct 23, 2026
6b35f11
Clean up ZGP typing
puddly Oct 23, 2026
8c4358e
Address review comments
puddly Oct 23, 2026
e2f1809
A bit more typing
puddly Oct 23, 2026
e36ab67
Fix further `Device` typing issues
puddly Oct 24, 2026
a851696
Add specificity to GP quirks
puddly Oct 24, 2026
14789d1
Add tests
puddly Oct 24, 2026
04d4eb2
Skip `async_is_bindable_target` when the source has no bindable clusters
puddly Oct 24, 2026
7b29ead
Merge branch 'dev' into puddly/zgp-split
puddly Oct 24, 2026
8012b01
Use `is not None` instead of `bool` for specificity fields
puddly Oct 26, 2026
744d324
Make specificity strictly numerical
puddly Oct 26, 2026
355d78a
Increase test coverage for ZGP device support
zigpy-review-bot Oct 26, 2026
ba93c42
Account for `last_seen = None`
puddly Oct 26, 2026
2a8ab10
Cover remaining PR-affected lines in device.py
zigpy-review-bot Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 31 additions & 46 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,36 +102,42 @@ non_interactive = true
# `python -m tools.regenerate_mypy_ignores`. They are sorted easiest-to-fix
# first; clean up a module's errors and re-run the tool to shrink the list.
# Autogenerated mypy overrides: start
# Total ignored errors: 182 across 20 modules
[[tool.mypy.overrides]]
module = "zha.application.platforms.virtual"
disable_error_code = [
"no-any-return", # 1
]

[[tool.mypy.overrides]]
module = "zha.quirks"
module = "zha.zigbee.group"
disable_error_code = [
"no-any-return", # 1
"return-value", # 1
]

[[tool.mypy.overrides]]
module = "zha.application.platforms.number"
module = "zha.application.gateway"
disable_error_code = [
"no-any-return", # 2
"assignment", # 2
]

[[tool.mypy.overrides]]
module = "zha.application.discovery"
module = "zha.application.platforms"
disable_error_code = [
"no-any-return", # 3
"arg-type", # 1
"assignment", # 1
]

[[tool.mypy.overrides]]
module = "zha.application.platforms"
module = "zha.application.platforms.number"
disable_error_code = [
"arg-type", # 1
"assignment", # 1
"no-any-return", # 1
"no-any-return", # 2
]

[[tool.mypy.overrides]]
module = "zha.application.discovery"
disable_error_code = [
"no-any-return", # 3
]

[[tool.mypy.overrides]]
Expand Down Expand Up @@ -177,29 +183,19 @@ disable_error_code = [
"no-untyped-def", # 4
]

[[tool.mypy.overrides]]
module = "zha.zigbee.group"
disable_error_code = [
"no-untyped-def", # 3
"return-value", # 2
]

[[tool.mypy.overrides]]
module = "zha.application.platforms.fan"
disable_error_code = [
"arg-type", # 2
"index", # 1
"no-any-return", # 3
"no-untyped-def", # 1
]

[[tool.mypy.overrides]]
module = "zha.application.gateway"
module = "zha.application.helpers"
disable_error_code = [
"arg-type", # 3
"assignment", # 2
"no-untyped-call", # 4
"return-value", # 1
"arg-type", # 2
"no-untyped-def", # 5
]

[[tool.mypy.overrides]]
Expand All @@ -218,52 +214,41 @@ disable_error_code = [
]

[[tool.mypy.overrides]]
module = "zha.application.helpers"
module = "zha.zigbee.device"
disable_error_code = [
"arg-type", # 2
"no-untyped-call", # 4
"no-untyped-def", # 10
"arg-type", # 1
"no-untyped-call", # 2
"union-attr", # 13
]

[[tool.mypy.overrides]]
module = "zha.application.platforms.light"
disable_error_code = [
"arg-type", # 3
"no-any-return", # 4
"no-any-return", # 3
"no-untyped-call", # 11
"no-untyped-def", # 8
"type-var", # 2
]

[[tool.mypy.overrides]]
module = "zha.application.platforms.climate"
disable_error_code = [
"call-overload", # 4
"no-any-return", # 23
"no-untyped-def", # 8
]

[[tool.mypy.overrides]]
module = "zha.application.platforms.sensor"
disable_error_code = [
"arg-type", # 12
"arg-type", # 11
"assignment", # 2
"attr-defined", # 5
"attr-defined", # 2
"no-any-return", # 9
"no-untyped-call", # 4
"no-untyped-def", # 3
"operator", # 4
"operator", # 2
]

[[tool.mypy.overrides]]
module = "zha.zigbee.device"
module = "zha.application.platforms.climate"
disable_error_code = [
"arg-type", # 12
"no-any-return", # 4
"no-untyped-call", # 3
"no-untyped-def", # 10
"return-value", # 1
"union-attr", # 18
"call-overload", # 4
"no-any-return", # 23
"no-untyped-def", # 8
]
# Autogenerated mypy overrides: end

Expand Down
74 changes: 74 additions & 0 deletions tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,80 @@ async def test_check_available_no_basic_cluster(
assert "does not have a mandatory basic cluster" in caplog.text


async def test_check_available_skips_stale_coordinator(
zha_gateway: Gateway,
) -> None:
"""Check that a former coordinator is not marked unavailable."""
old_coord_dev = zigpy_device(
zha_gateway,
with_basic_cluster=False,
ieee="aa:bb:cc:dd:ee:ff:00:12",
nwk=0x0000,
)
old_coord_dev.node_desc = old_coord_dev.node_desc.replace(
logical_type=zdo_t.LogicalType.Coordinator
)
zha_device = await join_zigpy_device(zha_gateway, old_coord_dev)

assert not zha_device.is_active_coordinator
assert zha_device.available is True

old_coord_dev.last_seen = time.time() - zha_device.consider_unavailable_time - 2
await _send_time_changed(
zha_gateway, zha_gateway._device_availability_checker.__polling_interval + 1
)

assert zha_device.available is True


async def test_check_available_skips_active_coordinator(
zha_gateway: Gateway,
) -> None:
"""Check that the active coordinator is never marked unavailable."""
coord_dev = zigpy_device(
zha_gateway,
with_basic_cluster=False,
ieee="aa:bb:cc:dd:ee:ff:00:11",
nwk=0x0000,
)
coord_dev.node_desc = coord_dev.node_desc.replace(
logical_type=zdo_t.LogicalType.Coordinator
)
zha_device = await join_zigpy_device(zha_gateway, coord_dev)
zha_gateway.application_controller.state.node_info.ieee = coord_dev.ieee

assert zha_device.is_active_coordinator
assert zha_device.available is True

coord_dev.last_seen = time.time() - zha_device.consider_unavailable_time - 2
await zha_device._check_available()

# `available` is always True for the active coordinator, so check the raw flag
assert zha_device._available is True
assert zha_device.available is True


async def test_discover_entities_skips_zdo_endpoint(
zha_gateway: Gateway,
) -> None:
"""Check that endpoint 0 is never passed to endpoint entity discovery."""
zha_device = await join_zigpy_device(
zha_gateway, zigpy_device(zha_gateway, with_basic_cluster=True)
)
# `Device.endpoints` only holds non-ZDO endpoints, so ep 0 is injected
zha_device._endpoints[0] = mock.MagicMock()

with patch(
"zha.zigbee.device.discovery.discover_entities_for_endpoint",
return_value=iter(()),
) as discover:
list(zha_device.discover_entities())

discovered = {c.args[0] for c in discover.mock_calls}
assert zha_device._endpoints[0] not in discovered
assert discovered == {ep for i, ep in zha_device._endpoints.items() if i != 0}


async def test_device_is_active_coordinator(
zha_gateway: Gateway,
) -> None:
Expand Down
Loading
Loading