diff --git a/bellows/__init__.py b/bellows/__init__.py index d7387d67..37b97d1e 100644 --- a/bellows/__init__.py +++ b/bellows/__init__.py @@ -1,5 +1,5 @@ MAJOR_VERSION = 0 MINOR_VERSION = 31 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" diff --git a/bellows/ezsp/protocol.py b/bellows/ezsp/protocol.py index ad479121..4246a0c5 100644 --- a/bellows/ezsp/protocol.py +++ b/bellows/ezsp/protocol.py @@ -134,8 +134,20 @@ async def update_policies(self, zigpy_config: dict) -> None: def __call__(self, data: bytes) -> None: """Handler for received data frame.""" + orig_data = data sequence, frame_id, data = self._ezsp_frame_rx(data) - frame_name = self.COMMANDS_BY_ID[frame_id][0] + + try: + frame_name = self.COMMANDS_BY_ID[frame_id][0] + except KeyError: + LOGGER.warning( + "Unknown application frame 0x%04X received: %s (%s). This is a bug!", + frame_id, + binascii.hexlify(data), + binascii.hexlify(orig_data), + ) + return + LOGGER.debug( "Application frame %s (%s) received: %s", frame_id, diff --git a/bellows/ezsp/v9/commands.py b/bellows/ezsp/v9/commands.py index 5dbaed7a..edc35fac 100644 --- a/bellows/ezsp/v9/commands.py +++ b/bellows/ezsp/v9/commands.py @@ -124,12 +124,12 @@ "getChildData": ( 0x004A, (t.uint8_t,), - (t.EmberStatus, t.EmberNodeId, t.EmberEUI64, t.EmberNodeType), + (t.EmberStatus, t.EmberChildData), ), "setChildData": ( 0x00AC, (t.uint8_t,), - (t.EmberStatus, t.EmberNodeId, t.EmberEUI64, t.EmberNodeType), + (t.EmberStatus, t.EmberChildData), ), "getSourceRouteTableTotalSize": (0x00C3, (), (t.uint8_t,)), "getSourceRouteTableFilledSize": (0x00C2, (), (t.uint8_t,)), diff --git a/bellows/ezsp/v9/types/struct.py b/bellows/ezsp/v9/types/struct.py index 0ba6a006..15b913e2 100644 --- a/bellows/ezsp/v9/types/struct.py +++ b/bellows/ezsp/v9/types/struct.py @@ -202,3 +202,25 @@ class EmberTransientKeyData(EzspStruct): # The number of seconds remaining before the key is automatically timed out of the # transient key table. remainingTimeSeconds: basic.uint16_t + + +class EmberChildData(EzspStruct): + """A structure containing a child node's data.""" + + # The EUI64 of the child + eui64: named.EmberEUI64 + # The node type of the child + type: named.EmberNodeType + # The short address of the child + id: named.EmberNodeId + # The phy of the child + phy: basic.uint8_t + # The power of the child + power: basic.uint8_t + # The timeout of the child + timeout: basic.uint8_t + + # The GPD's EUI64. + # gpdIeeeAddress: named.EmberEUI64 + # The GPD's source ID. + # sourceId: basic.uint32_t diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index bd96e831..f3af3dcd 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -5,7 +5,6 @@ import os from typing import Dict, Optional -from serial import SerialException import zigpy.application import zigpy.config import zigpy.device @@ -337,6 +336,8 @@ async def load_network_info(self, *, load_devices=False) -> None: # Ignore invalid NWK entries if nwk in t.EmberDistinguishedNodeId.__members__.values(): continue + elif eui64 == t.EmberEUI64.convert("00:00:00:00:00:00:00:00"): + continue self.state.network_info.nwk_addresses[ zigpy.types.EUI64(eui64) @@ -650,8 +651,12 @@ async def _reset_controller_loop(self): try: await self._reset_controller() break - except (asyncio.TimeoutError, SerialException) as exc: - LOGGER.debug("ControllerApplication reset unsuccessful: %s", str(exc)) + except Exception as exc: + LOGGER.warning( + "ControllerApplication reset unsuccessful: %s", + repr(exc), + exc_info=exc, + ) await asyncio.sleep(RESET_ATTEMPT_BACKOFF_TIME) self._reset_task = None diff --git a/tests/test_application_network_state.py b/tests/test_application_network_state.py index 4339f520..75b3c89e 100644 --- a/tests/test_application_network_state.py +++ b/tests/test_application_network_state.py @@ -280,6 +280,7 @@ def get_addr_table_node_id(index): { 16: t.EmberNodeId(0x44CB), 17: t.EmberNodeId(0x0702), + 18: t.EmberNodeId(0x0000), # bogus entry }.get(index, t.EmberNodeId(0xFFFF)), ) @@ -290,11 +291,12 @@ def get_addr_table_node_id(index): def get_addr_table_eui64(index): if index < 16: return (t.EmberEUI64.convert("ff:ff:ff:ff:ff:ff:ff:ff"),) - elif 16 <= index <= 17: + elif 16 <= index <= 18: return ( { 16: t.EmberEUI64.convert("cc:cc:cc:ff:fe:e6:8e:ca"), 17: t.EmberEUI64.convert("ec:1b:bd:ff:fe:2f:41:a4"), + 18: t.EmberEUI64.convert("00:00:00:00:00:00:00:00"), }[index], ) else: diff --git a/tests/test_ezsp_protocol.py b/tests/test_ezsp_protocol.py index 94595378..ebb43808 100644 --- a/tests/test_ezsp_protocol.py +++ b/tests/test_ezsp_protocol.py @@ -121,3 +121,14 @@ async def test_get_free_buffers(prot_hndl, status, raw, expected_value): assert free_buffers is expected_value else: assert free_buffers == expected_value + + +async def test_unknown_command(prot_hndl, caplog): + """Test receiving an unknown command.""" + + unregistered_command = 0x04 + + with caplog.at_level(logging.WARNING): + prot_hndl(bytes([0x00, 0x00, unregistered_command, 0xAB, 0xCD])) + + assert "0x0004 received: b'abcd' (b'000004abcd')" in caplog.text