Skip to content

Commit c5a23ad

Browse files
Fix bindable cluster matching (#81)
* Fix bindable cluster matching * coverage * negative tests * Update tests/test_application_helpers.py Co-authored-by: TheJulianJES <[email protected]> --------- Co-authored-by: TheJulianJES <[email protected]>
1 parent e592d39 commit c5a23ad

File tree

2 files changed

+120
-4
lines changed

2 files changed

+120
-4
lines changed

tests/test_application_helpers.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""Test zha application helpers."""
2+
3+
from collections.abc import Awaitable, Callable
4+
5+
import pytest
6+
from zigpy.device import Device as ZigpyDevice
7+
from zigpy.profiles import zha
8+
from zigpy.zcl.clusters.general import Basic, OnOff
9+
from zigpy.zcl.clusters.security import IasZone
10+
11+
from tests.conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
12+
from zha.application.gateway import Gateway
13+
from zha.application.helpers import async_is_bindable_target, get_matched_clusters
14+
from zha.zigbee.device import Device
15+
16+
IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8"
17+
IEEE_GROUPABLE_DEVICE2 = "02:2d:6f:00:0a:90:69:e8"
18+
19+
20+
@pytest.fixture
21+
def zigpy_device(zigpy_device_mock: Callable[..., ZigpyDevice]) -> ZigpyDevice:
22+
"""Device tracker zigpy device."""
23+
endpoints = {
24+
1: {
25+
SIG_EP_INPUT: [Basic.cluster_id, OnOff.cluster_id],
26+
SIG_EP_OUTPUT: [],
27+
SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH,
28+
SIG_EP_PROFILE: zha.PROFILE_ID,
29+
}
30+
}
31+
zigpy_dev: ZigpyDevice = zigpy_device_mock(endpoints)
32+
# this one is mains powered
33+
zigpy_dev.node_desc.mac_capability_flags |= 0b_0000_0100
34+
return zigpy_dev
35+
36+
37+
@pytest.fixture
38+
def zigpy_device_not_bindable(
39+
zigpy_device_mock: Callable[..., ZigpyDevice],
40+
) -> ZigpyDevice:
41+
"""Device tracker zigpy device."""
42+
endpoints = {
43+
1: {
44+
SIG_EP_INPUT: [Basic.cluster_id, IasZone.cluster_id],
45+
SIG_EP_OUTPUT: [],
46+
SIG_EP_TYPE: zha.DeviceType.IAS_ZONE,
47+
SIG_EP_PROFILE: zha.PROFILE_ID,
48+
}
49+
}
50+
zigpy_dev: ZigpyDevice = zigpy_device_mock(
51+
endpoints, ieee=IEEE_GROUPABLE_DEVICE2, nwk=0x2345
52+
)
53+
return zigpy_dev
54+
55+
56+
@pytest.fixture
57+
def remote_zigpy_device(zigpy_device_mock: Callable[..., ZigpyDevice]) -> ZigpyDevice:
58+
"""Device tracker zigpy device."""
59+
endpoints = {
60+
1: {
61+
SIG_EP_INPUT: [Basic.cluster_id],
62+
SIG_EP_OUTPUT: [OnOff.cluster_id],
63+
SIG_EP_TYPE: zha.DeviceType.REMOTE_CONTROL,
64+
SIG_EP_PROFILE: zha.PROFILE_ID,
65+
}
66+
}
67+
remote_zigpy_dev: ZigpyDevice = zigpy_device_mock(
68+
endpoints, ieee=IEEE_GROUPABLE_DEVICE, nwk=0x1234
69+
)
70+
return remote_zigpy_dev
71+
72+
73+
async def test_async_is_bindable_target(
74+
device_joined: Callable[[ZigpyDevice], Awaitable[Device]],
75+
zigpy_device: ZigpyDevice, # pylint: disable=redefined-outer-name
76+
zigpy_device_not_bindable: ZigpyDevice, # pylint: disable=redefined-outer-name
77+
remote_zigpy_device: ZigpyDevice, # pylint: disable=redefined-outer-name
78+
zha_gateway: Gateway, # pylint: disable=unused-argument
79+
) -> None:
80+
"""Test zha if a device is a binding target for another device."""
81+
zha_device = await device_joined(zigpy_device)
82+
not_bindable_zha_device = await device_joined(zigpy_device_not_bindable)
83+
remote_zha_device = await device_joined(remote_zigpy_device)
84+
85+
assert async_is_bindable_target(remote_zha_device, zha_device)
86+
87+
assert not async_is_bindable_target(not_bindable_zha_device, remote_zha_device)
88+
89+
90+
async def test_get_matched_clusters(
91+
device_joined: Callable[[ZigpyDevice], Awaitable[Device]],
92+
zigpy_device: ZigpyDevice, # pylint: disable=redefined-outer-name
93+
zigpy_device_not_bindable: ZigpyDevice, # pylint: disable=redefined-outer-name
94+
remote_zigpy_device: ZigpyDevice, # pylint: disable=redefined-outer-name
95+
zha_gateway: Gateway, # pylint: disable=unused-argument
96+
) -> None:
97+
"""Test getting matched clusters for 2 zha devices."""
98+
zha_device = await device_joined(zigpy_device)
99+
not_bindable_zha_device = await device_joined(zigpy_device_not_bindable)
100+
remote_zha_device = await device_joined(remote_zigpy_device)
101+
102+
matches = await get_matched_clusters(remote_zha_device, zha_device)
103+
assert len(matches) == 1
104+
assert (
105+
matches[0].source_cluster
106+
== remote_zha_device.device.endpoints[1].out_clusters[OnOff.cluster_id]
107+
)
108+
assert matches[0].target_ieee == zha_device.ieee
109+
assert matches[0].target_ep_id == 1
110+
111+
assert not await get_matched_clusters(not_bindable_zha_device, remote_zha_device)

zha/application/helpers.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@
2929
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS,
3030
)
3131
from zha.async_ import gather_with_limited_concurrency
32-
from zha.decorators import SetRegistry, periodic
33-
34-
# from zha.zigbee.cluster_handlers.registries import BINDABLE_CLUSTERS
35-
BINDABLE_CLUSTERS = SetRegistry()
32+
from zha.decorators import periodic
3633

3734
if TYPE_CHECKING:
3835
from zha.application.gateway import Gateway
@@ -91,6 +88,10 @@ async def get_matched_clusters(
9188
source_zha_device: Device, target_zha_device: Device
9289
) -> list[BindingPair]:
9390
"""Get matched input/output cluster pairs for 2 devices."""
91+
from zha.zigbee.cluster_handlers.registries import ( # pylint: disable=import-outside-toplevel
92+
BINDABLE_CLUSTERS,
93+
)
94+
9495
source_clusters = source_zha_device.async_get_std_clusters()
9596
target_clusters = target_zha_device.async_get_std_clusters()
9697
clusters_to_bind = []
@@ -163,6 +164,10 @@ def convert_to_zcl_values(
163164

164165
def async_is_bindable_target(source_zha_device: Device, target_zha_device: Device):
165166
"""Determine if target is bindable to source."""
167+
from zha.zigbee.cluster_handlers.registries import ( # pylint: disable=import-outside-toplevel
168+
BINDABLE_CLUSTERS,
169+
)
170+
166171
if target_zha_device.nwk == 0x0000:
167172
return True
168173

0 commit comments

Comments
 (0)