From c14f8b40f9f8a3f1d5c62ddae5a74355dc0d9e38 Mon Sep 17 00:00:00 2001 From: Hedda Date: Wed, 6 May 2020 16:04:32 +0200 Subject: [PATCH 1/3] Update README.md with more introduction descriptions about EmberZNet interfaces for new developers (#262) * Update README.md with more introduction descriptions for new developers Update README.md with more introduction descriptions for new developers. Much of this information been copied from zsmartsystems readme https://github.com/zsmartsystems/com.zsmartsystems.zigbee/blob/master/README.md which by the way contains even more information which I was not sure if it should also be copied as well or not, please see: ### Silicon Labs Ember EM35x / EFR32 The library supports the Silicon Labs EZSP (EmberZNet Serial Protocol) APIs for EM35x and EFR32 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. It is worth noting that EM3588 devices that have an embedded USB core will likely work with any baud rate, where dongles using external USB interface (eg CP2102 used with an EM3581) will likely require a specific baud rate. 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. #### Ember NCP configuration The library provide a standard set of configuration constants to configure the NCP for use as a coordinator. There are two methods available in the Ember driver to manipulate the configuration maps ```updateDefaultConfiguration``` and ```updateDefaultPolicy```. The configuration is sent to the NCP during the initialisation sequence which is performed when calling the ```ZigBeeNetworkManager.initialize()``` method, so any changes to configuration must be performed prior to this. The Ember dongle driver includes a public method ```getEmberNcp()``` which returns a ```EmberNcp``` class. This class provides high level methods for interacting directly with the NCP. #### Ember Reset By default the library uses the ASH Reset command to reset the NCP when the framework starts. Silabs have stated that this is not reliable due to possible communication problems between the NCP and the host. Where possible the user should implement a hardware reset to reset the NCP. Since this is application specific, the framework provides an interface for the application to implement this reset. The user should implement the ```EmberNcpResetProvider``` interface, and set this during NCP initialisation with the ```ZigBeeDongleEzsp.setEmberNcpResetProvider()``` method. #### Ember MfgLib use The library provides access to the ```mfglib``` functions in the NCP to facilitate device testing. To use this function, create the ```ZigBeeDongleEzsp``` and then call the ```getEmberMfglib(EmberMfglibListener)``` method to get the ```EmberMfglib``` class and also set the callback listener. The ```EmberMfglib``` class provides access to the ```mfglib``` functions. Note that this can only be used if the dongle is not configured for use in the network (ie ```initialize``` has not been called). The [com.zsmartsystems.zigbee.sniffer](https://github.com/zsmartsystems/com.zsmartsystems.zigbee.sniffer) project is an example of the use of these features to provide a network sniffer to route frames to [Wireshark](https://www.wireshark.org/). * Spelling correction in README.md Spelling correction in README.md --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3826430b..0b8e6b0e 100644 --- a/README.md +++ b/README.md @@ -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/) @@ -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 From 191cafe5ffad9bcb3dfe72dedeb78c25c2760b9c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 18 May 2020 21:58:55 -0400 Subject: [PATCH 2/3] Proper initialize EZSP multicast table (#264) * Proper initialize EZSP multicast table * Allow configurable multicast table size --- bellows/config/ezsp.py | 8 +++++--- bellows/multicast.py | 24 ++++++++++++++--------- bellows/zigbee/application.py | 6 ------ tests/test_application.py | 7 +++---- tests/test_multicast.py | 36 +++++++++++++++++++++++++---------- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/bellows/config/ezsp.py b/bellows/config/ezsp.py index 20c72550..8b04b5bf 100644 --- a/bellows/config/ezsp.py +++ b/bellows/config/ezsp.py @@ -1,3 +1,4 @@ +import bellows.multicast import bellows.types import voluptuous as vol @@ -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 diff --git a/bellows/multicast.py b/bellows/multicast.py index 9e3ebaae..647ab48c 100644 --- a/bellows/multicast.py +++ b/bellows/multicast.py @@ -1,7 +1,6 @@ import logging from bellows import types as t -import bellows.ezsp LOGGER = logging.getLogger(__name__) @@ -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 diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index 6114da8c..f4a14269 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -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], diff --git a/tests/test_application.py b/tests/test_application.py index b396d872..1031ce6d 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -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): diff --git a/tests/test_multicast.py b/tests/test_multicast.py index 45eb6bc1..31045288 100644 --- a/tests/test_multicast.py +++ b/tests/test_multicast.py @@ -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 @@ -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 @@ -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): @@ -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 @@ -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 From 7c13d52cd23352ac7605ca241a5a0592bb2f71fc Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 18 May 2020 21:59:40 -0400 Subject: [PATCH 3/3] 0.16.2 Version bump. --- bellows/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bellows/__init__.py b/bellows/__init__.py index 9a483f95..09819d31 100644 --- a/bellows/__init__.py +++ b/bellows/__init__.py @@ -1,5 +1,5 @@ MAJOR_VERSION = 0 -MINOR_VERSION = 17 -PATCH_VERSION = "0.dev0" +MINOR_VERSION = 16 +PATCH_VERSION = "2" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION)