Skip to content

Commit

Permalink
0.16.2 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
Adminiuga authored May 19, 2020
2 parents 210f9c0 + f463879 commit 7d12ea8
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 40 deletions.
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
[![Build Status](https://travis-ci.org/zigpy/bellows.svg?branch=master)](https://travis-ci.org/zigpy/bellows)
[![Coverage](https://coveralls.io/repos/github/zigpy/bellows/badge.svg?branch=master)](https://coveralls.io/github/zigpy/bellows?branch=master)

`bellows` is a Python 3 project to implement support for EmberZNet devices using the EZSP protocol.
`bellows` is a Python 3 library implemention for the [zigpy](https://github.com/zigpy/zigpy) project to add Zigbee radio support for Silicon Labs EM35x ("Ember") based devices using the EZSP (EmberZNet Serial Protocol) interface.

The goal is to use this project to add support for the ZigBee Network
Coprocessor (NCP) in devices like the [Linear/Nortek/GoControl HubZ/QuickStick
Combo (HUSBZB-1)][HubZ] device to [Home Assistant][hass].
The project can be used as a stand-alone library, however, the main goal of this project is to add native support for EmberZNet Zigbee radio based USB stick devices (a.k.a. "Ember" dongles) to Home Assistant's built-in ZHA (Zigbee Home Automation) integration component, allowing Home Assistant with such hardware to nativly support direct control of compatible Zigbee devices, such as; Philips HUE, GE, Ledvance, Osram Lightify, Xiaomi/Aqara, IKEA Tradfri, Samsung SmartThings, and many more.

[Hubz]: http://www.gocontrol.com/detail.php?productId=6
[hass]: https://home-assistant.io/
- https://www.home-assistant.io/integrations/zha/

## Compatible hardware
bellows interacts with the Zigbee Network Coprocessor (NCP) with EmberZNet PRO Zigbee coordinator firmware using the EZSP protocol serial interface APIs via via UART for Silicon Labs EM35x Zigbee radio module/chips hardware. The library currectly supports the Silicon Labs EZSP (EmberZNet Serial Protocol) API versions for Silabs EM35x "Ember" SoCs using ASH or SPI protocols over a serial interface. The implementation of the SPI protocol assumes that the SPI provides a TTY-like software interface to the application, or is otherwise abstracted via the ZigBeePort interface.

## Hardware requirement

EmberZNet based Zigbee radios using the EZSP protocol (via the [bellows](https://github.com/zigpy/bellows) library for zigpy)
- [Nortek GoControl QuickStick Combo Model HUSBZB-1 (Z-Wave & Zigbee USB Adapter)](https://www.nortekcontrol.com/products/2gig/husbzb-1-gocontrol-quickstick-combo/)
Expand All @@ -21,6 +20,14 @@ EmberZNet based Zigbee radios using the EZSP protocol (via the [bellows](https:/
- Telegesis ETRX357USB (Note! This first have to be flashed with other EmberZNet firmware)
- Telegesis ETRX357USB-LRS (Note! This first have to be flashed with other EmberZNet firmware)
- Telegesis ETRX357USB-LRS+8M (Note! This first have to be flashed with other EmberZNet firmware)

## Firmware requirement

bellows requires that the Zigbee adapter/board/module is pre-flashed/flashed with compatible firmware with EmberZNet PRO Zigbee Stack that uses the standard Silicon Labs EZSP (EmberZNet Serial Protocol) APIs for ASH or SPI protocols over a serial interface.

Silabs did use to provide two main NCP images pre-build with firmware for EM35x, one image supported hardware flow control with a baud rate of 115200 and the other image supported software flow control with a rate of 57600.

Silicon Labs no longer provide pre-build firmware images, so now you have to build and compile firmware with their Simplicity Studio SDK for EmberZNet PRO Zigbee Protocol Stack Software. Simplicity Studio is a free download but building and compiling EmberZNet PRO Zigbee firmware images required that you have the serialnumber of an official Zigbee devkit registered to your Silicon Labs user account.

## Project status

Expand Down
2 changes: 1 addition & 1 deletion bellows/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
MAJOR_VERSION = 0
MINOR_VERSION = 16
PATCH_VERSION = "1"
PATCH_VERSION = "2"
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
8 changes: 5 additions & 3 deletions bellows/config/ezsp.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import bellows.multicast
import bellows.types
import voluptuous as vol

Expand Down Expand Up @@ -31,9 +32,10 @@
),
#
# The maximum number of multicast groups that the device may be a member of
vol.Optional(c.CONFIG_MULTICAST_TABLE_SIZE.name): vol.All(
int, vol.Range(min=0, max=255)
),
vol.Optional(
c.CONFIG_MULTICAST_TABLE_SIZE.name,
default=bellows.multicast.Multicast.TABLE_SIZE,
): vol.All(int, vol.Range(min=0, max=255)),
#
# The maximum number of destinations to which a node can route messages. This
# includes both messages originating at this node and those relayed for others
Expand Down
24 changes: 15 additions & 9 deletions bellows/multicast.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging

from bellows import types as t
import bellows.ezsp

LOGGER = logging.getLogger(__name__)

Expand All @@ -16,22 +15,29 @@ def __init__(self, ezsp):
self._multicast = {}
self._available = set()

@classmethod
async def initialize(cls, ezsp: bellows.ezsp.EZSP) -> "Multicast":
multicast = cls(ezsp)
for i in range(0, cls.TABLE_SIZE):
status, entry = await ezsp.getMulticastTableEntry(i)
async def _initialize(self) -> None:
self._multicast = {}
self._available = set()

status, size = await self._ezsp.getConfigurationValue(
t.EzspConfigId.CONFIG_MULTICAST_TABLE_SIZE
)
if status != t.EmberStatus.SUCCESS:
return

for i in range(0, size):
status, entry = await self._ezsp.getMulticastTableEntry(i)
if status != t.EmberStatus.SUCCESS:
LOGGER.error("Couldn't get MulticastTableEntry #%s: %s", i, status)
continue
LOGGER.debug("MulticastTableEntry[%s] = %s", i, entry)
if entry.endpoint != 0:
multicast._multicast[entry.multicastId] = (entry, i)
self._multicast[entry.multicastId] = (entry, i)
else:
multicast._available.add(i)
return multicast
self._available.add(i)

async def startup(self, coordinator) -> None:
await self._initialize()
for ep_id, ep in coordinator.endpoints.items():
if ep_id == 0:
continue
Expand Down
6 changes: 0 additions & 6 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,6 @@ async def initialize(self):
else:
await self._cfg(c.CONFIG_END_DEVICE_POLL_TIMEOUT, 60)
await self._cfg(c.CONFIG_END_DEVICE_POLL_TIMEOUT_SHIFT, 8)
await self._cfg(
c.CONFIG_MULTICAST_TABLE_SIZE,
ezsp_config.get(
c.CONFIG_MULTICAST_TABLE_SIZE.name, self.multicast.TABLE_SIZE
),
)
await self._cfg(
t.EzspConfigId.CONFIG_PACKET_BUFFER_COUNT,
ezsp_config[c.CONFIG_PACKET_BUFFER_COUNT.name],
Expand Down
7 changes: 3 additions & 4 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,11 @@ async def mockinit(*args, **kwargs):

loop = asyncio.get_event_loop()
p1 = mock.patch.object(bellows.ezsp, "EZSP", new=ezsp_mock)
p2 = mock.patch.object(
bellows.multicast.Multicast, "initialize", new=CoroutineMock()
)
p2 = mock.patch.object(bellows.multicast.Multicast, "startup")

with p1, p2:
with p1, p2 as multicast_mock:
loop.run_until_complete(app.startup(auto_form=auto_form))
assert multicast_mock.await_count == 1


def test_startup(app, ieee):
Expand Down
36 changes: 26 additions & 10 deletions tests/test_multicast.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
import pytest
from zigpy.endpoint import Endpoint

CUSTOM_SIZE = 12


@pytest.fixture
def ezsp_f():
return mock.MagicMock()
e = mock.MagicMock()
e.getConfigurationValue = CoroutineMock(return_value=[0, CUSTOM_SIZE])
return e


@pytest.fixture
Expand All @@ -18,7 +22,7 @@ def multicast(ezsp_f):


@pytest.mark.asyncio
async def test_initialize(ezsp_f):
async def test_initialize(multicast):
group_id = 0x0200
mct = active_multicasts = 4

Expand All @@ -35,14 +39,24 @@ async def mock_get(*args):
mct -= 1
return [t.EmberStatus.SUCCESS, entry]

ezsp_f.getMulticastTableEntry.side_effect = mock_get
multicast = await bellows.multicast.Multicast.initialize(ezsp_f)
assert ezsp_f.getMulticastTableEntry.call_count == multicast.TABLE_SIZE
assert len(multicast._available) == multicast.TABLE_SIZE - active_multicasts
multicast._ezsp.getMulticastTableEntry.side_effect = mock_get
await multicast._initialize()
assert multicast._ezsp.getMulticastTableEntry.call_count == CUSTOM_SIZE
assert len(multicast._available) == CUSTOM_SIZE - active_multicasts


@pytest.mark.asyncio
async def test_initialize_fail_configured_size(multicast):

multicast._ezsp.getConfigurationValue.return_value = t.EmberStatus.ERR_FATAL, 16
await multicast._initialize()
ezsp = multicast._ezsp
assert ezsp.getMulticastTableEntry.call_count == 0
assert len(multicast._available) == 0


@pytest.mark.asyncio
async def test_initialize_fail(multicast, ezsp_f):
async def test_initialize_fail(multicast):
group_id = 0x0200

async def mock_get(*args):
Expand All @@ -54,10 +68,10 @@ async def mock_get(*args):
group_id += 1
return [t.EmberStatus.ERR_FATAL, entry]

ezsp_f.getMulticastTableEntry.side_effect = mock_get
await multicast.initialize(ezsp_f)
multicast._ezsp.getMulticastTableEntry.side_effect = mock_get
await multicast._initialize()
ezsp = multicast._ezsp
assert ezsp.getMulticastTableEntry.call_count == multicast.TABLE_SIZE
assert ezsp.getMulticastTableEntry.call_count == CUSTOM_SIZE
assert len(multicast._available) == 0


Expand All @@ -68,10 +82,12 @@ async def test_startup(multicast):
ep1 = mock.MagicMock(spec_set=Endpoint)
ep1.member_of = [mock.sentinel.grp, mock.sentinel.grp, mock.sentinel.grp]
coordinator.endpoints = {0: mock.sentinel.ZDO, 1: ep1}
multicast._initialize = CoroutineMock()
multicast.subscribe = mock.MagicMock()
multicast.subscribe.side_effect = CoroutineMock()
await multicast.startup(coordinator)

assert multicast._initialize.await_count == 1
assert multicast.subscribe.call_count == len(ep1.member_of)
assert multicast.subscribe.call_args[0][0] == mock.sentinel.grp

Expand Down

0 comments on commit 7d12ea8

Please sign in to comment.