Skip to content

Commit fe78fd5

Browse files
authored
Wrap _command() in wait_for (#21)
1 parent 431a52e commit fe78fd5

File tree

2 files changed

+36
-73
lines changed

2 files changed

+36
-73
lines changed

tests/test_api.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,22 @@ def test_commands():
5252

5353

5454
@pytest.mark.asyncio
55-
async def test_command(api):
55+
async def test_command(api, monkeypatch):
5656
def mock_api_frame(name, *args):
5757
c = deconz_api.TX_COMMANDS[name]
5858
return mock.sentinel.api_frame_data, c[2]
5959
api._api_frame = mock.MagicMock(side_effect=mock_api_frame)
6060
api._uart.send = mock.MagicMock()
6161

62+
async def mock_fut():
63+
return mock.sentinel.cmd_result
64+
monkeypatch.setattr(asyncio, 'Future', mock_fut)
65+
6266
for cmd_name, cmd_opts in deconz_api.TX_COMMANDS.items():
6367
_, _, expect_reply = cmd_opts
64-
ret = api._command(cmd_name, mock.sentinel.cmd_data)
68+
ret = await api._command(cmd_name, mock.sentinel.cmd_data)
6569
if expect_reply:
66-
assert asyncio.isfuture(ret) is True
67-
ret.cancel()
70+
assert ret is mock.sentinel.cmd_result
6871
else:
6972
assert ret is None
7073
assert api._api_frame.call_count == 1
@@ -168,7 +171,7 @@ def mock_cmd(*args, **kwargs):
168171
if success:
169172
res.set_result([7, 0x22, 0x11, mock.sentinel.dst_addr, 1, 0x00,
170173
0, 0, 0, 0])
171-
return res
174+
return asyncio.wait_for(res, timeout=deconz_api.COMMAND_TIMEOUT)
172175

173176
api._command = mock_cmd
174177
api._data_confirm = True
@@ -195,7 +198,7 @@ def mock_cmd(*args, **kwargs):
195198
if success:
196199
res.set_result([s.len, 0x22, t.DeconzAddress(), 1,
197200
t.DeconzAddress(), 1, 0x0104, 0x0000, b'\x00\x01\x02'])
198-
return res
201+
return asyncio.wait_for(res, timeout=deconz_api.COMMAND_TIMEOUT)
199202

200203
api._command = mock_cmd
201204
api._data_indication = True
@@ -242,9 +245,11 @@ async def test_aps_data_request_timeout(api, monkeypatch):
242245
b'aps payload'
243246
]
244247

245-
mock_cmd = mock.MagicMock(return_value=asyncio.Future())
246-
api._command = mock_cmd
247248
monkeypatch.setattr(deconz_api, 'COMMAND_TIMEOUT', .1)
249+
mock_cmd = mock.MagicMock(
250+
return_value=asyncio.wait_for(asyncio.Future(),
251+
timeout=deconz_api.COMMAND_TIMEOUT))
252+
api._command = mock_cmd
248253

249254
with pytest.raises(asyncio.TimeoutError):
250255
await api.aps_data_request(*params)

zigpy_deconz/api.py

Lines changed: 23 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ async def connect(self, device, baudrate=DECONZ_BAUDRATE):
124124
def close(self):
125125
return self._uart.close()
126126

127-
def _command(self, name, *args):
127+
async def _command(self, name, *args):
128128
LOGGER.debug("Command %s %s", name, args)
129129
data, needs_response = self._api_frame(name, *args)
130130
self._uart.send(data)
@@ -133,7 +133,11 @@ def _command(self, name, *args):
133133
fut = asyncio.Future()
134134
self._awaiting[self._seq] = (fut, )
135135
self._seq = (self._seq % 255) + 1
136-
return fut
136+
try:
137+
return await asyncio.wait_for(fut, timeout=COMMAND_TIMEOUT)
138+
except asyncio.TimeoutError:
139+
LOGGER.warning("No response to '%s' command", name)
140+
raise
137141

138142
def _api_frame(self, name, *args):
139143
c = TX_COMMANDS[name]
@@ -169,70 +173,35 @@ def data_received(self, data):
169173
fut.set_result(data)
170174
getattr(self, '_handle_%s' % (command, ))(data)
171175

172-
async def device_state(self):
173-
try:
174-
return await asyncio.wait_for(
175-
self._command('device_state', 0, 0, 0),
176-
timeout=COMMAND_TIMEOUT
177-
)
178-
except asyncio.TimeoutError:
179-
LOGGER.warning("No response to device_state command")
180-
raise
176+
def device_state(self):
177+
return self._command('device_state', 0, 0, 0)
181178

182179
def _handle_device_state(self, data):
183180
LOGGER.debug("Device state response: %s", data)
184181
self._handle_device_state_value(data[0])
185182

186-
async def change_network_state(self, state):
187-
try:
188-
return await asyncio.wait_for(
189-
self._command('change_network_state', state),
190-
timeout=COMMAND_TIMEOUT
191-
)
192-
except asyncio.TimeoutError:
193-
LOGGER.warning("No response to change_network_state command")
194-
raise
183+
def change_network_state(self, state):
184+
return self._command('change_network_state', state)
195185

196186
def _handle_change_network_state(self, data):
197187
LOGGER.debug("Change network state response: %s", NETWORK_STATE(data[0]).name)
198188

199-
async def read_parameter(self, id_):
200-
try:
201-
return await asyncio.wait_for(
202-
self._command('read_parameter', 1, id_),
203-
timeout=COMMAND_TIMEOUT
204-
)
205-
except asyncio.TimeoutError:
206-
LOGGER.warning("No response to read_parameter command")
207-
raise
189+
def read_parameter(self, id_):
190+
return self._command('read_parameter', 1, id_)
208191

209192
def _handle_read_parameter(self, data):
210193
LOGGER.debug("Read parameter %s response: %s", NETWORK_PARAMETER_BY_ID[data[1]][0], data[2])
211194

212-
async def write_parameter(self, id_, value):
213-
try:
214-
v = NETWORK_PARAMETER_BY_ID[id_][1](value).serialize()
215-
length = len(v) + 1
216-
return await asyncio.wait_for(
217-
self._command('write_parameter', length, id_, v),
218-
timeout=COMMAND_TIMEOUT
219-
)
220-
except asyncio.TimeoutError:
221-
LOGGER.warning("No response to write_parameter command")
222-
raise
195+
def write_parameter(self, id_, value):
196+
v = NETWORK_PARAMETER_BY_ID[id_][1](value).serialize()
197+
length = len(v) + 1
198+
return self._command('write_parameter', length, id_, v)
223199

224200
def _handle_write_parameter(self, data):
225201
LOGGER.debug("Write parameter %s: SUCCESS", NETWORK_PARAMETER_BY_ID[data[1]][0])
226202

227-
async def version(self):
228-
try:
229-
return await asyncio.wait_for(
230-
self._command('version'),
231-
timeout=COMMAND_TIMEOUT
232-
)
233-
except asyncio.TimeoutError:
234-
LOGGER.warning("No response to version command")
235-
raise
203+
def version(self):
204+
return self._command('version')
236205

237206
def _handle_version(self, data):
238207
LOGGER.debug("Version response: %x", data[0])
@@ -243,16 +212,13 @@ def _handle_device_state_changed(self, data):
243212

244213
async def _aps_data_indication(self):
245214
try:
246-
r = await asyncio.wait_for(
247-
self._command('aps_data_indication', 1, 1),
248-
timeout=COMMAND_TIMEOUT)
215+
r = await self._command('aps_data_indication', 1, 1)
249216
LOGGER.debug(("'aps_data_indication' responnse from %s, ep: %s, "
250217
"profile: 0x%04x, cluster_id: 0x%04x, data: %s"),
251218
r[4], r[5], r[6], r[7], binascii.hexlify(r[8]))
252219
return r
253220
except asyncio.TimeoutError:
254221
self._data_indication = False
255-
LOGGER.debug("No response to 'aps_data_indication'")
256222

257223
def _handle_aps_data_indication(self, data):
258224
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,
274240
delays = (0.5, 1.0, 1.5, None)
275241
for delay in delays:
276242
try:
277-
return await asyncio.wait_for(
278-
self._command('aps_data_request', length, req_id, 0,
279-
dst_addr_ep, profile, cluster, src_ep,
280-
aps_payload, 2, 0),
281-
timeout=COMMAND_TIMEOUT
282-
)
283-
except asyncio.TimeoutError:
284-
LOGGER.warning("No response to aps_data_request command")
285-
raise
243+
return await self._command('aps_data_request', length, req_id,
244+
0, dst_addr_ep, profile, cluster,
245+
src_ep, aps_payload, 2, 0)
286246
except CommandError as ex:
287247
LOGGER.debug("'aps_data_request' failure: %s", ex)
288248
if delay is not None and ex.status == STATUS.BUSY:
@@ -297,13 +257,11 @@ def _handle_aps_data_request(self, data):
297257

298258
async def _aps_data_confirm(self):
299259
try:
300-
r = await asyncio.wait_for(self._command('aps_data_confirm', 0),
301-
timeout=COMMAND_TIMEOUT)
260+
r = await self._command('aps_data_confirm', 0)
302261
LOGGER.debug(("Request id: 0x%02x 'aps_data_confirm' for %s, "
303262
"status: 0x%02x"), r[2], r[3], r[5])
304263
return r
305264
except asyncio.TimeoutError:
306-
LOGGER.debug("No response to 'aps_data_confirm'")
307265
self._data_confirm = False
308266

309267
def _handle_aps_data_confirm(self, data):

0 commit comments

Comments
 (0)