Skip to content

Commit

Permalink
Merge pull request #191 from puddly/rc
Browse files Browse the repository at this point in the history
0.15.0 Release
  • Loading branch information
puddly authored Apr 1, 2022
2 parents 2f8b50b + 39e04c1 commit c221599
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 56 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 20.8b1
rev: 22.3.0
hooks:
- id: black
args:
Expand All @@ -16,7 +16,7 @@ repos:
- pydocstyle==5.1.1

- repo: https://github.com/PyCQA/isort
rev: 5.5.2
rev: 5.10.1
hooks:
- id: isort

Expand Down
22 changes: 0 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,6 @@ The goal of this project to add native support for the Dresden-Elektronik deCONZ

This library uses the deCONZ serial protocol for communicating with [ConBee](https://www.dresden-elektronik.de/conbee/), [ConBee II (ConBee 2)](https://shop.dresden-elektronik.de/conbee-2.html), and [RaspBee](https://www.dresden-elektronik.de/raspbee/) adapters from [Dresden-Elektronik](https://github.com/dresden-elektronik/).

# Testing new releases

Testing a new release of the zigpy-deconz library before it is released in Home Assistant.

If you are using Supervised Home Assistant (formerly known as the Hassio/Hass.io distro):
- Add https://github.com/home-assistant/hassio-addons-development as "add-on" repository
- Install "Custom deps deployment" addon
- Update config like:
```
pypi:
- zigpy-deconz==0.9.0
apk: []
```
where 0.5.1 is the new version
- Start the addon

If you are instead using some custom python installation of Home Assistant then do this:
- Activate your python virtual env
- Update package with ``pip``
```
pip install zigpy-deconz==0.9.0
# Releases via PyPI
Tagged versions are also released via PyPI

Expand Down
27 changes: 25 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import binascii
import enum
import logging

import pytest
Expand Down Expand Up @@ -146,13 +147,15 @@ def mock_api_frame(name, *args):


def _fake_args(arg_type):
if isinstance(arg_type(), t.DeconzAddressEndpoint):
if issubclass(arg_type, enum.Enum):
return list(arg_type)[0] # Pick the first enum value
elif issubclass(arg_type, t.DeconzAddressEndpoint):
addr = t.DeconzAddressEndpoint()
addr.address_mode = t.ADDRESS_MODE.NWK
addr.address = t.uint8_t(0)
addr.endpoint = t.uint8_t(0)
return addr
if isinstance(arg_type(), t.EUI64):
elif issubclass(arg_type, t.EUI64):
return t.EUI64([0x01] * 8)

return arg_type()
Expand Down Expand Up @@ -609,3 +612,23 @@ async def test_set_item(api):
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


@pytest.mark.parametrize("relays", (None, [], [0x1234, 0x5678]))
async def test_aps_data_request_relays(relays, api):
mock_cmd = api._command = AsyncMock()

await api.aps_data_request(
0x00, # req id
t.DeconzAddressEndpoint.deserialize(b"\x02\xaa\x55\x01")[0], # dst + ep
0x0104, # profile id
0x0007, # cluster id
0x01, # src ep
b"aps payload",
relays=relays,
)
assert mock_cmd.call_count == 1

if relays:
assert isinstance(mock_cmd.mock_calls[0][1][-1], t.NWKList)
assert mock_cmd.mock_calls[0][1][-1] == t.NWKList(relays)
58 changes: 52 additions & 6 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,21 @@ async def test_permit(app, nwk):
assert app._api.write_parameter.call_args_list[0][0][1] == time_s


async def _test_request(app, send_success=True, aps_data_error=False, **kwargs):
async def _test_request(app, *, send_success=True, aps_data_error=False, **kwargs):
seq = 123

async def req_mock(req_id, dst_addr_ep, profile, cluster, src_ep, data):
async def req_mock(
req_id,
dst_addr_ep,
profile,
cluster,
src_ep,
data,
*,
relays=None,
tx_options=t.DeconzTransmitOptions.USE_NWK_KEY_SECURITY,
radius=0
):
if aps_data_error:
raise zigpy_deconz.exception.CommandError(1, "Command Error")
if send_success:
Expand All @@ -327,6 +338,7 @@ async def req_mock(req_id, dst_addr_ep, profile, cluster, src_ep, data):
app._pending[req_id].result.set_result(1)

app._api.aps_data_request = MagicMock(side_effect=req_mock)
app._api.protocol_version = 0
device = zigpy.device.Device(app, sentinel.ieee, 0x1122)
app.get_device = MagicMock(return_value=device)

Expand All @@ -336,27 +348,61 @@ async def req_mock(req_id, dst_addr_ep, profile, cluster, src_ep, data):
async def test_request_send_success(app):
req_id = sentinel.req_id
app.get_sequence = MagicMock(return_value=req_id)
r = await _test_request(app, True)
r = await _test_request(app, send_success=True)
assert r[0] == 0

r = await _test_request(app, True, use_ieee=True)
r = await _test_request(app, send_success=True, use_ieee=True)
assert r[0] == 0


async def test_request_send_fail(app):
req_id = sentinel.req_id
app.get_sequence = MagicMock(return_value=req_id)
r = await _test_request(app, False)
r = await _test_request(app, send_success=False)
assert r[0] != 0


async def test_request_send_aps_data_error(app):
req_id = sentinel.req_id
app.get_sequence = MagicMock(return_value=req_id)
r = await _test_request(app, False, aps_data_error=True)
r = await _test_request(app, send_success=False, aps_data_error=True)
assert r[0] != 0


async def test_request_retry(app):
req_id = sentinel.req_id
app.get_sequence = MagicMock(return_value=req_id)

device = zigpy.device.Device(app, sentinel.ieee, 0x1122)
device.relays = [0x5678, 0x1234]
app.get_device = MagicMock(return_value=device)

async def req_mock(
req_id,
dst_addr_ep,
profile,
cluster,
src_ep,
data,
*,
relays=None,
tx_options=t.DeconzTransmitOptions.USE_NWK_KEY_SECURITY,
radius=0
):
app._pending[req_id].result.set_result(1)

app._api.aps_data_request = MagicMock(side_effect=req_mock)
app._api.protocol_version = application.PROTO_VER_MANUAL_SOURCE_ROUTE

await app.request(device, 0x0260, 1, 2, 3, 123, b"\x01\x02\x03")

assert len(app._api.aps_data_request.mock_calls) == 2
without_relays, with_relays = app._api.aps_data_request.mock_calls

assert without_relays[2]["relays"] is None
assert with_relays[2]["relays"] == [0x0000, 0x1234, 0x5678]


async def _test_broadcast(app, send_success=True, aps_data_error=False, **kwargs):
seq = sentinel.req_id

Expand Down
16 changes: 16 additions & 0 deletions tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,19 @@ def test_deconz_addr_ep():
a.serialize()
a.endpoint = 0xCC
assert a.serialize() == data


def test_nwklist():
assert t.NWKList([]).serialize() == b"\x00"
assert t.NWKList([0x1234]).serialize() == b"\x01" + t.NWK(0x1234).serialize()
assert (
t.NWKList([0x1234, 0x5678]).serialize()
== b"\x02" + t.NWK(0x1234).serialize() + t.NWK(0x5678).serialize()
)

assert t.NWKList.deserialize(b"\x00abc") == (t.NWKList([]), b"abc")
assert t.NWKList.deserialize(b"\x01\x34\x12abc") == (t.NWKList([0x1234]), b"abc")
assert t.NWKList.deserialize(b"\x02\x34\x12\x78\x56abc") == (
t.NWKList([0x1234, 0x5678]),
b"abc",
)
2 changes: 1 addition & 1 deletion zigpy_deconz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# coding: utf-8
MAJOR_VERSION = 0
MINOR_VERSION = 14
MINOR_VERSION = 15
PATCH_VERSION = "0"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
49 changes: 39 additions & 10 deletions zigpy_deconz/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,15 @@ def _missing_(cls, value):
Command.aps_data_request: (
t.uint16_t,
t.uint8_t,
t.uint8_t,
t.DeconzSendDataFlags,
t.DeconzAddressEndpoint,
t.uint16_t,
t.uint16_t,
t.uint8_t,
t.LVBytes,
t.uint8_t,
t.uint8_t,
t.NWKList, # optional
),
Command.change_network_state: (t.uint8_t,),
Command.device_state: (t.uint8_t, t.uint8_t, t.uint8_t),
Expand Down Expand Up @@ -515,33 +516,61 @@ def _handle_aps_data_indication(self, data):
) # rssi

async def aps_data_request(
self, req_id, dst_addr_ep, profile, cluster, src_ep, aps_payload
self,
req_id,
dst_addr_ep,
profile,
cluster,
src_ep,
aps_payload,
*,
relays=None,
tx_options=t.DeconzTransmitOptions.USE_NWK_KEY_SECURITY,
radius=0,
):
dst = dst_addr_ep.serialize()
length = len(dst) + len(aps_payload) + 11
delays = (0.5, 1.0, 1.5, None)

flags = t.DeconzSendDataFlags.NONE
extras = []

# https://github.com/zigpy/zigpy-deconz/issues/180#issuecomment-1017932865
if relays:
# There is a max of 9 relays
assert len(relays) <= 9

# APS ACKs should be used to mitigate errors.
tx_options |= t.DeconzTransmitOptions.USE_APS_ACKS

flags |= t.DeconzSendDataFlags.RELAYS
extras.append(t.NWKList(relays))

length += sum(len(e.serialize()) for e in extras)

for delay in delays:
try:
return await self._command(
Command.aps_data_request,
length,
req_id,
0,
flags,
dst_addr_ep,
profile,
cluster,
src_ep,
aps_payload,
2,
0,
tx_options,
radius,
*extras,
)
except CommandError as ex:
LOGGER.debug("'aps_data_request' failure: %s", ex)
if delay is not None and ex.status == Status.BUSY:
LOGGER.debug("retrying 'aps_data_request' in %ss", delay)
await asyncio.sleep(delay)
continue
raise
if delay is None or ex.status != Status.BUSY:
raise

LOGGER.debug("retrying 'aps_data_request' in %ss", delay)
await asyncio.sleep(delay)

def _handle_aps_data_request(self, data):
LOGGER.debug("APS data request response: %s", data)
Expand Down
10 changes: 10 additions & 0 deletions zigpy_deconz/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@
from zigpy.config import ( # noqa: F401 pylint: disable=unused-import
CONF_DEVICE,
CONF_DEVICE_PATH,
CONF_NWK,
CONF_NWK_CHANNEL,
CONF_NWK_CHANNELS,
CONF_NWK_EXTENDED_PAN_ID,
CONF_NWK_KEY,
CONF_NWK_PAN_ID,
CONF_NWK_TC_ADDRESS,
CONF_NWK_TC_LINK_KEY,
CONF_NWK_UPDATE_ID,
CONFIG_SCHEMA,
SCHEMA_DEVICE,
cv_boolean,
)

CONF_WATCHDOG_TTL = "watchdog_ttl"
Expand Down
Loading

0 comments on commit c221599

Please sign in to comment.