From db2861d0dc9a0ebf3b8331490c1652d66b26e156 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Wed, 10 Mar 2021 11:07:02 -0500 Subject: [PATCH 1/5] Accept /dev/ttyACMx as a RaspBee device --- zigpy_deconz/zigbee/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zigpy_deconz/zigbee/application.py b/zigpy_deconz/zigbee/application.py index 5a40a43..8c0ad2d 100644 --- a/zigpy_deconz/zigbee/application.py +++ b/zigpy_deconz/zigbee/application.py @@ -381,7 +381,7 @@ def __init__(self, version: int, device_path: str, *args): """Initialize instance.""" super().__init__(*args) - is_gpio_device = re.match(r"/dev/tty(S|AMA)\d+", device_path) + is_gpio_device = re.match(r"/dev/tty(S|AMA|ACM)\d+", device_path) self._model = "RaspBee" if is_gpio_device else "ConBee" self._model += " II" if ((version & 0x0000FF00) == 0x00000700) else "" From 407ff653974450575ef6c08bab0007ddc10cc0f2 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 18 Mar 2021 15:26:42 -0400 Subject: [PATCH 2/5] Better handling of delayed or duplicate responses (#153) * Better handling of delayed or duplicate responses * fixup * Ignore exceptions * Reset aps_data_confirm/request state on error --- zigpy_deconz/api.py | 28 +++++++++++++++++++--------- zigpy_deconz/uart.py | 5 ++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/zigpy_deconz/api.py b/zigpy_deconz/api.py index 21307d0..b7b70fa 100644 --- a/zigpy_deconz/api.py +++ b/zigpy_deconz/api.py @@ -9,7 +9,6 @@ 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 @@ -307,7 +306,7 @@ async def _command(self, cmd, *args): LOGGER.warning( "No response to '%s' command with seq id '0x%02x'", cmd, seq ) - self._awaiting.pop(seq) + self._awaiting.pop(seq, None) raise def _api_frame(self, cmd, *args): @@ -338,16 +337,21 @@ def data_received(self, data): if solicited and seq in self._awaiting: fut = self._awaiting.pop(seq) if status != Status.SUCCESS: - fut.set_exception( - CommandError(status, "%s, status: %s" % (command, status)) - ) + try: + fut.set_exception( + CommandError(status, "%s, status: %s" % (command, status)) + ) + except asyncio.InvalidStateError: + LOGGER.warning( + "Duplicate or delayed response for 0x:%02x sequence", seq + ) 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: + if fut is not None and not fut.done(): fut.set_exception( APIException( f"Failed to deserialize frame: {binascii.hexlify(data)}" @@ -356,7 +360,13 @@ def data_received(self, data): return if fut is not None: - fut.set_result(data) + try: + fut.set_result(data) + except asyncio.InvalidStateError: + LOGGER.warning( + "Duplicate or delayed response for 0x:%02x sequence", seq + ) + getattr(self, "_handle_%s" % (command.name,))(data) add_neighbour = functools.partialmethod(_command, Command.add_neighbour, 12) @@ -474,7 +484,7 @@ async def _aps_data_indication(self): binascii.hexlify(r[8]), ) return r - except (asyncio.TimeoutError, zigpy.exceptions.ZigbeeException): + finally: self._data_indication = False def _handle_aps_data_indication(self, data): @@ -536,7 +546,7 @@ async def _aps_data_confirm(self): r[5], ) return r - except (asyncio.TimeoutError, zigpy.exceptions.ZigbeeException): + finally: self._data_confirm = False def _handle_add_neighbour(self, data) -> None: diff --git a/zigpy_deconz/uart.py b/zigpy_deconz/uart.py index e421b1f..d26dfdb 100644 --- a/zigpy_deconz/uart.py +++ b/zigpy_deconz/uart.py @@ -88,7 +88,10 @@ def data_received(self, data): continue LOGGER.debug("Frame received: 0x%s", binascii.hexlify(frame).decode()) - self._api.data_received(frame) + try: + self._api.data_received(frame) + except Exception as exc: + LOGGER.error("Unexpected error handling the frame: %s", exc) def _unescape(self, data): ret = [] From 582f45a6ca49e2fb37e465d136556fc2768a66cb Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 18 Mar 2021 19:51:39 -0400 Subject: [PATCH 3/5] Update tests and coverage (#155) * Fix tests * Fix tests * Just barely pass above threshold * Update test requirements * Fix coveralls --- .github/workflows/ci.yml | 2 +- requirements_test.txt | 2 +- tests/test_api.py | 35 +++++++++++++++++++---------------- zigpy_deconz/api.py | 5 +++++ 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b854707..0334132 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -330,7 +330,7 @@ jobs: COVERALLS_PARALLEL: true run: | . venv/bin/activate - coveralls + coveralls --service=github coverage: diff --git a/requirements_test.txt b/requirements_test.txt index 3e7f591..120f3a4 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ # Test dependencies asynctest -coveralls +coveralls==3.0.1 pytest pytest-cov pytest-asyncio diff --git a/tests/test_api.py b/tests/test_api.py index 13ceab8..9fdc94a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,10 +3,8 @@ import asyncio import binascii import logging -import sys import pytest -import serial import zigpy.config from zigpy_deconz import api as deconz_api, types as t, uart @@ -239,7 +237,7 @@ def test_simplified_beacon(api): async def test_aps_data_confirm(api, monkeypatch): - monkeypatch.setattr(deconz_api, "COMMAND_TIMEOUT", 0.1) + monkeypatch.setattr(deconz_api, "COMMAND_TIMEOUT", 0.01) success = True @@ -254,9 +252,10 @@ def mock_cmd(*args, **kwargs): res = await api._aps_data_confirm() assert res is not None - assert api._data_confirm is True + assert api._data_confirm is False success = False + api._data_confirm = True res = await api._aps_data_confirm() assert res is None assert api._data_confirm is False @@ -291,9 +290,10 @@ def mock_cmd(*args, **kwargs): res = await api._aps_data_indication() assert res is not None - assert api._data_indication is True + assert api._data_indication is False success = False + api._data_indication = True res = await api._aps_data_indication() assert res is None assert api._data_indication is False @@ -524,16 +524,7 @@ async def test_probe_success(mock_connect, mock_device_state): @patch.object(deconz_api.Deconz, "device_state", new_callable=AsyncMock) @patch("zigpy_deconz.uart.connect", return_value=MagicMock(spec_set=uart.Gateway)) -@pytest.mark.parametrize( - "exception", - ( - asyncio.TimeoutError, - serial.SerialException, - zigpy_deconz.exception.CommandError, - ) - if sys.version_info[:3] != (3, 7, 9) - else (asyncio.TimeoutError,), -) +@pytest.mark.parametrize("exception", (asyncio.TimeoutError,)) async def test_probe_fail(mock_connect, mock_device_state, exception): """Test device probing fails.""" @@ -587,7 +578,7 @@ async def test_aps_data_req_deserialize_error(api, uart_gw, status, caplog): device_state = ( deconz_api.DeviceState.APSDE_DATA_INDICATION - | deconz_api.DeviceState.APSDE_DATA_REQUEST_SLOTS_AVAILABLE + | deconz_api.DeviceState.APSDE_DATA_CONFIRM | deconz_api.NetworkState.CONNECTED ) api._handle_device_state_value(device_state) @@ -606,3 +597,15 @@ async def test_aps_data_req_deserialize_error(api, uart_gw, status, caplog): await asyncio.sleep(0) await asyncio.sleep(0) assert api._data_indication is False + + +async def test_set_item(api): + """Test item setter.""" + + with patch.object(api, "write_parameter", new=AsyncMock()) as write_mock: + api["test"] = sentinel.test_param + for i in range(10): + await asyncio.sleep(0) + assert write_mock.await_count == 1 + assert write_mock.call_args[0][0] == "test" + assert write_mock.call_args[0][1] is sentinel.test_param diff --git a/zigpy_deconz/api.py b/zigpy_deconz/api.py index b7b70fa..71c846a 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 @@ -484,6 +485,8 @@ async def _aps_data_indication(self): binascii.hexlify(r[8]), ) return r + except (asyncio.TimeoutError, zigpy.exceptions.ZigbeeException): + pass finally: self._data_indication = False @@ -546,6 +549,8 @@ async def _aps_data_confirm(self): r[5], ) return r + except (asyncio.TimeoutError, zigpy.exceptions.ZigbeeException): + pass finally: self._data_confirm = False From 6eb96a6558086bfbe9c6658f48c44f8d33a14057 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 18 Mar 2021 19:56:59 -0400 Subject: [PATCH 4/5] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0334132..e446838 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -374,4 +374,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | . venv/bin/activate - coveralls --finish + coveralls --service=github --finish From 74ca93153cab2a932738296a682d2b318d09f17c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 23 Mar 2021 09:55:23 -0400 Subject: [PATCH 5/5] 0.12.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 5dd151a..62cca50 100644 --- a/zigpy_deconz/__init__.py +++ b/zigpy_deconz/__init__.py @@ -3,6 +3,6 @@ # coding: utf-8 MAJOR_VERSION = 0 MINOR_VERSION = 12 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}"