From f11faabfa03a322354752aeb248c19232e939a9e Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 18 Feb 2019 22:16:56 -0500 Subject: [PATCH] Wrap _command() in wait_for --- tests/test_api.py | 21 ++++++----- zigpy_deconz/api.py | 88 ++++++++++++--------------------------------- 2 files changed, 36 insertions(+), 73 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index de51fbf..d972005 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -52,19 +52,22 @@ def test_commands(): @pytest.mark.asyncio -async def test_command(api): +async def test_command(api, monkeypatch): def mock_api_frame(name, *args): c = deconz_api.TX_COMMANDS[name] return mock.sentinel.api_frame_data, c[2] api._api_frame = mock.MagicMock(side_effect=mock_api_frame) api._uart.send = mock.MagicMock() + async def mock_fut(): + return mock.sentinel.cmd_result + monkeypatch.setattr(asyncio, 'Future', mock_fut) + for cmd_name, cmd_opts in deconz_api.TX_COMMANDS.items(): _, _, expect_reply = cmd_opts - ret = api._command(cmd_name, mock.sentinel.cmd_data) + ret = await api._command(cmd_name, mock.sentinel.cmd_data) if expect_reply: - assert asyncio.isfuture(ret) is True - ret.cancel() + assert ret is mock.sentinel.cmd_result else: assert ret is None assert api._api_frame.call_count == 1 @@ -168,7 +171,7 @@ def mock_cmd(*args, **kwargs): if success: res.set_result([7, 0x22, 0x11, mock.sentinel.dst_addr, 1, 0x00, 0, 0, 0, 0]) - return res + return asyncio.wait_for(res, timeout=deconz_api.COMMAND_TIMEOUT) api._command = mock_cmd api._data_confirm = True @@ -195,7 +198,7 @@ def mock_cmd(*args, **kwargs): if success: res.set_result([s.len, 0x22, t.DeconzAddress(), 1, t.DeconzAddress(), 1, 0x0104, 0x0000, b'\x00\x01\x02']) - return res + return asyncio.wait_for(res, timeout=deconz_api.COMMAND_TIMEOUT) api._command = mock_cmd api._data_indication = True @@ -242,9 +245,11 @@ async def test_aps_data_request_timeout(api, monkeypatch): b'aps payload' ] - mock_cmd = mock.MagicMock(return_value=asyncio.Future()) - api._command = mock_cmd monkeypatch.setattr(deconz_api, 'COMMAND_TIMEOUT', .1) + mock_cmd = mock.MagicMock( + return_value=asyncio.wait_for(asyncio.Future(), + timeout=deconz_api.COMMAND_TIMEOUT)) + api._command = mock_cmd with pytest.raises(asyncio.TimeoutError): await api.aps_data_request(*params) diff --git a/zigpy_deconz/api.py b/zigpy_deconz/api.py index 08c94c5..b578240 100644 --- a/zigpy_deconz/api.py +++ b/zigpy_deconz/api.py @@ -124,7 +124,7 @@ async def connect(self, device, baudrate=DECONZ_BAUDRATE): def close(self): return self._uart.close() - def _command(self, name, *args): + async def _command(self, name, *args): LOGGER.debug("Command %s %s", name, args) data, needs_response = self._api_frame(name, *args) self._uart.send(data) @@ -133,7 +133,11 @@ def _command(self, name, *args): fut = asyncio.Future() self._awaiting[self._seq] = (fut, ) self._seq = (self._seq % 255) + 1 - return fut + try: + return await asyncio.wait_for(fut, timeout=COMMAND_TIMEOUT) + except asyncio.TimeoutError: + LOGGER.warning("No response to '%s' command", name) + raise def _api_frame(self, name, *args): c = TX_COMMANDS[name] @@ -169,70 +173,35 @@ def data_received(self, data): fut.set_result(data) getattr(self, '_handle_%s' % (command, ))(data) - async def device_state(self): - try: - return await asyncio.wait_for( - self._command('device_state', 0, 0, 0), - timeout=COMMAND_TIMEOUT - ) - except asyncio.TimeoutError: - LOGGER.warning("No response to device_state command") - raise + def device_state(self): + return self._command('device_state', 0, 0, 0) def _handle_device_state(self, data): LOGGER.debug("Device state response: %s", data) self._handle_device_state_value(data[0]) - async def change_network_state(self, state): - try: - return await asyncio.wait_for( - self._command('change_network_state', state), - timeout=COMMAND_TIMEOUT - ) - except asyncio.TimeoutError: - LOGGER.warning("No response to change_network_state command") - raise + def change_network_state(self, state): + return self._command('change_network_state', state) def _handle_change_network_state(self, data): LOGGER.debug("Change network state response: %s", NETWORK_STATE(data[0]).name) - async def read_parameter(self, id_): - try: - return await asyncio.wait_for( - self._command('read_parameter', 1, id_), - timeout=COMMAND_TIMEOUT - ) - except asyncio.TimeoutError: - LOGGER.warning("No response to read_parameter command") - raise + def read_parameter(self, id_): + return self._command('read_parameter', 1, id_) def _handle_read_parameter(self, data): LOGGER.debug("Read parameter %s response: %s", NETWORK_PARAMETER_BY_ID[data[1]][0], data[2]) - async def write_parameter(self, id_, value): - try: - v = NETWORK_PARAMETER_BY_ID[id_][1](value).serialize() - length = len(v) + 1 - return await asyncio.wait_for( - self._command('write_parameter', length, id_, v), - timeout=COMMAND_TIMEOUT - ) - except asyncio.TimeoutError: - LOGGER.warning("No response to write_parameter command") - raise + def write_parameter(self, id_, value): + v = NETWORK_PARAMETER_BY_ID[id_][1](value).serialize() + length = len(v) + 1 + return self._command('write_parameter', length, id_, v) def _handle_write_parameter(self, data): LOGGER.debug("Write parameter %s: SUCCESS", NETWORK_PARAMETER_BY_ID[data[1]][0]) - async def version(self): - try: - return await asyncio.wait_for( - self._command('version'), - timeout=COMMAND_TIMEOUT - ) - except asyncio.TimeoutError: - LOGGER.warning("No response to version command") - raise + def version(self): + return self._command('version') def _handle_version(self, data): LOGGER.debug("Version response: %x", data[0]) @@ -243,16 +212,13 @@ def _handle_device_state_changed(self, data): async def _aps_data_indication(self): try: - r = await asyncio.wait_for( - self._command('aps_data_indication', 1, 1), - timeout=COMMAND_TIMEOUT) + r = await self._command('aps_data_indication', 1, 1) LOGGER.debug(("'aps_data_indication' responnse from %s, ep: %s, " "profile: 0x%04x, cluster_id: 0x%04x, data: %s"), r[4], r[5], r[6], r[7], binascii.hexlify(r[8])) return r except asyncio.TimeoutError: self._data_indication = False - LOGGER.debug("No response to 'aps_data_indication'") def _handle_aps_data_indication(self, data): LOGGER.debug("APS data indication response: %s", data) @@ -274,15 +240,9 @@ async def aps_data_request(self, req_id, dst_addr_ep, profile, cluster, src_ep, delays = (0.5, 1.0, 1.5, None) for delay in delays: try: - return await asyncio.wait_for( - self._command('aps_data_request', length, req_id, 0, - dst_addr_ep, profile, cluster, src_ep, - aps_payload, 2, 0), - timeout=COMMAND_TIMEOUT - ) - except asyncio.TimeoutError: - LOGGER.warning("No response to aps_data_request command") - raise + return await self._command('aps_data_request', length, req_id, + 0, dst_addr_ep, profile, cluster, + src_ep, aps_payload, 2, 0) except CommandError as ex: LOGGER.debug("'aps_data_request' failure: %s", ex) if delay is not None and ex.status == STATUS.BUSY: @@ -297,13 +257,11 @@ def _handle_aps_data_request(self, data): async def _aps_data_confirm(self): try: - r = await asyncio.wait_for(self._command('aps_data_confirm', 0), - timeout=COMMAND_TIMEOUT) + r = await self._command('aps_data_confirm', 0) LOGGER.debug(("Request id: 0x%02x 'aps_data_confirm' for %s, " "status: 0x%02x"), r[2], r[3], r[5]) return r except asyncio.TimeoutError: - LOGGER.debug("No response to 'aps_data_confirm'") self._data_confirm = False def _handle_aps_data_confirm(self, data):