diff --git a/tests/test_application.py b/tests/test_application.py index f5bbb5f..4d06e5d 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -3,7 +3,7 @@ from unittest import mock import pytest -from zigpy.types import EUI64, Group +from zigpy.types import EUI64, Group, BroadcastAddress import zigpy.zdo.types as zdo_t from zigpy.zcl.clusters.general import Groups @@ -186,13 +186,34 @@ async def test_mrequest(app: application.ControllerApplication): assert 1 == len(app._api._waiters) assert ( - "SREQ AF dataRequestExt tsn: 39 {'dstaddrmode': 1, 'dstaddr': 0x0002, 'destendpoint': 255, 'dstpanid': 0, " + "SREQ AF dataRequestExt tsn: 39 {'dstaddrmode': , " + "'dstaddr': 0x0002, 'destendpoint': 255, 'dstpanid': 0, " "'srcendpoint': 1, 'clusterid': 4, 'transid': 39, 'options': 0, 'radius': 30, 'len': 3, " "'data': b\"\\x01'\\x00\"}" == str(app._api.request_raw.call_args[0][0]) ) assert (0, "message send success") == res +@pytest.mark.asyncio +async def test_broadcast(app: application.ControllerApplication): + fut = asyncio.Future() + fut.set_result(None) + app._api.request_raw = mock.MagicMock(return_value=fut) + + # broadcast (0, 54, 0, 0, 0, 0, 45, b'-<\x00', ) + res = await app.broadcast( + 0, 54, 0, 0, 0, 0, 45, b"-<\x00", BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR + ) + + assert 0 == len(app._api._waiters) + assert ( + "SREQ ZDO mgmtPermitJoinReq tsn: 45 {'addrmode': , " + "'dstaddr': 0xfffc, 'duration': 60, 'tcsignificance': 0}" + == str(app._api.request_raw.call_args[0][0]) + ) + assert (0, "broadcast send success") == res + + """ zigpy_cc.api DEBUG <-- SREQ ZDO nodeDescReq {'dstaddr': 53322, 'nwkaddrofinterest': 0} zigpy_cc.api DEBUG --> SRSP ZDO nodeDescReq {'status': 0} diff --git a/tests/test_buffalo.py b/tests/test_buffalo.py index 7e01b1d..0e7bd56 100644 --- a/tests/test_buffalo.py +++ b/tests/test_buffalo.py @@ -34,13 +34,13 @@ def test_write_ieee_group(): def test_read_ieee(): data_in = Buffalo(ieeeAddr1["hex"]) - actual = data_in.read_parameter(t.ParameterType.IEEEADDR, {}) + actual = data_in.read_parameter("test", t.ParameterType.IEEEADDR, {}) assert ieeeAddr1["string"] == actual def test_read_ieee2(): data_in = Buffalo(ieeeAddr2["hex"]) - actual = data_in.read_parameter(t.ParameterType.IEEEADDR, {}) + actual = data_in.read_parameter("test", t.ParameterType.IEEEADDR, {}) assert ieeeAddr2["string"] == actual @@ -68,5 +68,5 @@ def test_list_nighbor_lqi(): data_in = Buffalo(data_out.buffer) options = BuffaloOptions() options.length = len(value) - act = data_in.read_parameter(t.ParameterType.LIST_NEIGHBOR_LQI, options) + act = data_in.read_parameter("test", t.ParameterType.LIST_NEIGHBOR_LQI, options) assert value == act diff --git a/tests/test_types.py b/tests/test_types.py index 62263c6..10888b1 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -368,7 +368,8 @@ def test_bind_req(): b"\x01 SREQ ZDO bindReq tsn: 1 { - 'dstaddr': 0xbd8b, 'srcaddr': 00:0b:57:ff:fe:27:78:3c, 'srcendpoint': 1, 'clusterid': 8, 'dstaddrmode': 3, 'dstaddress': 00:12:4b:00:18:ed:25:0c, 'dstendpoint': 1} + 'dstaddr': 0xbd8b, 'srcaddr': 00:0b:57:ff:fe:27:78:3c, 'srcendpoint': 1, 'clusterid': 8, + 'dstaddrmode': 3, 'dstaddress': 00:12:4b:00:18:ed:25:0c, 'dstendpoint': 1} zigpy_cc.uart DEBUG Send: b"\xfe\x17%!\x8b\xbd, " "'dstaddress': 00:12:4b:00:18:ed:25:0c, " "'dstendpoint': 1}" == str(obj) ) @@ -431,7 +432,7 @@ def test_bind_req_serialize(): "srcaddr": EUI64(reversed(b"\x00\x0b\x57\xff\xfe\x27\x78\x3c")), "srcendpoint": 1, "clusterid": 8, - "dstaddrmode": 3, + "dstaddrmode": t.AddressMode.ADDR_64BIT, "dstaddress": EUI64(reversed(b"\x00\x12\x4b\x00\x18\xed\x25\x0c")), "dstendpoint": 1, } @@ -442,7 +443,7 @@ def test_bind_req_serialize(): "'srcaddr': 00:0b:57:ff:fe:27:78:3c, " "'srcendpoint': 1, " "'clusterid': 8, " - "'dstaddrmode': 3, " + "'dstaddrmode': , " "'dstaddress': 00:12:4b:00:18:ed:25:0c, " "'dstendpoint': 1}" == str(obj) ) diff --git a/zigpy_cc/buffalo.py b/zigpy_cc/buffalo.py index a5c15b5..23399ea 100644 --- a/zigpy_cc/buffalo.py +++ b/zigpy_cc/buffalo.py @@ -2,14 +2,13 @@ import zigpy.types from zigpy_cc.exception import TODO -from zigpy_cc.types import ParameterType +from zigpy_cc.types import AddressMode, ParameterType class BuffaloOptions: def __init__(self) -> None: self.startIndex = None self.length = None - self.is_address = False class Buffalo: @@ -68,12 +67,19 @@ def write_neighbor_lqi(self, value): self.write(value["depth"]) self.write(value["lqi"]) - def read_parameter(self, type, options): + def read_parameter(self, name, type, options): + if type == ParameterType.UINT8: res = self.read_int() + if name.endswith("addrmode"): + res = AddressMode(res) elif type == ParameterType.UINT16: res = self.read_int(2) - if options.is_address: + if ( + name.endswith("addr") + or name.endswith("address") + or name.endswith("addrofinterest") + ): res = zigpy.types.NWK(res) elif type == ParameterType.UINT32: res = self.read_int(4) diff --git a/zigpy_cc/types.py b/zigpy_cc/types.py index 5e04e1b..e3c43fd 100644 --- a/zigpy_cc/types.py +++ b/zigpy_cc/types.py @@ -18,6 +18,14 @@ class Timeouts: default = 10000 +class AddressMode(t.uint8_t, enum.Enum): + ADDR_NOT_PRESENT = 0 + ADDR_GROUP = 1 + ADDR_16BIT = 2 + ADDR_64BIT = 3 + ADDR_BROADCAST = 15 + + class LedMode(t.uint8_t, enum.Enum): Off = 0 On = 1 diff --git a/zigpy_cc/zigbee/application.py b/zigpy_cc/zigbee/application.py index 2b7a063..a47b821 100644 --- a/zigpy_cc/zigbee/application.py +++ b/zigpy_cc/zigbee/application.py @@ -16,7 +16,7 @@ from zigpy_cc.api import API from zigpy_cc.config import CONF_DEVICE, CONFIG_SCHEMA, SCHEMA_DEVICE from zigpy_cc.exception import TODO, CommandError -from zigpy_cc.types import NetworkOptions, Subsystem, ZnpVersion, LedMode +from zigpy_cc.types import NetworkOptions, Subsystem, ZnpVersion, LedMode, AddressMode from zigpy_cc.zigbee.start_znp import start_znp from zigpy_cc.zpi_object import ZpiObject @@ -178,7 +178,14 @@ async def mrequest( ) try: obj = ZpiObject.from_cluster( - 0, profile, cluster, src_ep, 0xFF, sequence, data, group=group_id + group_id, + profile, + cluster, + src_ep or 1, + 0xFF, + sequence, + data, + addr_mode=AddressMode.ADDR_GROUP, ) waiter_id = None waiter = self._api.create_response_waiter(obj, sequence) @@ -281,10 +288,17 @@ async def broadcast( sequence, data, radius=radius, + addr_mode=AddressMode.ADDR_16BIT, ) async with self._semaphore: await self._api.request_raw(obj) + """ + As a broadcast command is not confirmed and thus immediately returns + (contrary to network address requests) we will give the + command some time to 'settle' in the network. + """ + await asyncio.sleep(0.2) except CommandError as ex: return ( @@ -297,7 +311,7 @@ async def broadcast( async def permit_ncp(self, time_s=60): assert 0 <= time_s <= 254 payload = { - "addrmode": 0x0F, + "addrmode": AddressMode.ADDR_BROADCAST, "dstaddr": BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR, "duration": time_s, "tcsignificance": 0, diff --git a/zigpy_cc/zpi_object.py b/zigpy_cc/zpi_object.py index f375a03..d69c6a6 100644 --- a/zigpy_cc/zpi_object.py +++ b/zigpy_cc/zpi_object.py @@ -4,7 +4,7 @@ from zigpy_cc import uart from zigpy_cc.buffalo import Buffalo, BuffaloOptions from zigpy_cc.definition import Definition -from zigpy_cc.types import CommandType, ParameterType, Subsystem +from zigpy_cc.types import CommandType, ParameterType, Subsystem, AddressMode BufferAndListTypes = [ ParameterType.BUFFER, @@ -30,7 +30,7 @@ def __init__( command_type, subsystem, command: str, - commandId, + command_id, payload, parameters, sequence=None, @@ -38,7 +38,7 @@ def __init__( self.command_type = CommandType(command_type) self.subsystem = Subsystem(subsystem) self.command = command - self.command_id = commandId + self.command_id = command_id self.payload = payload self.parameters = parameters self.sequence = sequence @@ -101,11 +101,11 @@ def from_cluster( data, *, radius=30, - group=None + addr_mode=None ): if profile == zha.PROFILE_ID: subsystem = Subsystem.AF - if group is None: + if addr_mode is None: cmd = next(c for c in Definition[subsystem] if c["ID"] == 1) else: cmd = next(c for c in Definition[subsystem] if c["ID"] == 2) @@ -131,12 +131,11 @@ def from_cluster( } elif name == "dataRequestExt": payload = { - # 1: group - "dstaddrmode": 1, - "dstaddr": group, - "destendpoint": 0xFF, + "dstaddrmode": addr_mode, + "dstaddr": nwk, + "destendpoint": dst_ep, "dstpanid": 0, - "srcendpoint": src_ep or 1, + "srcendpoint": src_ep, "clusterid": cluster, "transid": sequence, "options": 0, @@ -146,7 +145,9 @@ def from_cluster( } elif name == "mgmtPermitJoinReq": addrmode = ( - 0x0F if nwk == BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR else 0x02 + AddressMode.ADDR_BROADCAST + if nwk == BroadcastAddress.ALL_ROUTERS_AND_COORDINATOR + else AddressMode.ADDR_16BIT ) payload = cls.read_parameters( bytes([addrmode]) + nwk.to_bytes(2, "little") + data[1:], parameters @@ -168,29 +169,24 @@ def read_parameters(cls, data: bytes, parameters): buffalo = Buffalo(data) res = {} length = None - startIndex = None + start_index = None for p in parameters: options = BuffaloOptions() name = p["name"] - if ( - name.endswith("addr") - or name.endswith("address") - or name.endswith("addrofinterest") - ): - options.is_address = True - type = p["parameterType"] - if type in BufferAndListTypes: + param_type = p["parameterType"] + if param_type in BufferAndListTypes: if isinstance(length, int): options.length = length - if type == ParameterType.LIST_ASSOC_DEV: - if isinstance(startIndex, int): - options.startIndex = startIndex + if param_type == ParameterType.LIST_ASSOC_DEV: + if isinstance(start_index, int): + options.startIndex = start_index + + res[name] = buffalo.read_parameter(name, param_type, options) - res[name] = buffalo.read_parameter(type, options) - # For LIST_ASSOC_DEV, we need to grab the startindex which is + # For LIST_ASSOC_DEV, we need to grab the start_index which is # right before the length - startIndex = length + start_index = length # When reading a buffer, assume that the previous parsed parameter # contains the length of the buffer length = res[name]