From 9ee4440f267e10066541c23ff956a22fb2523d2f Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Fri, 18 Sep 2020 20:40:20 -0400 Subject: [PATCH 1/3] 0.11.0.dev0 version bump --- zigpy_deconz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigpy_deconz/__init__.py b/zigpy_deconz/__init__.py index 66dfb1c..ff377f6 100644 --- a/zigpy_deconz/__init__.py +++ b/zigpy_deconz/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 MAJOR_VERSION = 0 -MINOR_VERSION = 10 +MINOR_VERSION = 11 PATCH_VERSION = "0.dev0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION) From 68b01415ae0a049076d3b7f16164da5b0ec87eff Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 22 Oct 2020 10:38:27 -0400 Subject: [PATCH 2/3] Refactor handling of errors in received data (#137) * Refactor handling of errors in received data More robust handling of errors for failing aps_data_confirm and aps_data_indication commands. * Fix tests * Handle exceptions --- tests/test_api.py | 46 +++++++++++++++++++++++++++++++++++++++------ zigpy_deconz/api.py | 29 ++++++++++++++++++---------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 6c2a68a..b5dfe86 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,6 +1,7 @@ """Test api module.""" import asyncio +import binascii import logging import pytest @@ -18,12 +19,18 @@ @pytest.fixture -def api(event_loop): +def uart_gw(): + gw = MagicMock(auto_spec=uart.Gateway(MagicMock())) + return gw + + +@pytest.fixture +def api(event_loop, uart_gw): controller = MagicMock( spec_set=zigpy_deconz.zigbee.application.ControllerApplication ) api = deconz_api.Deconz(controller, {zigpy.config.CONF_DEVICE_PATH: "/dev/null"}) - api._uart = MagicMock() + api._uart = uart_gw return api @@ -192,18 +199,18 @@ def test_data_received_unk_status(api, monkeypatch): my_handler = MagicMock() for cmd, cmd_opts in deconz_api.RX_COMMANDS.items(): - _, unsolicited = cmd_opts + _, solicited = cmd_opts payload = b"\x01\x02\x03\x04" status = t.uint8_t(0xFE).serialize() data = cmd.serialize() + b"\x00" + status + b"\x00\x00" + payload setattr(api, "_handle_{}".format(cmd.name), my_handler) api._awaiting[0] = MagicMock() api.data_received(data) - assert t.deserialize.call_count == 1 - assert t.deserialize.call_args[0][0] == payload - if unsolicited: + if solicited: assert my_handler.call_count == 0 + assert t.deserialize.call_count == 0 else: + assert t.deserialize.call_count == 1 assert my_handler.call_count == 1 t.deserialize.reset_mock() my_handler.reset_mock() @@ -566,3 +573,30 @@ def test_tx_status(value, name): def test_handle_add_neighbour(api): """Test handle_add_neighbour.""" api._handle_add_neighbour((12, 1, 0x1234, sentinel.ieee, 0x80)) + + +@pytest.mark.parametrize("status", (0x00, 0x05)) +async def test_aps_data_req_deserialize_error(api, uart_gw, status, caplog): + """Test deserialization error.""" + + device_state = ( + deconz_api.DeviceState.APSDE_DATA_INDICATION + | deconz_api.DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE + | deconz_api.NetworkState.CONNECTED + ) + api._handle_device_state_value(device_state) + await asyncio.sleep(0) + await asyncio.sleep(0) + await asyncio.sleep(0) + assert uart_gw.send.call_count == 1 + assert api._data_indication is True + + api.data_received( + uart_gw.send.call_args[0][0][0:2] + + bytes([status]) + + binascii.unhexlify("0800010022") + ) + await asyncio.sleep(0) + await asyncio.sleep(0) + await asyncio.sleep(0) + assert api._data_indication is False diff --git a/zigpy_deconz/api.py b/zigpy_deconz/api.py index 04075d0..21307d0 100644 --- a/zigpy_deconz/api.py +++ b/zigpy_deconz/api.py @@ -9,6 +9,7 @@ import serial from zigpy.config import CONF_DEVICE_PATH +import zigpy.exceptions from zigpy.types import APSStatus, Channels from zigpy_deconz.exception import APIException, CommandError @@ -332,14 +333,8 @@ def data_received(self, data): status = Status(data[2]) except ValueError: status = data[2] - try: - data, _ = t.deserialize(data[5:], schema) - except Exception as exc: - LOGGER.warning("Failed to deserialize frame: %s", binascii.hexlify(data)) - if solicited and seq in self._awaiting: - fut = self._awaiting.pop(seq) - fut.set_exception(exc) - return + + fut = None if solicited and seq in self._awaiting: fut = self._awaiting.pop(seq) if status != Status.SUCCESS: @@ -347,6 +342,20 @@ def data_received(self, data): CommandError(status, "%s, status: %s" % (command, status)) ) return + + try: + data, _ = t.deserialize(data[5:], schema) + except Exception: + LOGGER.warning("Failed to deserialize frame: %s", binascii.hexlify(data)) + if fut is not None: + fut.set_exception( + APIException( + f"Failed to deserialize frame: {binascii.hexlify(data)}" + ) + ) + return + + if fut is not None: fut.set_result(data) getattr(self, "_handle_%s" % (command.name,))(data) @@ -465,7 +474,7 @@ async def _aps_data_indication(self): binascii.hexlify(r[8]), ) return r - except asyncio.TimeoutError: + except (asyncio.TimeoutError, zigpy.exceptions.ZigbeeException): self._data_indication = False def _handle_aps_data_indication(self, data): @@ -527,7 +536,7 @@ async def _aps_data_confirm(self): r[5], ) return r - except asyncio.TimeoutError: + except (asyncio.TimeoutError, zigpy.exceptions.ZigbeeException): self._data_confirm = False def _handle_add_neighbour(self, data) -> None: From 4a341e926d89f78ef526bc3bb51505443076a659 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 22 Oct 2020 12:24:17 -0400 Subject: [PATCH 3/3] 0.11.0 Version bump --- zigpy_deconz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigpy_deconz/__init__.py b/zigpy_deconz/__init__.py index ff377f6..1f1b828 100644 --- a/zigpy_deconz/__init__.py +++ b/zigpy_deconz/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 MAJOR_VERSION = 0 MINOR_VERSION = 11 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION)