Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
88 changes: 23 additions & 65 deletions zigpy_deconz/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand Down Expand Up @@ -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])
Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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):
Expand Down