diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..004002ae --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,22 @@ +name-template: '$NEXT_PATCH_VERSION Release.' +tag-template: '$NEXT_PATCH_VERSION' +categories: + - title: 'Breaking changes' + labels: + - 'breaking' + - title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '🧰 Maintenance' + label: 'chore' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +template: | + ## Changes + + $CHANGES diff --git a/.github/workflows/release-management.yml b/.github/workflows/release-management.yml new file mode 100644 index 00000000..83d8d984 --- /dev/null +++ b/.github/workflows/release-management.yml @@ -0,0 +1,16 @@ +name: Release Management + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + +jobs: + update_draft_release: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: toolmantim/release-drafter@v5.2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..2e200938 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: +- repo: https://github.com/psf/black + rev: 19.3b0 + hooks: + - id: black + args: + - --safe + - --quiet +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.8 + hooks: + - id: flake8 diff --git a/.travis.yml b/.travis.yml index b4b59914..592d8694 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,16 @@ language: python matrix: fast_finish: true include: - - python: "3.5" + - python: "3.6" env: TOXENV=lint - - python: "3.5" - env: TOXENV=py35 - python: "3.6" env: TOXENV=py36 - python: "3.7" env: TOXENV=py37 dist: xenial sudo: true + - python: "3.8" + env: TOXENV=py38 install: pip install -U setuptools tox coveralls script: tox after_success: coveralls diff --git a/README.md b/README.md index d197defa..5b206cdb 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ [![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 project to implement support for EmberZNet devices using the EZSP protocol. 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 @@ -13,7 +12,17 @@ Combo (HUSBZB-1)][HubZ] device to [Home Assistant][hass]. [Hubz]: http://www.gocontrol.com/detail.php?productId=6 [hass]: https://home-assistant.io/ -## Status +## Compatible hardware + +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/) + - [Elelabs Zigbee USB Adapter](https://elelabs.com/products/elelabs_usb_adapter.html) + - [Elelabs Zigbee Raspberry Pi Shield](https://elelabs.com/products/elelabs_zigbee_shield.html) + - 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) + +## Project status This project is in early stages, so it is likely that APIs will change. @@ -50,6 +59,11 @@ $ bellows zcl 00:0d:6f:00:05:7d:2d:34 1 1026 read_attribute 0 0=1806 ``` +## Release packages available via PyPI + +Packages of tagged versions are also released via PyPI + - https://pypi.org/project/bellows-homeassistant/ + ## Reference documentation * EZSP UART Gateway Protocol Reference: diff --git a/bellows/__init__.py b/bellows/__init__.py index 5131efbd..52ccaaaa 100644 --- a/bellows/__init__.py +++ b/bellows/__init__.py @@ -1,5 +1,5 @@ MAJOR_VERSION = 0 -MINOR_VERSION = 10 -PATCH_VERSION = '0' -__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) -__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) +MINOR_VERSION = 11 +PATCH_VERSION = "0" +__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) +__version__ = "{}.{}".format(__short_version__, PATCH_VERSION) diff --git a/bellows/cli/application.py b/bellows/cli/application.py index 03e05fbe..463967b5 100644 --- a/bellows/cli/application.py +++ b/bellows/cli/application.py @@ -22,10 +22,10 @@ @click.pass_context def form(ctx, database, channel, pan_id, extended_pan_id): """Form a new ZigBee network""" - ctx.obj['database_file'] = database + ctx.obj["database_file"] = database async def inner(ctx): - app = ctx.obj['app'] + app = ctx.obj["app"] await app.initialize() await app.form_network(channel, pan_id, extended_pan_id) @@ -38,13 +38,13 @@ async def inner(ctx): @click.pass_context def permit(ctx, database, duration_s): """Allow devices to join this ZigBee network""" - ctx.obj['database_file'] = database + ctx.obj["database_file"] = database async def inner(ctx): - app = ctx.obj['app'] + app = ctx.obj["app"] await app.permit(duration_s) - click.echo("Joins are permitted for the next %ss..." % (duration_s, )) + click.echo("Joins are permitted for the next %ss..." % (duration_s,)) await asyncio.sleep(duration_s + 1) click.echo("Done") @@ -52,22 +52,22 @@ async def inner(ctx): @main.command() -@click.argument('node', type=util.ZigbeeNodeParamType()) -@click.argument('code') +@click.argument("node", type=util.ZigbeeNodeParamType()) +@click.argument("code") @opts.database_file @opts.duration_s @click.pass_context def permit_with_key(ctx, database, duration_s, node, code): """Allow devices to join this ZigBee network using an install code""" - ctx.obj['database_file'] = database + ctx.obj["database_file"] = database code = binascii.unhexlify(code) async def inner(ctx): - app = ctx.obj['app'] + app = ctx.obj["app"] try: await app.permit_with_key(node, code, duration_s) - click.echo("Joins are permitted for the next %ss..." % (duration_s, )) + click.echo("Joins are permitted for the next %ss..." % (duration_s,)) await asyncio.sleep(duration_s + 1) click.echo("Done") except Exception as e: @@ -85,18 +85,16 @@ def devices(ctx, database): def print_clusters(title, clusters): clusters = sorted(list(clusters.items())) if clusters: - click.echo(" %s:" % (title, )) + click.echo(" %s:" % (title,)) for cluster_id, cluster in clusters: - click.echo(" %s (%s)" % ( - cluster.name, cluster_id - )) + click.echo(" %s (%s)" % (cluster.name, cluster_id)) ezsp = bellows.ezsp.EZSP() app = bellows.zigbee.application.ControllerApplication(ezsp, database) for ieee, dev in app.devices.items(): click.echo("Device:") - click.echo(" NWK: 0x%04x" % (dev.nwk, )) - click.echo(" IEEE: %s" % (ieee, )) + click.echo(" NWK: 0x%04x" % (dev.nwk,)) + click.echo(" IEEE: %s" % (ieee,)) click.echo(" Endpoints:") for epid, ep in dev.endpoints.items(): if epid == 0: @@ -105,11 +103,8 @@ def print_clusters(title, clusters): click.echo(" %s: Uninitialized") else: click.echo( - " %s: profile=0x%02x, device_type=%s" % ( - epid, - ep.profile_id, - ep.device_type, - ) + " %s: profile=0x%02x, device_type=%s" + % (epid, ep.profile_id, ep.device_type) ) print_clusters("Input Clusters", ep.in_clusters) print_clusters("Output Clusters", ep.out_clusters) @@ -118,11 +113,11 @@ def print_clusters(title, clusters): @main.group() @click.pass_context @opts.database_file -@click.argument('node', type=util.ZigbeeNodeParamType()) +@click.argument("node", type=util.ZigbeeNodeParamType()) def zdo(ctx, node, database): """Perform ZDO operations against a device""" - ctx.obj['node'] = node - ctx.obj['database_file'] = database + ctx.obj["node"] = node + ctx.obj["database_file"] = database @zdo.command() @@ -130,8 +125,8 @@ def zdo(ctx, node, database): @util.app async def endpoints(ctx): """List endpoints on a device""" - app = ctx.obj['app'] - node = ctx.obj['node'] + app = ctx.obj["app"] + node = ctx.obj["node"] dev = util.get_device(app, node) if dev is None: @@ -140,7 +135,7 @@ async def endpoints(ctx): try: v = await dev.zdo.request(0x0005, dev.nwk) if v[0] != t.EmberStatus.SUCCESS: - click.echo("Non-success response: %s" % (v, )) + click.echo("Non-success response: %s" % (v,)) else: click.echo(v[2]) except bellows.zigbee.exceptions.DeliveryError as e: @@ -150,11 +145,11 @@ async def endpoints(ctx): @zdo.command() @click.pass_context @util.app -@click.argument('endpoint', type=click.IntRange(1, 255)) +@click.argument("endpoint", type=click.IntRange(1, 255)) async def get_endpoint(ctx, endpoint): """Show an endpoint's simple descriptor""" - app = ctx.obj['app'] - node = ctx.obj['node'] + app = ctx.obj["app"] + node = ctx.obj["node"] dev, endp = util.get_endpoint(app, node, endpoint) if endp is None: @@ -163,7 +158,7 @@ async def get_endpoint(ctx, endpoint): try: v = await dev.zdo.request(0x0004, dev.nwk, endpoint) if v[0] != t.EmberStatus.SUCCESS: - click.echo("Non-success response: %s" % (v, )) + click.echo("Non-success response: %s" % (v,)) else: click.echo(v[2]) except bellows.zigbee.exceptions.DeliveryError as e: @@ -172,13 +167,13 @@ async def get_endpoint(ctx, endpoint): @zdo.command() @click.pass_context -@click.argument('endpoint', type=click.IntRange(1, 255)) -@click.argument('cluster', type=click.IntRange(0, 65535)) +@click.argument("endpoint", type=click.IntRange(1, 255)) +@click.argument("cluster", type=click.IntRange(0, 65535)) @util.app async def bind(ctx, endpoint, cluster): """Bind to a cluster on a node""" - app = ctx.obj['app'] - node = ctx.obj['node'] + app = ctx.obj["app"] + node = ctx.obj["node"] dev, endp, clust = util.get_in_cluster(app, node, endpoint, cluster) if clust is None: @@ -193,13 +188,13 @@ async def bind(ctx, endpoint, cluster): @zdo.command() @click.pass_context -@click.argument('endpoint', type=click.IntRange(1, 255)) -@click.argument('cluster', type=click.IntRange(0, 65535)) +@click.argument("endpoint", type=click.IntRange(1, 255)) +@click.argument("cluster", type=click.IntRange(0, 65535)) @util.app async def unbind(ctx, endpoint, cluster): """Unbind a cluster on a node""" - app = ctx.obj['app'] - node = ctx.obj['node'] + app = ctx.obj["app"] + node = ctx.obj["node"] dev, endp, clust = util.get_in_cluster(app, node, endpoint, cluster) if clust is None: @@ -217,10 +212,10 @@ async def unbind(ctx, endpoint, cluster): @util.app async def leave(ctx): """Tell a node to leave the network""" - app = ctx.obj['app'] + app = ctx.obj["app"] try: - v = await app.remove(ctx.obj['node']) + v = await app.remove(ctx.obj["node"]) click.echo(v) except bellows.zigbee.exceptions.DeliveryError as e: click.echo(e) @@ -229,40 +224,42 @@ async def leave(ctx): @main.group() @click.pass_context @opts.database_file -@click.argument('node', type=util.ZigbeeNodeParamType()) -@click.argument('endpoint', type=click.IntRange(1, 255)) -@click.argument('cluster', type=click.IntRange(0, 65535)) +@click.argument("node", type=util.ZigbeeNodeParamType()) +@click.argument("endpoint", type=click.IntRange(1, 255)) +@click.argument("cluster", type=click.IntRange(0, 65535)) def zcl(ctx, database, node, cluster, endpoint): """Peform ZCL operations against a device""" - ctx.obj['database_file'] = database - ctx.obj['node'] = node - ctx.obj['endpoint'] = endpoint - ctx.obj['cluster'] = cluster + ctx.obj["database_file"] = database + ctx.obj["node"] = node + ctx.obj["endpoint"] = endpoint + ctx.obj["cluster"] = cluster @zcl.command() @click.pass_context -@click.argument('attribute', type=click.IntRange(0, 65535)) +@click.argument("attribute", type=click.IntRange(0, 65535)) @opts.manufacturer @util.app async def read_attribute(ctx, attribute, manufacturer): - app = ctx.obj['app'] - node = ctx.obj['node'] - endpoint_id = ctx.obj['endpoint'] - cluster_id = ctx.obj['cluster'] + app = ctx.obj["app"] + node = ctx.obj["node"] + endpoint_id = ctx.obj["endpoint"] + cluster_id = ctx.obj["cluster"] dev, endpoint, cluster = util.get_in_cluster(app, node, endpoint_id, cluster_id) if cluster is None: return try: - v = await cluster.read_attributes([attribute], allow_cache=False, manufacturer=manufacturer) + v = await cluster.read_attributes( + [attribute], allow_cache=False, manufacturer=manufacturer + ) if not v: click.echo("Received empty response") elif attribute not in v[0]: - click.echo("Attribute %s not successful. Status=%s" % ( - attribute, v[1][attribute] - )) + click.echo( + "Attribute %s not successful. Status=%s" % (attribute, v[1][attribute]) + ) else: click.echo("%s=%s" % (attribute, v[0][attribute])) except bellows.zigbee.exceptions.DeliveryError as e: @@ -271,22 +268,24 @@ async def read_attribute(ctx, attribute, manufacturer): @zcl.command() @click.pass_context -@click.argument('attribute', type=click.IntRange(0, 65535)) -@click.argument('value', type=click.IntRange(0, 65535)) +@click.argument("attribute", type=click.IntRange(0, 65535)) +@click.argument("value", type=click.IntRange(0, 65535)) @opts.manufacturer @util.app async def write_attribute(ctx, attribute, value, manufacturer): - app = ctx.obj['app'] - node = ctx.obj['node'] - endpoint_id = ctx.obj['endpoint'] - cluster_id = ctx.obj['cluster'] + app = ctx.obj["app"] + node = ctx.obj["node"] + endpoint_id = ctx.obj["endpoint"] + cluster_id = ctx.obj["cluster"] dev, endpoint, cluster = util.get_in_cluster(app, node, endpoint_id, cluster_id) if cluster is None: return try: - v = await cluster.write_attributes({attribute: value}, manufacturer=manufacturer) + v = await cluster.write_attributes( + {attribute: value}, manufacturer=manufacturer + ) click.echo(v) except bellows.zigbee.exceptions.DeliveryError as e: click.echo(e) @@ -295,10 +294,10 @@ async def write_attribute(ctx, attribute, value, manufacturer): @zcl.command() @click.pass_context def commands(ctx): - database = ctx.obj['database_file'] - node = ctx.obj['node'] - endpoint_id = ctx.obj['endpoint'] - cluster_id = ctx.obj['cluster'] + database = ctx.obj["database_file"] + node = ctx.obj["node"] + endpoint_id = ctx.obj["endpoint"] + cluster_id = ctx.obj["cluster"] ezsp = bellows.ezsp.EZSP() app = bellows.zigbee.application.ControllerApplication(ezsp, database) @@ -313,15 +312,15 @@ def commands(ctx): @zcl.command() @click.pass_context -@click.argument('command') -@click.argument('parameters', nargs=-1) +@click.argument("command") +@click.argument("parameters", nargs=-1) @opts.manufacturer @util.app async def command(ctx, command, parameters, manufacturer): - app = ctx.obj['app'] - node = ctx.obj['node'] - endpoint_id = ctx.obj['endpoint'] - cluster_id = ctx.obj['cluster'] + app = ctx.obj["app"] + node = ctx.obj["node"] + endpoint_id = ctx.obj["endpoint"] + cluster_id = ctx.obj["cluster"] dev, endpoint, cluster = util.get_in_cluster(app, node, endpoint_id, cluster_id) if cluster is None: @@ -338,20 +337,18 @@ async def command(ctx, command, parameters, manufacturer): @zcl.command() @click.pass_context -@click.argument('attribute', type=click.IntRange(0, 65535)) -@click.argument('min_interval', type=click.IntRange(0, 65535)) -@click.argument('max_interval', type=click.IntRange(0, 65535)) -@click.argument('reportable_change', type=click.INT) +@click.argument("attribute", type=click.IntRange(0, 65535)) +@click.argument("min_interval", type=click.IntRange(0, 65535)) +@click.argument("max_interval", type=click.IntRange(0, 65535)) +@click.argument("reportable_change", type=click.INT) @util.app -async def configure_reporting(ctx, - attribute, - min_interval, - max_interval, - reportable_change): - app = ctx.obj['app'] - node = ctx.obj['node'] - endpoint_id = ctx.obj['endpoint'] - cluster_id = ctx.obj['cluster'] +async def configure_reporting( + ctx, attribute, min_interval, max_interval, reportable_change +): + app = ctx.obj["app"] + node = ctx.obj["node"] + endpoint_id = ctx.obj["endpoint"] + cluster_id = ctx.obj["cluster"] dev, endpoint, cluster = util.get_in_cluster(app, node, endpoint_id, cluster_id) if cluster is None: @@ -359,10 +356,7 @@ async def configure_reporting(ctx, try: v = await cluster.configure_reporting( - attribute, - min_interval, - max_interval, - reportable_change, + attribute, min_interval, max_interval, reportable_change ) click.echo(v) except bellows.zigbee.exceptions.DeliveryError as e: diff --git a/bellows/cli/dump.py b/bellows/cli/dump.py index d541a0fe..fe146dab 100644 --- a/bellows/cli/dump.py +++ b/bellows/cli/dump.py @@ -10,13 +10,11 @@ @main.command() @click.option( - '-c', '--channel', - type=click.IntRange(11, 26), - metavar="CHANNEL", - required=True, + "-c", "--channel", type=click.IntRange(11, 26), metavar="CHANNEL", required=True ) @click.option( - '-w', 'outfile', + "-w", + "outfile", type=click.Path(writable=True, dir_okay=False), metavar="FILE", required=True, @@ -28,20 +26,20 @@ def dump(ctx, channel, outfile): try: loop.run_until_complete(_dump(ctx, channel, outfile)) except KeyboardInterrupt: - captured = ctx.obj.get('captured', 0) - start_time = ctx.obj.get('start_time', None) + captured = ctx.obj.get("captured", 0) + start_time = ctx.obj.get("start_time", None) if start_time: duration = time.time() - start_time click.echo("\nCaptured %s frames in %0.2fs" % (captured, duration)) finally: - if 'ezsp' in ctx.obj: - loop.run_until_complete(ctx.obj['ezsp'].mfglibEnd()) - ctx.obj['ezsp'].close() + if "ezsp" in ctx.obj: + loop.run_until_complete(ctx.obj["ezsp"].mfglibEnd()) + ctx.obj["ezsp"].close() async def _dump(ctx, channel, outfile): - s = await util.setup(ctx.obj['device'], ctx.obj['baudrate']) - ctx.obj['ezsp'] = s + s = await util.setup(ctx.obj["device"], ctx.obj["baudrate"]) + ctx.obj["ezsp"] = s v = await s.mfglibStart(True) util.check(v[0], "Unable to start mfglib") @@ -52,18 +50,18 @@ async def _dump(ctx, channel, outfile): pcap = pure_pcapy.Dumper(outfile, 128, 195) # DLT_IEEE_15_4 click.echo("Capture started") - ctx.obj['start_time'] = time.time() - ctx.obj['captured'] = 0 + ctx.obj["start_time"] = time.time() + ctx.obj["captured"] = 0 def cb(frame_name, response): - if frame_name == 'mfglibRxHandler': + if frame_name == "mfglibRxHandler": data = response[2] ts = time.time() ts_sec = int(ts) ts_usec = int((ts - ts_sec) * 1000000) hdr = pure_pcapy.Pkthdr(ts_sec, ts_usec, len(data), len(data)) pcap.dump(hdr, data) - ctx.obj['captured'] += 1 + ctx.obj["captured"] += 1 s.add_callback(cb) diff --git a/bellows/cli/main.py b/bellows/cli/main.py index 2b37f509..69254f94 100644 --- a/bellows/cli/main.py +++ b/bellows/cli/main.py @@ -12,9 +12,9 @@ @opts.baudrate @click.pass_context def main(ctx, device, baudrate): - ctx.obj = {'device': device, 'baudrate': baudrate} + ctx.obj = {"device": device, "baudrate": baudrate} click_log.basic_config() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bellows/cli/ncp.py b/bellows/cli/ncp.py index e7f43c9e..3f05e51b 100644 --- a/bellows/cli/ncp.py +++ b/bellows/cli/ncp.py @@ -6,23 +6,20 @@ @main.command() -@click.argument('config', required=False) -@click.option('-a', '--all', 'all_', is_flag=True) +@click.argument("config", required=False) +@click.option("-a", "--all", "all_", is_flag=True) @click.pass_context @util.background async def config(ctx, config, all_): """Get/set configuration on the NCP""" - click.secho( - "NOTE: Configuration changes do not persist across resets", - fg='red' - ) + click.secho("NOTE: Configuration changes do not persist across resets", fg="red") if config and all_: raise click.BadOptionUsage("Specify a config or --all, not both") if not (config or all_): raise click.BadOptionUsage("One of config or --all must be specified") - s = await util.setup(ctx.obj['device'], ctx.obj['baudrate'], util.print_cb) + s = await util.setup(ctx.obj["device"], ctx.obj["baudrate"], util.print_cb) if all_: for config in t.EzspConfigId: @@ -33,30 +30,24 @@ async def config(ctx, config, all_): s.close() return - if '=' in config: + if "=" in config: config, value = config.split("=", 1) if config.isdigit(): try: config = t.EzspConfigId(int(config)) except ValueError: - raise click.BadArgumentUsage("Invalid config ID: %s" % ( - config, - )) + raise click.BadArgumentUsage("Invalid config ID: %s" % (config,)) else: try: config = t.EzspConfigId[config] except KeyError: - raise click.BadArgumentUsage("Invalid config name: %s" % ( - config, - )) + raise click.BadArgumentUsage("Invalid config name: %s" % (config,)) try: value = t.uint16_t(value) if not (0 <= value <= 65535): - raise ValueError("%s out of allowed range 0..65535" % ( - value, - )) + raise ValueError("%s out of allowed range 0..65535" % (value,)) except ValueError as e: - raise click.BadArgumentUsage("Invalid value: %s" % (e, )) + raise click.BadArgumentUsage("Invalid value: %s" % (e,)) v = await s.setConfigurationValue(config, value) click.echo(v) @@ -72,15 +63,15 @@ async def config(ctx, config, all_): @util.background async def info(ctx): """Get NCP information""" - s = await util.setup(ctx.obj['device'], ctx.obj['baudrate']) + s = await util.setup(ctx.obj["device"], ctx.obj["baudrate"]) await util.network_init(s) commands = [ - 'getEui64', - 'getNodeId', - 'networkState', - 'getNetworkParameters', - 'getCurrentSecurityState', + "getEui64", + "getNodeId", + "networkState", + "getNetworkParameters", + "getCurrentSecurityState", ] for c in commands: diff --git a/bellows/cli/network.py b/bellows/cli/network.py index f955c348..5e424ecb 100644 --- a/bellows/cli/network.py +++ b/bellows/cli/network.py @@ -23,11 +23,12 @@ @util.background async def join(ctx, channels, pan_id, extended_pan_id): """Join an existing ZigBee network as an end device""" + def cb(fut, frame_name, response): - if frame_name == 'stackStatusHandler': + if frame_name == "stackStatusHandler": fut.set_result(response) - s = await util.setup(ctx.obj['device'], ctx.obj['baudrate']) + s = await util.setup(ctx.obj["device"], ctx.obj["baudrate"]) channel = None @@ -42,9 +43,10 @@ def cb(fut, frame_name, response): if not (pan_id or extended_pan_id): scan_type = t.EzspNetworkScanType.ACTIVE_SCAN channel_mask = util.channel_mask(channels) - click.echo("PAN not provided, scanning channels %s..." % ( - ' '.join(map(str, channels)), - )) + click.echo( + "PAN not provided, scanning channels %s..." + % (" ".join(map(str, channels)),) + ) v = await s.startScan(scan_type, channel_mask, 3) networks = [n[0] for n in v if n[0].allowingJoin] @@ -61,9 +63,9 @@ def cb(fut, frame_name, response): extended_pan_id = network.extendedPanId channel = network.channel - click.echo("Found network %s %s on channel %s" % ( - pan_id, extended_pan_id, channel, - )) + click.echo( + "Found network %s %s on channel %s" % (pan_id, extended_pan_id, channel) + ) if pan_id is None: pan_id = t.uint16_t(0) @@ -79,12 +81,12 @@ def cb(fut, frame_name, response): if v[0] == t.EmberStatus.SUCCESS: LOGGER.debug("Network was up, leaving...") v = await s.leaveNetwork() - util.check(v[0], "Failure leaving network: %s" % (v[0], )) + util.check(v[0], "Failure leaving network: %s" % (v[0],)) await asyncio.sleep(1) # TODO initial_security_state = zutil.zha_security() v = await s.setInitialSecurityState(initial_security_state) - util.check(v[0], "Setting security state failed: %s" % (v[0], )) + util.check(v[0], "Setting security state failed: %s" % (v[0],)) parameters = t.EmberNetworkParameters() parameters.extendedPanId = extended_pan_id @@ -100,7 +102,7 @@ def cb(fut, frame_name, response): fut = asyncio.Future() cbid = s.add_callback(functools.partial(cb, fut)) v = await s.joinNetwork(t.EmberNodeType.END_DEVICE, parameters) - util.check(v[0], "Joining network failed: %s" % (v[0], )) + util.check(v[0], "Joining network failed: %s" % (v[0],)) v = await fut click.echo(v) @@ -114,13 +116,13 @@ def cb(fut, frame_name, response): @util.background async def leave(ctx): """Leave the ZigBee network""" - s = await util.setup(ctx.obj['device'], ctx.obj['baudrate']) + s = await util.setup(ctx.obj["device"], ctx.obj["baudrate"]) v = await util.network_init(s) if v[0] == t.EmberStatus.NOT_JOINED: click.echo("Not joined, not leaving") else: v = await s.leaveNetwork() - util.check(v[0], "Failure leaving network: %s" % (v[0], )) + util.check(v[0], "Failure leaving network: %s" % (v[0],)) s.close() @@ -128,25 +130,22 @@ async def leave(ctx): @main.command() @opts.channels @opts.duration_ms -@click.option('-e', '--energy', 'energy_scan', is_flag=True) +@click.option("-e", "--energy", "energy_scan", is_flag=True) @click.pass_context @util.background async def scan(ctx, channels, duration_ms, energy_scan): """Scan for networks or radio interference""" - s = await util.setup(ctx.obj['device'], ctx.obj['baudrate']) + s = await util.setup(ctx.obj["device"], ctx.obj["baudrate"]) channel_mask = util.channel_mask(channels) - click.echo("Scanning channels %s" % (' '.join(map(str, channels)), )) + click.echo("Scanning channels %s" % (" ".join(map(str, channels)),)) # TFM says: # Sets the exponent of the number of scan periods, where a scan period is # 960 symbols. The scan will occur for ((2^duration) + 1) scan periods. # 1 symbol is 16us duration_symbols = duration_ms / (960 * 0.016) - duration_symbol_exp = max( - 0, - math.ceil(math.log(duration_symbols - 1, 2)), - ) + duration_symbol_exp = max(0, math.ceil(math.log(duration_symbols - 1, 2))) scan_type = t.EzspNetworkScanType.ACTIVE_SCAN if energy_scan: diff --git a/bellows/cli/opts.py b/bellows/cli/opts.py index 9387ad42..3cf599d2 100644 --- a/bellows/cli/opts.py +++ b/bellows/cli/opts.py @@ -7,72 +7,62 @@ CHANNELS = list(range(11, 27)) channel = click.option( - '-c', '--channel', - type=click.IntRange(11, 26), - required=True, - default=15, + "-c", "--channel", type=click.IntRange(11, 26), required=True, default=15 ) channels = click.option( - '-C', '--channels', + "-C", + "--channels", type=util.CSVParamType(11, 26), - metavar='CHANNELS', - default=','.join(map(str, CHANNELS)), + metavar="CHANNELS", + default=",".join(map(str, CHANNELS)), show_default=True, ) device = click.option( - '-d', '--device', - type=click.STRING, - envvar='EZSP_DEVICE', - required=True, + "-d", "--device", type=click.STRING, envvar="EZSP_DEVICE", required=True ) baudrate = click.option( - '-b', '--baudrate', - type=click.INT, - envvar='EZSP_BAUDRATE', - default=57600 + "-b", "--baudrate", type=click.INT, envvar="EZSP_BAUDRATE", default=57600 ) database_file = click.option( - '-D', '--database', - type=click.Path( - exists=True, - dir_okay=False, - writable=True, - ), + "-D", + "--database", + type=click.Path(exists=True, dir_okay=False, writable=True), required=True, default=os.path.join(click.get_app_dir("bellows"), "app.db"), ) duration_ms = click.option( - '-t', '--duration', 'duration_ms', - type=click.INT, metavar='MILLISECONDS', + "-t", + "--duration", + "duration_ms", + type=click.INT, + metavar="MILLISECONDS", default=50, show_default=True, ) duration_s = click.option( - '-t', '--duration', 'duration_s', - type=click.INT, metavar='SECONDS', + "-t", + "--duration", + "duration_s", + type=click.INT, + metavar="SECONDS", default=30, show_default=True, ) -extended_pan = click.option( - '-E', '--extended-pan-id', - type=click.STRING, -) +extended_pan = click.option("-E", "--extended-pan-id", type=click.STRING) -pan = click.option( - '-P', '--pan-id', - type=click.IntRange(0, 65535), -) +pan = click.option("-P", "--pan-id", type=click.IntRange(0, 65535)) manufacturer = click.option( - '-m', '--manufacturer', + "-m", + "--manufacturer", type=click.IntRange(0, 0xFFFF), default=None, - help='send a manufacturer specific command' + help="send a manufacturer specific command", ) diff --git a/bellows/cli/util.py b/bellows/cli/util.py index 70cf90bb..7f4bf4ba 100644 --- a/bellows/cli/util.py +++ b/bellows/cli/util.py @@ -12,25 +12,23 @@ class CSVParamType(click.ParamType): - name = 'comma separated integers' + name = "comma separated integers" def __init__(self, min=None, max=None): self.intrange = click.IntRange(min, max) def convert(self, value, param, ctx): - values = [ - self.intrange.convert(v, param, ctx) for v in value.split(',') - ] + values = [self.intrange.convert(v, param, ctx) for v in value.split(",")] return values class ZigbeeNodeParamType(click.ParamType): - name = 'colon separated hex bytes' + name = "colon separated hex bytes" def convert(self, value, param, ctx): if ":" not in value or len(value) != 23: self.fail("Node format should be a 8 byte hex string seprated by ':'") - return t.EmberEUI64([t.uint8_t(p, base=16) for p in value.split(':')]) + return t.EmberEUI64([t.uint8_t(p, base=16) for p in value.split(":")]) def background(f): @@ -47,14 +45,11 @@ def app(f, app_startup=True): async def async_inner(ctx, *args, **kwargs): nonlocal database_file - database_file = ctx.obj['database_file'] + database_file = ctx.obj["database_file"] app = await setup_application( - ctx.obj['device'], - ctx.obj['baudrate'], - database_file, - startup=app_startup, + ctx.obj["device"], ctx.obj["baudrate"], database_file, startup=app_startup ) - ctx.obj['app'] = app + ctx.obj["app"] = app await f(ctx, *args, **kwargs) await asyncio.sleep(0.5) @@ -109,7 +104,8 @@ async def setup(dev, baudrate, cbh=None, configure=True): async def cfg(config_id, value): v = await s.setConfigurationValue(config_id, value) - check(v[0], 'Setting config %s to %s: %s' % (config_id, value, v[0])) + check(v[0], "Setting config %s to %s: %s" % (config_id, value, v[0])) + c = t.EzspConfigId if configure: @@ -117,7 +113,7 @@ async def cfg(config_id, value): await cfg(c.CONFIG_STACK_PROFILE, 2) await cfg(c.CONFIG_SECURITY_LEVEL, 5) await cfg(c.CONFIG_SUPPORTED_NETWORKS, 1) - await cfg(c.CONFIG_PACKET_BUFFER_COUNT, 0xff) + await cfg(c.CONFIG_PACKET_BUFFER_COUNT, 0xFF) return s @@ -143,7 +139,7 @@ async def network_init(s): v = await s.networkInit() check( v[0], - "Failure initializing network: %s" % (v[0], ), + "Failure initializing network: %s" % (v[0],), [0, t.EmberStatus.NOT_JOINED], ) return v @@ -158,17 +154,13 @@ def parse_epan(epan): async def basic_tc_permits(s): async def set_policy(policy, decision): v = await s.setPolicy(policy, decision) - check(v[0], "Failed to set policy %s to %s: %s" % ( - policy, decision, v[0], - )) + check(v[0], "Failed to set policy %s to %s: %s" % (policy, decision, v[0])) await set_policy( - t.EzspPolicyId.TC_KEY_REQUEST_POLICY, - t.EzspDecisionId.DENY_TC_KEY_REQUESTS, + t.EzspPolicyId.TC_KEY_REQUEST_POLICY, t.EzspDecisionId.DENY_TC_KEY_REQUESTS ) await set_policy( - t.EzspPolicyId.APP_KEY_REQUEST_POLICY, - t.EzspDecisionId.ALLOW_APP_KEY_REQUESTS, + t.EzspPolicyId.APP_KEY_REQUEST_POLICY, t.EzspDecisionId.ALLOW_APP_KEY_REQUESTS ) await set_policy( t.EzspPolicyId.TRUST_CENTER_POLICY, @@ -178,7 +170,7 @@ async def set_policy(policy, decision): def get_device(app, node): if node not in app.devices: - click.echo("Device %s is not in the device database" % (node, )) + click.echo("Device %s is not in the device database" % (node,)) return None return app.devices[node] @@ -199,10 +191,13 @@ def get_endpoint(app, node, endpoint_id): def get_in_cluster(app, node, endpoint_id, cluster_id): dev, endpoint = get_endpoint(app, node, endpoint_id) if endpoint is None: - return(dev, endpoint, None) + return (dev, endpoint, None) if cluster_id not in endpoint.in_clusters: - click.echo("Device %s has no cluster %d on endpoint %d" % (node, cluster_id, endpoint_id)) - return(dev, endpoint, None) + click.echo( + "Device %s has no cluster %d on endpoint %d" + % (node, cluster_id, endpoint_id) + ) + return (dev, endpoint, None) return (dev, endpoint, endpoint.in_clusters[cluster_id]) diff --git a/bellows/commands.py b/bellows/commands.py index e8b7a895..db468f95 100644 --- a/bellows/commands.py +++ b/bellows/commands.py @@ -2,238 +2,636 @@ COMMANDS = { # 4. Configuration frames - 'version': (0x00, (t.uint8_t, ), (t.uint8_t, t.uint8_t, t.uint16_t)), - 'getConfigurationValue': (0x52, (t.EzspConfigId, ), (t.EzspStatus, t.uint16_t)), - 'setConfigurationValue': (0x53, (t.EzspConfigId, t.uint16_t), (t.EzspStatus, )), - 'addEndpoint': (0x02, (t.uint8_t, t.uint16_t, t.uint16_t, t.uint8_t, - t.uint8_t, t.uint8_t, t.List(t.uint16_t,), - t.List(t.uint16_t)), (t.EzspStatus, )), - 'setPolicy': (0x55, (t.EzspPolicyId, t.EzspDecisionId), (t.EzspStatus, )), - 'getPolicy': (0x56, (t.EzspPolicyId, ), (t.EzspStatus, t.EzspDecisionId)), - 'getValue': (0xAA, (t.EzspValueId, ), (t.EzspStatus, t.LVBytes)), - 'getExtendedValue': (0x03, (t.EzspExtendedValueId, t.uint32_t), (t.EzspStatus, t.LVBytes)), - 'setValue': (0xAB, (t.EzspValueId, t.LVBytes), (t.EzspStatus, )), - 'setGpioCurrentConfiguration': (0xAC, (t.uint8_t, t.uint8_t, t.uint8_t), (t.EzspStatus, )), - 'setGpioPowerUpDownConfiguration': (0xAD, (t.uint8_t, t.uint8_t, t.uint8_t, t.uint8_t, t.uint8_t), (t.EzspStatus, )), - 'setGpioRadioPowerMask': (0xAE, (t.uint32_t, ), ()), - 'setCtune': (0xF5, (t.uint16_t, ), ()), - 'getCtune': (0xF6, (), (t.uint16_t, )), - 'setChannelMap': (0xF7, (t.uint8_t, t.uint8_t), ()), + "version": (0x00, (t.uint8_t,), (t.uint8_t, t.uint8_t, t.uint16_t)), + "getConfigurationValue": (0x52, (t.EzspConfigId,), (t.EzspStatus, t.uint16_t)), + "setConfigurationValue": (0x53, (t.EzspConfigId, t.uint16_t), (t.EzspStatus,)), + "addEndpoint": ( + 0x02, + ( + t.uint8_t, + t.uint16_t, + t.uint16_t, + t.uint8_t, + t.uint8_t, + t.uint8_t, + t.List(t.uint16_t), + t.List(t.uint16_t), + ), + (t.EzspStatus,), + ), + "setPolicy": (0x55, (t.EzspPolicyId, t.EzspDecisionId), (t.EzspStatus,)), + "getPolicy": (0x56, (t.EzspPolicyId,), (t.EzspStatus, t.EzspDecisionId)), + "getValue": (0xAA, (t.EzspValueId,), (t.EzspStatus, t.LVBytes)), + "getExtendedValue": ( + 0x03, + (t.EzspExtendedValueId, t.uint32_t), + (t.EzspStatus, t.LVBytes), + ), + "setValue": (0xAB, (t.EzspValueId, t.LVBytes), (t.EzspStatus,)), + "setGpioCurrentConfiguration": ( + 0xAC, + (t.uint8_t, t.uint8_t, t.uint8_t), + (t.EzspStatus,), + ), + "setGpioPowerUpDownConfiguration": ( + 0xAD, + (t.uint8_t, t.uint8_t, t.uint8_t, t.uint8_t, t.uint8_t), + (t.EzspStatus,), + ), + "setGpioRadioPowerMask": (0xAE, (t.uint32_t,), ()), + "setCtune": (0xF5, (t.uint16_t,), ()), + "getCtune": (0xF6, (), (t.uint16_t,)), + "setChannelMap": (0xF7, (t.uint8_t, t.uint8_t), ()), # 5. Utilities Frames - 'nop': (0x05, (), ()), - 'echo': (0x81, (t.LVBytes, ), (t.LVBytes, )), - 'invalidCommand': (0x58, (), (t.EzspStatus, )), - 'callback': (0x06, (), ()), - 'noCallbacks': (0x07, (), ()), - 'setToken': (0x09, (t.uint8_t, t.fixed_list(8, t.uint8_t)), (t.EmberStatus, )), - 'getToken': (0x0A, (t.uint8_t, ), (t.EmberStatus, t.fixed_list(8, t.uint8_t))), - 'getMfgToken': (0x0B, (t.EzspMfgTokenId, ), (t.LVBytes, )), - 'setMfgToken': (0x0C, (t.EzspMfgTokenId, t.LVBytes), (t.EmberStatus, )), - 'stackTokenChangedHandler': (0x0D, (), (t.uint16_t, )), - 'getRandomNumber': (0x49, (), (t.EmberStatus, t.uint16_t)), - 'setTimer': (0x0E, (t.uint8_t, t.uint16_t, t.EmberEventUnits, t.Bool), (t.EmberStatus, )), - 'getTimer': (0x4E, (t.uint8_t, ), (t.uint16_t, t.EmberEventUnits, t.Bool)), - 'timerHandler': (0x0F, (), (t.uint8_t, )), - 'debugWrite': (0x12, (t.Bool, t.LVBytes), (t.EmberStatus, )), - 'readAndClearCounters': (0x65, (), (t.fixed_list(t.EmberCounterType.COUNTER_TYPE_COUNT, t.uint16_t), )), - 'readCounters': (0xF1, (), (t.fixed_list(t.EmberCounterType.COUNTER_TYPE_COUNT, t.uint16_t), )), - 'counterRolloverHandler': (0xF2, (), (t.EmberCounterType, )), - 'delayTest': (0x9D, (t.uint16_t, ), ()), - 'getLibraryStatus': (0x01, (t.uint8_t, ), (t.EmberLibraryStatus, )), - 'getXncpInfo': (0x13, (), (t.EmberStatus, t.uint16_t, t.uint16_t)), - 'customFrame': (0x47, (t.LVBytes, ), (t.EmberStatus, t.LVBytes)), - 'customFrameHandler': (0x54, (), (t.LVBytes, )), - 'getEui64': (0x26, (), (t.EmberEUI64, )), - 'getNodeId': (0x27, (), (t.EmberNodeId, )), - 'networkInit': (0x17, (), (t.EmberStatus, )), + "nop": (0x05, (), ()), + "echo": (0x81, (t.LVBytes,), (t.LVBytes,)), + "invalidCommand": (0x58, (), (t.EzspStatus,)), + "callback": (0x06, (), ()), + "noCallbacks": (0x07, (), ()), + "setToken": (0x09, (t.uint8_t, t.fixed_list(8, t.uint8_t)), (t.EmberStatus,)), + "getToken": (0x0A, (t.uint8_t,), (t.EmberStatus, t.fixed_list(8, t.uint8_t))), + "getMfgToken": (0x0B, (t.EzspMfgTokenId,), (t.LVBytes,)), + "setMfgToken": (0x0C, (t.EzspMfgTokenId, t.LVBytes), (t.EmberStatus,)), + "stackTokenChangedHandler": (0x0D, (), (t.uint16_t,)), + "getRandomNumber": (0x49, (), (t.EmberStatus, t.uint16_t)), + "setTimer": ( + 0x0E, + (t.uint8_t, t.uint16_t, t.EmberEventUnits, t.Bool), + (t.EmberStatus,), + ), + "getTimer": (0x4E, (t.uint8_t,), (t.uint16_t, t.EmberEventUnits, t.Bool)), + "timerHandler": (0x0F, (), (t.uint8_t,)), + "debugWrite": (0x12, (t.Bool, t.LVBytes), (t.EmberStatus,)), + "readAndClearCounters": ( + 0x65, + (), + (t.fixed_list(t.EmberCounterType.COUNTER_TYPE_COUNT, t.uint16_t),), + ), + "readCounters": ( + 0xF1, + (), + (t.fixed_list(t.EmberCounterType.COUNTER_TYPE_COUNT, t.uint16_t),), + ), + "counterRolloverHandler": (0xF2, (), (t.EmberCounterType,)), + "delayTest": (0x9D, (t.uint16_t,), ()), + "getLibraryStatus": (0x01, (t.uint8_t,), (t.EmberLibraryStatus,)), + "getXncpInfo": (0x13, (), (t.EmberStatus, t.uint16_t, t.uint16_t)), + "customFrame": (0x47, (t.LVBytes,), (t.EmberStatus, t.LVBytes)), + "customFrameHandler": (0x54, (), (t.LVBytes,)), + "getEui64": (0x26, (), (t.EmberEUI64,)), + "getNodeId": (0x27, (), (t.EmberNodeId,)), + "networkInit": (0x17, (), (t.EmberStatus,)), # 6. Networking Frames - 'setManufacturerCode': (0x15, (t.uint16_t, ), ()), - 'setPowerDescriptor': (0x16, (t.uint16_t, ), ()), - 'networkInitExtended': (0x70, (t.EmberNetworkInitStruct, ), (t.EmberStatus, )), - 'networkState': (0x18, (), (t.EmberNetworkStatus, )), - 'stackStatusHandler': (0x19, (), (t.EmberStatus, )), - 'startScan': (0x1A, (t.EzspNetworkScanType, t.uint32_t, t.uint8_t), (t.EmberStatus, )), - 'energyScanResultHandler': (0x48, (), (t.uint8_t, t.int8s)), - 'networkFoundHandler': (0x1B, (), (t.EmberZigbeeNetwork, t.uint8_t, t.int8s)), - 'scanCompleteHandler': (0x1C, (), (t.uint8_t, t.EmberStatus)), - 'stopScan': (0x1D, (), (t.EmberStatus, )), - 'formNetwork': (0x1E, (t.EmberNetworkParameters, ), (t.EmberStatus, )), - 'joinNetwork': (0x1F, (t.EmberNodeType, t.EmberNetworkParameters), (t.EmberStatus, )), - 'leaveNetwork': (0x20, (), (t.EmberStatus, )), - 'findAndRejoinNetwork': (0x21, (t.Bool, t.uint32_t), (t.EmberStatus, )), - 'permitJoining': (0x22, (t.uint8_t, ), (t.EmberStatus, )), - 'childJoinHandler': (0x23, (), (t.uint8_t, t.Bool, t.EmberNodeId, t.EmberEUI64, t.EmberNodeType)), - 'energyScanRequest': (0x9C, (t.EmberNodeId, t.uint32_t, t.uint8_t, t.uint16_t), (t.EmberStatus, )), - 'getNetworkParameters': (0x28, (), (t.EmberStatus, t.EmberNodeType, t.EmberNetworkParameters)), - 'getParentChildParameters': (0x29, (), (t.uint8_t, t.EmberEUI64, t.EmberNodeId)), - 'getChildData': (0x4A, (t.uint8_t, ), (t.EmberStatus, t.EmberNodeId, t.EmberEUI64, t.EmberNodeType)), - 'getNeighbor': (0x79, (t.uint8_t, ), (t.EmberStatus, t.EmberNeighborTableEntry)), - 'neighborCount': (0x7A, (), (t.uint8_t, )), - 'getRouteTableEntry': (0x7B, (t.uint8_t, ), (t.EmberStatus, t.EmberRouteTableEntry)), - 'setRadioPower': (0x99, (t.int8s, ), (t.EmberStatus, )), - 'setRadioChannel': (0x9A, (t.uint8_t, ), (t.EmberStatus, )), - 'setConcentrator': (0x10, (t.Bool, t.uint16_t, t.uint16_t, t.uint16_t, t.uint8_t, t.uint8_t, t.uint8_t), (t.EmberStatus, )), + "setManufacturerCode": (0x15, (t.uint16_t,), ()), + "setPowerDescriptor": (0x16, (t.uint16_t,), ()), + "networkInitExtended": (0x70, (t.EmberNetworkInitStruct,), (t.EmberStatus,)), + "networkState": (0x18, (), (t.EmberNetworkStatus,)), + "stackStatusHandler": (0x19, (), (t.EmberStatus,)), + "startScan": ( + 0x1A, + (t.EzspNetworkScanType, t.uint32_t, t.uint8_t), + (t.EmberStatus,), + ), + "energyScanResultHandler": (0x48, (), (t.uint8_t, t.int8s)), + "networkFoundHandler": (0x1B, (), (t.EmberZigbeeNetwork, t.uint8_t, t.int8s)), + "scanCompleteHandler": (0x1C, (), (t.uint8_t, t.EmberStatus)), + "stopScan": (0x1D, (), (t.EmberStatus,)), + "formNetwork": (0x1E, (t.EmberNetworkParameters,), (t.EmberStatus,)), + "joinNetwork": ( + 0x1F, + (t.EmberNodeType, t.EmberNetworkParameters), + (t.EmberStatus,), + ), + "leaveNetwork": (0x20, (), (t.EmberStatus,)), + "findAndRejoinNetwork": (0x21, (t.Bool, t.uint32_t), (t.EmberStatus,)), + "permitJoining": (0x22, (t.uint8_t,), (t.EmberStatus,)), + "childJoinHandler": ( + 0x23, + (), + (t.uint8_t, t.Bool, t.EmberNodeId, t.EmberEUI64, t.EmberNodeType), + ), + "energyScanRequest": ( + 0x9C, + (t.EmberNodeId, t.uint32_t, t.uint8_t, t.uint16_t), + (t.EmberStatus,), + ), + "getNetworkParameters": ( + 0x28, + (), + (t.EmberStatus, t.EmberNodeType, t.EmberNetworkParameters), + ), + "getParentChildParameters": (0x29, (), (t.uint8_t, t.EmberEUI64, t.EmberNodeId)), + "getChildData": ( + 0x4A, + (t.uint8_t,), + (t.EmberStatus, t.EmberNodeId, t.EmberEUI64, t.EmberNodeType), + ), + "getNeighbor": (0x79, (t.uint8_t,), (t.EmberStatus, t.EmberNeighborTableEntry)), + "neighborCount": (0x7A, (), (t.uint8_t,)), + "getRouteTableEntry": (0x7B, (t.uint8_t,), (t.EmberStatus, t.EmberRouteTableEntry)), + "setRadioPower": (0x99, (t.int8s,), (t.EmberStatus,)), + "setRadioChannel": (0x9A, (t.uint8_t,), (t.EmberStatus,)), + "setConcentrator": ( + 0x10, + (t.Bool, t.uint16_t, t.uint16_t, t.uint16_t, t.uint8_t, t.uint8_t, t.uint8_t), + (t.EmberStatus,), + ), # 7. Binding Frames - 'clearBindingTable': (0x2A, (), (t.EmberStatus, )), - 'setBinding': (0x2B, (t.uint8_t, t.EmberBindingTableEntry), (t.EmberStatus, )), - 'getBinding': (0x2C, (t.uint8_t, ), (t.EmberStatus, t.EmberBindingTableEntry)), - 'deleteBinding': (0x2D, (t.uint8_t, ), (t.EmberStatus, )), - 'bindingIsActive': (0x2E, (t.uint8_t, ), (t.Bool, )), - 'getBindingRemoteNodeId': (0x2F, (t.uint8_t, ), (t.EmberNodeId, )), - 'setBindingRemoteNodeId': (0x30, (t.uint8_t, ), ()), - 'remoteSetBindingHandler': (0x31, (), (t.EmberBindingTableEntry, )), - 'remoteDeleteBindingHandler': (0x32, (), (t.uint8_t, t.EmberStatus)), + "clearBindingTable": (0x2A, (), (t.EmberStatus,)), + "setBinding": (0x2B, (t.uint8_t, t.EmberBindingTableEntry), (t.EmberStatus,)), + "getBinding": (0x2C, (t.uint8_t,), (t.EmberStatus, t.EmberBindingTableEntry)), + "deleteBinding": (0x2D, (t.uint8_t,), (t.EmberStatus,)), + "bindingIsActive": (0x2E, (t.uint8_t,), (t.Bool,)), + "getBindingRemoteNodeId": (0x2F, (t.uint8_t,), (t.EmberNodeId,)), + "setBindingRemoteNodeId": (0x30, (t.uint8_t,), ()), + "remoteSetBindingHandler": (0x31, (), (t.EmberBindingTableEntry,)), + "remoteDeleteBindingHandler": (0x32, (), (t.uint8_t, t.EmberStatus)), # 8. Messaging Frames - 'maximumPayloadLength': (0x33, (), (t.uint8_t, )), - 'sendUnicast': (0x34, (t.EmberOutgoingMessageType, t.EmberNodeId, t.EmberApsFrame, t.uint8_t, t.LVBytes), (t.EmberStatus, t.uint8_t)), - 'sendBroadcast': (0x36, (t.EmberNodeId, t.EmberApsFrame, t.uint8_t, t.uint8_t, t.LVBytes), (t.EmberStatus, t.uint8_t)), - 'proxyBroadcast': (0x37, (t.EmberNodeId, t.EmberNodeId, t.uint8_t, t.EmberApsFrame, t.uint8_t, t.uint8_t, t.LVBytes), (t.EmberStatus, t.uint8_t)), - 'sendMulticast': (0x38, (t.EmberApsFrame, t.uint8_t, t.uint8_t, t.uint8_t, t.LVBytes), (t.EmberStatus, t.uint8_t)), - 'sendReply': (0x39, (t.EmberNodeId, t.EmberApsFrame, t.LVBytes), (t.EmberStatus, )), - 'messageSentHandler': (0x3F, (), (t.EmberOutgoingMessageType, t.uint16_t, t.EmberApsFrame, t.uint8_t, t.EmberStatus, t.LVBytes)), - 'sendManyToOneRouteRequest': (0x41, (t.uint16_t, t.uint8_t), (t.EmberStatus, )), - 'pollForData': (0x42, (t.uint16_t, t.EmberEventUnits, t.uint8_t), (t.EmberStatus, )), - 'pollCompleteHandler': (0x43, (), (t.EmberStatus, )), - 'pollHandler': (0x44, (), (t.EmberNodeId, )), - 'incomingSenderEui64Handler': (0x62, (), (t.EmberEUI64, )), - 'incomingMessageHandler': (0x45, (), (t.EmberIncomingMessageType, t.EmberApsFrame, t.uint8_t, t.int8s, t.EmberNodeId, t.uint8_t, t.uint8_t, t.LVBytes)), - 'incomingRouteRecordHandler': (0x59, (), (t.EmberNodeId, t.EmberEUI64, t.uint8_t, t.int8s, t.LVList(t.EmberNodeId))), - 'changeSourceRouteHandler': (0xC4, (), (t.EmberNodeId, t.EmberNodeId, t.Bool)), - 'setSourceRoute': (0x5A, (t.EmberNodeId, t.LVList(t.EmberNodeId)), (t.EmberStatus, )), - 'incomingManyToOneRouteRequestHandler': (0x7D, (), (t.EmberNodeId, t.EmberEUI64, t.uint8_t)), - 'incomingRouteErrorHandler': (0x80, (), (t.EmberStatus, t.EmberNodeId)), - 'addressTableEntryIsActive': (0x5B, (t.uint8_t, ), (t.Bool, )), - 'setAddressTableRemoteEui64': (0x5C, (t.uint8_t, t.EmberEUI64), (t.EmberStatus, )), - 'setAddressTableRemoteNodeId': (0x5D, (t.uint8_t, t.EmberNodeId), ()), - 'getAddressTableRemoteEui64': (0x5E, (t.uint8_t, ), (t.EmberEUI64, )), - 'getAddressTableRemoteNodeId': (0x5F, (t.uint8_t, ), (t.EmberNodeId, )), - 'setExtendedTimeout': (0x7E, (t.EmberEUI64, t.Bool), ()), - 'getExtendedTimeout': (0x7F, (t.EmberEUI64, ), (t.Bool, )), - 'replaceAddressTableEntry': (0x82, (t.uint8_t, t.EmberEUI64, t.EmberNodeId, t.Bool, ), (t.EmberStatus, t.EmberEUI64, t.EmberNodeId, t.Bool, )), - 'lookupNodeIdByEui64': (0x60, (t.EmberEUI64, ), (t.EmberNodeId, )), - 'lookupEui64ByNodeId': (0x61, (t.EmberNodeId, ), (t.EmberStatus, t.EmberEUI64)), - 'getMulticastTableEntry': (0x63, (t.uint8_t, ), (t.EmberStatus, t.EmberMulticastTableEntry)), - 'setMulticastTableEntry': (0x64, (t.uint8_t, t.EmberMulticastTableEntry), (t.EmberStatus, )), - 'idConflictHandler': (0x7C, (), (t.EmberNodeId, )), - 'sendRawMessage': (0x96, (t.LVBytes, ), (t.EmberStatus, )), - 'macPassthroughMessageHandler': (0x97, (), (t.EmberMacPassthroughType, t.uint8_t, t.int8s, t.LVBytes)), - 'macFilterMatchMessageHandler': (0x46, (), (t.uint8_t, t.EmberMacPassthroughType, t.uint8_t, t.int8s, t.LVBytes)), - 'rawTransmitCompleteHandler': (0x98, (), (t.EmberStatus, )), + "maximumPayloadLength": (0x33, (), (t.uint8_t,)), + "sendUnicast": ( + 0x34, + ( + t.EmberOutgoingMessageType, + t.EmberNodeId, + t.EmberApsFrame, + t.uint8_t, + t.LVBytes, + ), + (t.EmberStatus, t.uint8_t), + ), + "sendBroadcast": ( + 0x36, + (t.EmberNodeId, t.EmberApsFrame, t.uint8_t, t.uint8_t, t.LVBytes), + (t.EmberStatus, t.uint8_t), + ), + "proxyBroadcast": ( + 0x37, + ( + t.EmberNodeId, + t.EmberNodeId, + t.uint8_t, + t.EmberApsFrame, + t.uint8_t, + t.uint8_t, + t.LVBytes, + ), + (t.EmberStatus, t.uint8_t), + ), + "sendMulticast": ( + 0x38, + (t.EmberApsFrame, t.uint8_t, t.uint8_t, t.uint8_t, t.LVBytes), + (t.EmberStatus, t.uint8_t), + ), + "sendReply": (0x39, (t.EmberNodeId, t.EmberApsFrame, t.LVBytes), (t.EmberStatus,)), + "messageSentHandler": ( + 0x3F, + (), + ( + t.EmberOutgoingMessageType, + t.uint16_t, + t.EmberApsFrame, + t.uint8_t, + t.EmberStatus, + t.LVBytes, + ), + ), + "sendManyToOneRouteRequest": (0x41, (t.uint16_t, t.uint8_t), (t.EmberStatus,)), + "pollForData": (0x42, (t.uint16_t, t.EmberEventUnits, t.uint8_t), (t.EmberStatus,)), + "pollCompleteHandler": (0x43, (), (t.EmberStatus,)), + "pollHandler": (0x44, (), (t.EmberNodeId,)), + "incomingSenderEui64Handler": (0x62, (), (t.EmberEUI64,)), + "incomingMessageHandler": ( + 0x45, + (), + ( + t.EmberIncomingMessageType, + t.EmberApsFrame, + t.uint8_t, + t.int8s, + t.EmberNodeId, + t.uint8_t, + t.uint8_t, + t.LVBytes, + ), + ), + "incomingRouteRecordHandler": ( + 0x59, + (), + (t.EmberNodeId, t.EmberEUI64, t.uint8_t, t.int8s, t.LVList(t.EmberNodeId)), + ), + "changeSourceRouteHandler": (0xC4, (), (t.EmberNodeId, t.EmberNodeId, t.Bool)), + "setSourceRoute": ( + 0x5A, + (t.EmberNodeId, t.LVList(t.EmberNodeId)), + (t.EmberStatus,), + ), + "incomingManyToOneRouteRequestHandler": ( + 0x7D, + (), + (t.EmberNodeId, t.EmberEUI64, t.uint8_t), + ), + "incomingRouteErrorHandler": (0x80, (), (t.EmberStatus, t.EmberNodeId)), + "addressTableEntryIsActive": (0x5B, (t.uint8_t,), (t.Bool,)), + "setAddressTableRemoteEui64": (0x5C, (t.uint8_t, t.EmberEUI64), (t.EmberStatus,)), + "setAddressTableRemoteNodeId": (0x5D, (t.uint8_t, t.EmberNodeId), ()), + "getAddressTableRemoteEui64": (0x5E, (t.uint8_t,), (t.EmberEUI64,)), + "getAddressTableRemoteNodeId": (0x5F, (t.uint8_t,), (t.EmberNodeId,)), + "setExtendedTimeout": (0x7E, (t.EmberEUI64, t.Bool), ()), + "getExtendedTimeout": (0x7F, (t.EmberEUI64,), (t.Bool,)), + "replaceAddressTableEntry": ( + 0x82, + (t.uint8_t, t.EmberEUI64, t.EmberNodeId, t.Bool), + (t.EmberStatus, t.EmberEUI64, t.EmberNodeId, t.Bool), + ), + "lookupNodeIdByEui64": (0x60, (t.EmberEUI64,), (t.EmberNodeId,)), + "lookupEui64ByNodeId": (0x61, (t.EmberNodeId,), (t.EmberStatus, t.EmberEUI64)), + "getMulticastTableEntry": ( + 0x63, + (t.uint8_t,), + (t.EmberStatus, t.EmberMulticastTableEntry), + ), + "setMulticastTableEntry": ( + 0x64, + (t.uint8_t, t.EmberMulticastTableEntry), + (t.EmberStatus,), + ), + "idConflictHandler": (0x7C, (), (t.EmberNodeId,)), + "sendRawMessage": (0x96, (t.LVBytes,), (t.EmberStatus,)), + "macPassthroughMessageHandler": ( + 0x97, + (), + (t.EmberMacPassthroughType, t.uint8_t, t.int8s, t.LVBytes), + ), + "macFilterMatchMessageHandler": ( + 0x46, + (), + (t.uint8_t, t.EmberMacPassthroughType, t.uint8_t, t.int8s, t.LVBytes), + ), + "rawTransmitCompleteHandler": (0x98, (), (t.EmberStatus,)), # 9. Security Frames - 'setInitialSecurityState': (0x68, (t.EmberInitialSecurityState, ), (t.EmberStatus, )), - 'getCurrentSecurityState': (0x69, (), (t.EmberStatus, t.EmberCurrentSecurityState)), - 'getKey': (0x6A, (t.EmberKeyType, ), (t.EmberStatus, t.EmberKeyStruct)), - 'switchNetworkKeyHandler': (0x6E, (), (t.uint8_t, )), - 'getKeyTableEntry': (0x71, (t.uint8_t, ), (t.EmberStatus, t.EmberKeyStruct)), - 'setKeyTableEntry': (0x72, (t.uint8_t, t.EmberEUI64, t.Bool, t.EmberKeyData), (t.EmberStatus, )), - 'findKeyTableEntry': (0x75, (t.EmberEUI64, t.Bool), (t.uint8_t, )), - 'addOrUpdateKeyTableEntry': (0x66, (t.EmberEUI64, t.Bool, t.EmberKeyData), (t.EmberStatus, )), - 'eraseKeyTableEntry': (0x76, (t.uint8_t, ), (t.EmberStatus, )), - 'clearKeyTable': (0xB1, (), (t.EmberStatus, )), - 'requestLinkKey': (0x14, (t.EmberEUI64, ), (t.EmberStatus, )), - 'zigbeeKeyEstablishmentHandler': (0x9B, (), (t.EmberEUI64, t.EmberKeyStatus)), - 'addTransientLinkKey': (0xAF, (t.EmberEUI64, t.EmberKeyData), (t.EmberStatus, )), - 'clearTransientLinkKeys': (0x6B, (), ()), - 'getTransientLinkKey': (0xCE, (t.EmberEUI64, ), (t.EmberStatus, t.EmberTransientKeyData)), - 'setSecurityKey': (0xCA, (t.EmberKeyData, t.SecureEzspSecurityType), (t.EzspStatus, )), - 'setSecurityParameters': (0xCB, (t.SecureEzspSecurityLevel, t.SecureEzspRandomNumber), (t.EzspStatus, t.SecureEzspRandomNumber)), - 'resetToFactoryDefaults': (0xCC, (), (t.EzspStatus, )), - 'getSecurityKeyStatus': (0xCD, (), (t.EzspStatus, t.SecureEzspSecurityType)), + "setInitialSecurityState": (0x68, (t.EmberInitialSecurityState,), (t.EmberStatus,)), + "getCurrentSecurityState": (0x69, (), (t.EmberStatus, t.EmberCurrentSecurityState)), + "getKey": (0x6A, (t.EmberKeyType,), (t.EmberStatus, t.EmberKeyStruct)), + "switchNetworkKeyHandler": (0x6E, (), (t.uint8_t,)), + "getKeyTableEntry": (0x71, (t.uint8_t,), (t.EmberStatus, t.EmberKeyStruct)), + "setKeyTableEntry": ( + 0x72, + (t.uint8_t, t.EmberEUI64, t.Bool, t.EmberKeyData), + (t.EmberStatus,), + ), + "findKeyTableEntry": (0x75, (t.EmberEUI64, t.Bool), (t.uint8_t,)), + "addOrUpdateKeyTableEntry": ( + 0x66, + (t.EmberEUI64, t.Bool, t.EmberKeyData), + (t.EmberStatus,), + ), + "eraseKeyTableEntry": (0x76, (t.uint8_t,), (t.EmberStatus,)), + "clearKeyTable": (0xB1, (), (t.EmberStatus,)), + "requestLinkKey": (0x14, (t.EmberEUI64,), (t.EmberStatus,)), + "zigbeeKeyEstablishmentHandler": (0x9B, (), (t.EmberEUI64, t.EmberKeyStatus)), + "addTransientLinkKey": (0xAF, (t.EmberEUI64, t.EmberKeyData), (t.EmberStatus,)), + "clearTransientLinkKeys": (0x6B, (), ()), + "getTransientLinkKey": ( + 0xCE, + (t.EmberEUI64,), + (t.EmberStatus, t.EmberTransientKeyData), + ), + "setSecurityKey": ( + 0xCA, + (t.EmberKeyData, t.SecureEzspSecurityType), + (t.EzspStatus,), + ), + "setSecurityParameters": ( + 0xCB, + (t.SecureEzspSecurityLevel, t.SecureEzspRandomNumber), + (t.EzspStatus, t.SecureEzspRandomNumber), + ), + "resetToFactoryDefaults": (0xCC, (), (t.EzspStatus,)), + "getSecurityKeyStatus": (0xCD, (), (t.EzspStatus, t.SecureEzspSecurityType)), # 10. Trust Center Frames - 'trustCenterJoinHandler': (0x24, (), (t.EmberNodeId, t.EmberEUI64, t.EmberDeviceUpdate, t.EmberJoinDecision, t.EmberNodeId)), - 'broadcastNextNetworkKey': (0x73, (t.EmberKeyData, ), (t.EmberStatus, )), - 'broadcastNetworkKeySwitch': (0x74, (), (t.EmberStatus, )), - 'becomeTrustCenter': (0x77, (t.EmberKeyData, ), (t.EmberStatus, )), - 'aesMmoHash': (0x6F, (t.EmberAesMmoHashContext, t.Bool, t.LVBytes), (t.EmberStatus, t.EmberAesMmoHashContext)), - 'removeDevice': (0xA8, (t.EmberNodeId, t.EmberEUI64, t.EmberEUI64), (t.EmberStatus, )), - 'unicastNwkKeyUpdate': (0xA9, (t.EmberNodeId, t.EmberEUI64, t.EmberKeyData), (t.EmberStatus, )), + "trustCenterJoinHandler": ( + 0x24, + (), + ( + t.EmberNodeId, + t.EmberEUI64, + t.EmberDeviceUpdate, + t.EmberJoinDecision, + t.EmberNodeId, + ), + ), + "broadcastNextNetworkKey": (0x73, (t.EmberKeyData,), (t.EmberStatus,)), + "broadcastNetworkKeySwitch": (0x74, (), (t.EmberStatus,)), + "becomeTrustCenter": (0x77, (t.EmberKeyData,), (t.EmberStatus,)), + "aesMmoHash": ( + 0x6F, + (t.EmberAesMmoHashContext, t.Bool, t.LVBytes), + (t.EmberStatus, t.EmberAesMmoHashContext), + ), + "removeDevice": ( + 0xA8, + (t.EmberNodeId, t.EmberEUI64, t.EmberEUI64), + (t.EmberStatus,), + ), + "unicastNwkKeyUpdate": ( + 0xA9, + (t.EmberNodeId, t.EmberEUI64, t.EmberKeyData), + (t.EmberStatus,), + ), # 11. Certificate Based Key Exchange (CBKE) Frames - 'generateCbkeKeys': (0xA4, (), (t.EmberStatus, )), - 'generateCbkeKeysHandler': (0x9E, (), (t.EmberStatus, t.EmberPublicKeyData)), - 'calculateSmacs': (0x9F, (t.Bool, t.EmberCertificateData, t.EmberPublicKeyData), (t.EmberStatus, )), - 'calculateSmacsHandler': (0xA0, (), (t.EmberStatus, t.EmberSmacData, t.EmberSmacData)), - 'generateCbkeKeys283k1': (0xE8, (), (t.EmberStatus, )), - 'generateCbkeKeysHandler283k1': (0xE9, (), (t.EmberStatus, t.EmberPublicKey283k1Data)), - 'calculateSmacs283k1': (0xEA, (t.Bool, t.EmberCertificate283k1Data, t.EmberPublicKey283k1Data), (t.EmberStatus, )), - 'calculateSmacsHandler283k1': (0xEB, (), (t.EmberStatus, t.EmberSmacData, t.EmberSmacData)), - 'clearTemporaryDataMaybeStoreLinkKey': (0xA1, (t.Bool, ), (t.EmberStatus, )), - 'clearTemporaryDataMaybeStoreLinkKey283k1': (0xEE, (t.Bool, ), (t.EmberStatus, )), - 'getCertificate': (0xA5, (), (t.EmberStatus, t.EmberCertificateData)), - 'getCertificate283k1': (0xEC, (), (t.EmberStatus, t.EmberCertificate283k1Data)), - 'dsaSign': (0xA6, (t.LVBytes, ), (t.EmberStatus, )), # Deprecated - 'dsaSignHandler': (0xA7, (), (t.EmberStatus, t.LVBytes)), # Deprecated - 'dsaVerify': (0xA3, (t.EmberMessageDigest, t.EmberCertificateData, t.EmberSignatureData), (t.EmberStatus, )), - 'dsaVerifyHandler': (0x78, (), (t.EmberStatus, )), - 'dsaVerify283k1': (0xB0, (t.EmberMessageDigest, t.EmberCertificate283k1Data, t.EmberSignature283k1Data), (t.EmberStatus, )), - 'setPreinstalledCbkeData': (0xA2, (t.EmberPublicKeyData, t.EmberCertificateData, t.EmberPrivateKeyData), (t.EmberStatus, )), - 'setPreinstalledCbkeData283k1': (0xED, (t.EmberPublicKey283k1Data, t.EmberCertificate283k1Data, t.EmberPrivateKey283k1Data), (t.EmberStatus, )), + "generateCbkeKeys": (0xA4, (), (t.EmberStatus,)), + "generateCbkeKeysHandler": (0x9E, (), (t.EmberStatus, t.EmberPublicKeyData)), + "calculateSmacs": ( + 0x9F, + (t.Bool, t.EmberCertificateData, t.EmberPublicKeyData), + (t.EmberStatus,), + ), + "calculateSmacsHandler": ( + 0xA0, + (), + (t.EmberStatus, t.EmberSmacData, t.EmberSmacData), + ), + "generateCbkeKeys283k1": (0xE8, (), (t.EmberStatus,)), + "generateCbkeKeysHandler283k1": ( + 0xE9, + (), + (t.EmberStatus, t.EmberPublicKey283k1Data), + ), + "calculateSmacs283k1": ( + 0xEA, + (t.Bool, t.EmberCertificate283k1Data, t.EmberPublicKey283k1Data), + (t.EmberStatus,), + ), + "calculateSmacsHandler283k1": ( + 0xEB, + (), + (t.EmberStatus, t.EmberSmacData, t.EmberSmacData), + ), + "clearTemporaryDataMaybeStoreLinkKey": (0xA1, (t.Bool,), (t.EmberStatus,)), + "clearTemporaryDataMaybeStoreLinkKey283k1": (0xEE, (t.Bool,), (t.EmberStatus,)), + "getCertificate": (0xA5, (), (t.EmberStatus, t.EmberCertificateData)), + "getCertificate283k1": (0xEC, (), (t.EmberStatus, t.EmberCertificate283k1Data)), + "dsaSign": (0xA6, (t.LVBytes,), (t.EmberStatus,)), # Deprecated + "dsaSignHandler": (0xA7, (), (t.EmberStatus, t.LVBytes)), # Deprecated + "dsaVerify": ( + 0xA3, + (t.EmberMessageDigest, t.EmberCertificateData, t.EmberSignatureData), + (t.EmberStatus,), + ), + "dsaVerifyHandler": (0x78, (), (t.EmberStatus,)), + "dsaVerify283k1": ( + 0xB0, + (t.EmberMessageDigest, t.EmberCertificate283k1Data, t.EmberSignature283k1Data), + (t.EmberStatus,), + ), + "setPreinstalledCbkeData": ( + 0xA2, + (t.EmberPublicKeyData, t.EmberCertificateData, t.EmberPrivateKeyData), + (t.EmberStatus,), + ), + "setPreinstalledCbkeData283k1": ( + 0xED, + ( + t.EmberPublicKey283k1Data, + t.EmberCertificate283k1Data, + t.EmberPrivateKey283k1Data, + ), + (t.EmberStatus,), + ), # 12. Mfglib Frames - 'mfglibStart': (0x83, (t.Bool, ), (t.EmberStatus, )), - 'mfglibEnd': (0x84, (), (t.EmberStatus, )), - 'mfglibStartTone': (0x85, (), (t.EmberStatus, )), - 'mfglibStopTone': (0x86, (), (t.EmberStatus, )), - 'mfglibStartStream': (0x87, (), (t.EmberStatus, )), - 'mfglibStopStream': (0x88, (), (t.EmberStatus, )), - 'mfglibSendPacket': (0x89, (t.LVBytes, ), (t.EmberStatus, )), - 'mfglibSetChannel': (0x8A, (t.uint8_t, ), (t.EmberStatus, )), - 'mfglibGetChannel': (0x8B, (), (t.uint8_t, )), - 'mfglibSetPower': (0x8C, (t.uint16_t, t.int8s), (t.EmberStatus, )), - 'mfglibGetPower': (0x8D, (), (t.int8s, )), - 'mfglibRxHandler': (0x8E, (), (t.uint8_t, t.int8s, t.LVBytes)), + "mfglibStart": (0x83, (t.Bool,), (t.EmberStatus,)), + "mfglibEnd": (0x84, (), (t.EmberStatus,)), + "mfglibStartTone": (0x85, (), (t.EmberStatus,)), + "mfglibStopTone": (0x86, (), (t.EmberStatus,)), + "mfglibStartStream": (0x87, (), (t.EmberStatus,)), + "mfglibStopStream": (0x88, (), (t.EmberStatus,)), + "mfglibSendPacket": (0x89, (t.LVBytes,), (t.EmberStatus,)), + "mfglibSetChannel": (0x8A, (t.uint8_t,), (t.EmberStatus,)), + "mfglibGetChannel": (0x8B, (), (t.uint8_t,)), + "mfglibSetPower": (0x8C, (t.uint16_t, t.int8s), (t.EmberStatus,)), + "mfglibGetPower": (0x8D, (), (t.int8s,)), + "mfglibRxHandler": (0x8E, (), (t.uint8_t, t.int8s, t.LVBytes)), # 13. Bootloader Frames - 'launchStandaloneBootloader': (0x8F, (t.uint8_t, ), (t.EmberStatus, )), - 'sendBootloadMessage': (0x90, (t.Bool, t.EmberEUI64, t.LVBytes), (t.EmberStatus, )), - 'getStandaloneBootloaderVersionPlatMicroPhy': (0x91, (), (t.uint16_t, t.uint8_t, t.uint8_t, t.uint8_t)), - 'incomingBootloadMessageHandler': (0x92, (), (t.EmberEUI64, t.uint8_t, t.int8s, t.LVBytes)), - 'bootloadTransmitCompleteHandler': (0x93, (), (t.EmberStatus, t.LVBytes)), - 'aesEncrypt': (0x94, (t.fixed_list(16, t.uint8_t), t.fixed_list(16, t.uint8_t)), (t.fixed_list(16, t.uint8_t), )), - 'overrideCurrentChannel': (0x95, (t.uint8_t, ), (t.EmberStatus, )), + "launchStandaloneBootloader": (0x8F, (t.uint8_t,), (t.EmberStatus,)), + "sendBootloadMessage": (0x90, (t.Bool, t.EmberEUI64, t.LVBytes), (t.EmberStatus,)), + "getStandaloneBootloaderVersionPlatMicroPhy": ( + 0x91, + (), + (t.uint16_t, t.uint8_t, t.uint8_t, t.uint8_t), + ), + "incomingBootloadMessageHandler": ( + 0x92, + (), + (t.EmberEUI64, t.uint8_t, t.int8s, t.LVBytes), + ), + "bootloadTransmitCompleteHandler": (0x93, (), (t.EmberStatus, t.LVBytes)), + "aesEncrypt": ( + 0x94, + (t.fixed_list(16, t.uint8_t), t.fixed_list(16, t.uint8_t)), + (t.fixed_list(16, t.uint8_t),), + ), + "overrideCurrentChannel": (0x95, (t.uint8_t,), (t.EmberStatus,)), # 14. ZLL Frames - 'zllNetworkOps': (0xB2, (t.EmberZllNetwork, t.EzspZllNetworkOperation, t.int8s), (t.EmberStatus, )), - 'zllSetInitialSecurityState': (0xB3, (t.EmberKeyData, t.EmberZllInitialSecurityState), (t.EmberStatus, )), - 'zllStartScan': (0xB4, (t.uint32_t, t.int8s, t.EmberNodeType), (t.EmberStatus, )), - 'zllSetRxOnWhenIdle': (0xB5, (t.uint16_t, ), (t.EmberStatus, )), - 'zllNetworkFoundHandler': (0xB6, (), (t.EmberZllNetwork, t.Bool, t.EmberZllDeviceInfoRecord, t.uint8_t, t.int8s)), - 'zllScanCompleteHandler': (0xB7, (), (t.EmberStatus, )), - 'zllAddressAssignmentHandler': (0xB8, (), (t.EmberZllAddressAssignment, t.uint8_t, t.int8s)), - 'setLogicalAndRadioChannel': (0xB9, (t.uint8_t, ), (t.EmberStatus, )), - 'getLogicalChannel': (0xBA, (), (t.uint8_t, )), - 'zllTouchLinkTargetHandler': (0xBB, (), (t.EmberZllNetwork, )), - 'zllGetTokens': (0xBC, (), (t.EmberTokTypeStackZllData, t.EmberTokTypeStackZllSecurity)), - 'zllSetDataToken': (0xBD, (t.EmberTokTypeStackZllData, ), ()), - 'zllSetNonZllNetwork': (0xBF, (), ()), - 'isZllNetwork': (0xBE, (), (t.Bool, )), + "zllNetworkOps": ( + 0xB2, + (t.EmberZllNetwork, t.EzspZllNetworkOperation, t.int8s), + (t.EmberStatus,), + ), + "zllSetInitialSecurityState": ( + 0xB3, + (t.EmberKeyData, t.EmberZllInitialSecurityState), + (t.EmberStatus,), + ), + "zllStartScan": (0xB4, (t.uint32_t, t.int8s, t.EmberNodeType), (t.EmberStatus,)), + "zllSetRxOnWhenIdle": (0xB5, (t.uint16_t,), (t.EmberStatus,)), + "zllNetworkFoundHandler": ( + 0xB6, + (), + (t.EmberZllNetwork, t.Bool, t.EmberZllDeviceInfoRecord, t.uint8_t, t.int8s), + ), + "zllScanCompleteHandler": (0xB7, (), (t.EmberStatus,)), + "zllAddressAssignmentHandler": ( + 0xB8, + (), + (t.EmberZllAddressAssignment, t.uint8_t, t.int8s), + ), + "setLogicalAndRadioChannel": (0xB9, (t.uint8_t,), (t.EmberStatus,)), + "getLogicalChannel": (0xBA, (), (t.uint8_t,)), + "zllTouchLinkTargetHandler": (0xBB, (), (t.EmberZllNetwork,)), + "zllGetTokens": ( + 0xBC, + (), + (t.EmberTokTypeStackZllData, t.EmberTokTypeStackZllSecurity), + ), + "zllSetDataToken": (0xBD, (t.EmberTokTypeStackZllData,), ()), + "zllSetNonZllNetwork": (0xBF, (), ()), + "isZllNetwork": (0xBE, (), (t.Bool,)), # 15 RF4CE Frames - 'rf4ceSetPairingTableEntry': (0xD0, (t.uint8_t, t.EmberRf4cePairingTableEntry), (t.EmberStatus, )), - 'rf4ceGetPairingTableEntry': (0xD1, (t.uint8_t, ), (t.EmberStatus, t.EmberRf4cePairingTableEntry)), - 'rf4ceDeletePairingTableEntry': (0xD2, (t.uint8_t, ), (t.EmberStatus, )), - 'rf4ceKeyUpdate': (0xD3, (t.uint8_t, t.EmberKeyData), (t.EmberStatus, )), - 'rf4ceSend': (0xD4, (t.uint8_t, t.uint8_t, t.uint16_t, t.EmberRf4ceTxOption, t.uint8_t, t.LVBytes), (t.EmberStatus, )), - 'rf4ceIncomingMessageHandler': (0xD5, (), (t.uint8_t, t.uint8_t, t.uint16_t, t.EmberRf4ceTxOption, t.LVBytes)), - 'rf4ceMessageSentHandler': (0xD6, (), (t.EmberStatus, t.uint8_t, t.EmberRf4ceTxOption, t.uint8_t, t.uint16_t, t.uint8_t, t.LVBytes)), - 'rf4ceStart': (0xD7, (t.EmberRf4ceNodeCapabilities, t.EmberRf4ceVendorInfo, t.int8s), (t.EmberStatus, )), - 'rf4ceStop': (0xD8, (), (t.EmberStatus, )), - 'rf4ceDiscovery': (0xD9, (t.EmberPanId, t.EmberNodeId, t.uint8_t, t.uint16_t, t.LVBytes), (t.EmberStatus, )), - 'rf4ceDiscoveryCompleteHandler': (0xDA, (), (t.EmberStatus, )), - 'rf4ceDiscoveryRequestHandler': (0xDB, (), (t.EmberEUI64, t.uint8_t, t.EmberRf4ceVendorInfo, t.EmberRf4ceApplicationInfo, t.uint8_t, t.uint8_t)), - 'rf4ceDiscoveryResponseHandler': (0xDC, (), (t.Bool, t.uint8_t, t.EmberPanId, t.EmberEUI64, t.uint8_t, t.EmberRf4ceVendorInfo, t.EmberRf4ceApplicationInfo, t.uint8_t, t.uint8_t)), - 'rf4ceEnableAutoDiscoveryResponse': (0xDD, (t.uint16_t, ), (t.EmberStatus, )), - 'rf4ceAutoDiscoveryResponseCompleteHandler': (0xDE, (), (t.EmberStatus, t.EmberEUI64, t.uint8_t, t.EmberRf4ceVendorInfo, t.EmberRf4ceApplicationInfo, t.uint8_t)), - 'rf4cePair': (0xDF, (t.uint8_t, t.EmberPanId, t.EmberEUI64, t.uint8_t), (t.EmberStatus, )), - 'rf4cePairCompleteHandler': (0xE0, (), (t.EmberStatus, t.uint8_t, t.EmberRf4ceVendorInfo, t.EmberRf4ceApplicationInfo)), - 'rf4cePairRequestHandler': (0xE1, (), (t.EmberStatus, t.uint8_t, t.EmberEUI64, t.uint8_t, t.EmberRf4ceVendorInfo, t.EmberRf4ceApplicationInfo, t.uint8_t)), - 'rf4ceUnpair': (0xE2, (t.uint8_t, ), (t.EmberStatus, )), - 'rf4ceUnpairHandler': (0xE3, (), (t.uint8_t, )), - 'rf4ceUnpairCompleteHandler': (0xE4, (), (t.uint8_t, )), - 'rf4ceSetPowerSavingParameters': (0xE5, (t.uint32_t, t.uint32_t), (t.EmberStatus, )), - 'rf4ceSetFrequencyAgilityParameters': (0xE6, (t.uint8_t, t.uint8_t, t.int8s, t.uint16_t, t.uint8_t), (t.EmberStatus, )), - 'rf4ceSetApplicationInfo': (0xE7, (t.EmberRf4ceApplicationInfo, ), (t.EmberStatus, )), - 'rf4ceGetApplicationInfo': (0xEF, (), (t.EmberStatus, t.EmberRf4ceApplicationInfo)), - 'rf4ceGetMaxPayload': (0xF3, (t.uint8_t, t.EmberRf4ceTxOption), (t.uint8_t, )), - 'rf4ceGetNetworkParameters': (0xF4, (), (t.EmberStatus, t.EmberNodeType, t.EmberNetworkParameters)), + "rf4ceSetPairingTableEntry": ( + 0xD0, + (t.uint8_t, t.EmberRf4cePairingTableEntry), + (t.EmberStatus,), + ), + "rf4ceGetPairingTableEntry": ( + 0xD1, + (t.uint8_t,), + (t.EmberStatus, t.EmberRf4cePairingTableEntry), + ), + "rf4ceDeletePairingTableEntry": (0xD2, (t.uint8_t,), (t.EmberStatus,)), + "rf4ceKeyUpdate": (0xD3, (t.uint8_t, t.EmberKeyData), (t.EmberStatus,)), + "rf4ceSend": ( + 0xD4, + (t.uint8_t, t.uint8_t, t.uint16_t, t.EmberRf4ceTxOption, t.uint8_t, t.LVBytes), + (t.EmberStatus,), + ), + "rf4ceIncomingMessageHandler": ( + 0xD5, + (), + (t.uint8_t, t.uint8_t, t.uint16_t, t.EmberRf4ceTxOption, t.LVBytes), + ), + "rf4ceMessageSentHandler": ( + 0xD6, + (), + ( + t.EmberStatus, + t.uint8_t, + t.EmberRf4ceTxOption, + t.uint8_t, + t.uint16_t, + t.uint8_t, + t.LVBytes, + ), + ), + "rf4ceStart": ( + 0xD7, + (t.EmberRf4ceNodeCapabilities, t.EmberRf4ceVendorInfo, t.int8s), + (t.EmberStatus,), + ), + "rf4ceStop": (0xD8, (), (t.EmberStatus,)), + "rf4ceDiscovery": ( + 0xD9, + (t.EmberPanId, t.EmberNodeId, t.uint8_t, t.uint16_t, t.LVBytes), + (t.EmberStatus,), + ), + "rf4ceDiscoveryCompleteHandler": (0xDA, (), (t.EmberStatus,)), + "rf4ceDiscoveryRequestHandler": ( + 0xDB, + (), + ( + t.EmberEUI64, + t.uint8_t, + t.EmberRf4ceVendorInfo, + t.EmberRf4ceApplicationInfo, + t.uint8_t, + t.uint8_t, + ), + ), + "rf4ceDiscoveryResponseHandler": ( + 0xDC, + (), + ( + t.Bool, + t.uint8_t, + t.EmberPanId, + t.EmberEUI64, + t.uint8_t, + t.EmberRf4ceVendorInfo, + t.EmberRf4ceApplicationInfo, + t.uint8_t, + t.uint8_t, + ), + ), + "rf4ceEnableAutoDiscoveryResponse": (0xDD, (t.uint16_t,), (t.EmberStatus,)), + "rf4ceAutoDiscoveryResponseCompleteHandler": ( + 0xDE, + (), + ( + t.EmberStatus, + t.EmberEUI64, + t.uint8_t, + t.EmberRf4ceVendorInfo, + t.EmberRf4ceApplicationInfo, + t.uint8_t, + ), + ), + "rf4cePair": ( + 0xDF, + (t.uint8_t, t.EmberPanId, t.EmberEUI64, t.uint8_t), + (t.EmberStatus,), + ), + "rf4cePairCompleteHandler": ( + 0xE0, + (), + (t.EmberStatus, t.uint8_t, t.EmberRf4ceVendorInfo, t.EmberRf4ceApplicationInfo), + ), + "rf4cePairRequestHandler": ( + 0xE1, + (), + ( + t.EmberStatus, + t.uint8_t, + t.EmberEUI64, + t.uint8_t, + t.EmberRf4ceVendorInfo, + t.EmberRf4ceApplicationInfo, + t.uint8_t, + ), + ), + "rf4ceUnpair": (0xE2, (t.uint8_t,), (t.EmberStatus,)), + "rf4ceUnpairHandler": (0xE3, (), (t.uint8_t,)), + "rf4ceUnpairCompleteHandler": (0xE4, (), (t.uint8_t,)), + "rf4ceSetPowerSavingParameters": (0xE5, (t.uint32_t, t.uint32_t), (t.EmberStatus,)), + "rf4ceSetFrequencyAgilityParameters": ( + 0xE6, + (t.uint8_t, t.uint8_t, t.int8s, t.uint16_t, t.uint8_t), + (t.EmberStatus,), + ), + "rf4ceSetApplicationInfo": (0xE7, (t.EmberRf4ceApplicationInfo,), (t.EmberStatus,)), + "rf4ceGetApplicationInfo": (0xEF, (), (t.EmberStatus, t.EmberRf4ceApplicationInfo)), + "rf4ceGetMaxPayload": (0xF3, (t.uint8_t, t.EmberRf4ceTxOption), (t.uint8_t,)), + "rf4ceGetNetworkParameters": ( + 0xF4, + (), + (t.EmberStatus, t.EmberNodeType, t.EmberNetworkParameters), + ), # 16 Green Power Frames - 'gpProxyTableProcessGpPairing': (0xC9, (t.uint32_t, t.EmberGpAddress, t.uint8_t, t.uint16_t, t.uint16_t, t.uint16_t, t.fixed_list(8, t.uint8_t), t.EmberKeyData), ()), - 'dGpSend': (0xC6, (t.Bool, t.Bool, t.EmberGpAddress, t.uint8_t, t.LVBytes, t.uint8_t, t.uint16_t), (t.EmberStatus, )), - 'dGpSentHandler': (0xC7, (), (t.EmberStatus, t.uint8_t)), - 'gpepIncomingMessageHandler': (0xC5, (), (t.EmberStatus, t.uint8_t, t.uint8_t, t.EmberGpAddress, t.EmberGpSecurityLevel, t.EmberGpKeyType, t.Bool, t.Bool, t.uint32_t, t.uint8_t, t.uint32_t, t.EmberGpSinkListEntry, t.LVBytes)), + "gpProxyTableProcessGpPairing": ( + 0xC9, + ( + t.uint32_t, + t.EmberGpAddress, + t.uint8_t, + t.uint16_t, + t.uint16_t, + t.uint16_t, + t.fixed_list(8, t.uint8_t), + t.EmberKeyData, + ), + (), + ), + "dGpSend": ( + 0xC6, + (t.Bool, t.Bool, t.EmberGpAddress, t.uint8_t, t.LVBytes, t.uint8_t, t.uint16_t), + (t.EmberStatus,), + ), + "dGpSentHandler": (0xC7, (), (t.EmberStatus, t.uint8_t)), + "gpepIncomingMessageHandler": ( + 0xC5, + (), + ( + t.EmberStatus, + t.uint8_t, + t.uint8_t, + t.EmberGpAddress, + t.EmberGpSecurityLevel, + t.EmberGpKeyType, + t.Bool, + t.Bool, + t.uint32_t, + t.uint8_t, + t.uint32_t, + t.EmberGpSinkListEntry, + t.LVBytes, + ), + ), } diff --git a/bellows/ezsp.py b/bellows/ezsp.py index 51e2ea40..e6be20a8 100644 --- a/bellows/ezsp.py +++ b/bellows/ezsp.py @@ -15,7 +15,7 @@ class EZSP: COMMANDS = COMMANDS - ezsp_version = 4 + EZSP_VERSION = 4 def __init__(self): self._awaiting = {} @@ -25,6 +25,7 @@ def __init__(self): self._ezsp_event = asyncio.Event() self._seq = 0 self._gw = None + self._ezsp_version = self.EZSP_VERSION self._awaiting = {} self.COMMANDS_BY_ID = {} for name, details in self.COMMANDS.items(): @@ -38,8 +39,9 @@ async def connect(self, device, baudrate): def reconnect(self): """Reconnect using saved parameters.""" - LOGGER.debug("Reconnecting %s serial port on %s bauds", - self._device, self._baudrate) + LOGGER.debug( + "Reconnecting %s serial port on %s bauds", self._device, self._baudrate + ) return self.connect(self._device, self._baudrate) async def reset(self): @@ -56,11 +58,14 @@ async def reset(self): self.start_ezsp() async def version(self): - version = self.ezsp_version - result = await self._command('version', version) - if result[0] != version: - LOGGER.debug("Switching to eszp version %d", result[0]) - await self._command('version', result[0]) + ver, stack_type, stack_version = await self._command( + "version", self.ezsp_version + ) + if ver != self.version: + self._ezsp_version = ver + await self._command("version", ver) + LOGGER.debug("Switched to EZSP protocol version %d", self.ezsp_version) + LOGGER.info("EZSP Stack Type: %s, Stack Version: %s", stack_type, stack_version) def close(self): self.stop_ezsp() @@ -71,11 +76,7 @@ def close(self): def _ezsp_frame(self, name, *args): c = self.COMMANDS[name] data = t.serialize(args, c[1]) - frame = [ - self._seq & 0xff, - 0, # Frame control. TODO. - c[0] # Frame ID - ] + frame = [self._seq & 0xFF, 0, c[0]] # Frame control. TODO. # Frame ID if self.ezsp_version >= 5: frame.insert(1, 0xFF) # Legacy Frame ID frame.insert(1, 0x00) # Ext frame control. TODO. @@ -121,30 +122,26 @@ def cb(frame_name, response): startScan = functools.partialmethod( _list_command, - 'startScan', - ['energyScanResultHandler', 'networkFoundHandler'], - 'scanCompleteHandler', + "startScan", + ["energyScanResultHandler", "networkFoundHandler"], + "scanCompleteHandler", 1, ) pollForData = functools.partialmethod( - _list_command, - 'pollForData', - ['pollHandler'], - 'pollCompleteHandler', - 0, + _list_command, "pollForData", ["pollHandler"], "pollCompleteHandler", 0 ) zllStartScan = functools.partialmethod( _list_command, - 'zllStartScan', - ['zllNetworkFoundHandler'], - 'zllScanCompleteHandler', + "zllStartScan", + ["zllNetworkFoundHandler"], + "zllScanCompleteHandler", 0, ) rf4ceDiscovery = functools.partialmethod( _list_command, - 'rf4ceDiscovery', - ['rf4ceDiscoveryResponseHandler'], - 'rf4ceDiscoveryCompleteHandler', + "rf4ceDiscovery", + ["rf4ceDiscoveryResponseHandler"], + "rf4ceDiscoveryCompleteHandler", 0, ) @@ -155,33 +152,32 @@ def connection_lost(self, exc): def enter_failed_state(self, error): """UART received error frame.""" - LOGGER.error( - "NCP entered failed state. Requesting APP controller restart") + LOGGER.error("NCP entered failed state. Requesting APP controller restart") self.stop_ezsp() - self.handle_callback('_reset_controller_application', (error, )) + self.handle_callback("_reset_controller_application", (error,)) async def formNetwork(self, parameters): # noqa: N802 fut = asyncio.Future() def cb(frame_name, response): nonlocal fut - if frame_name == 'stackStatusHandler': + if frame_name == "stackStatusHandler": fut.set_result(response) self.add_callback(cb) - v = await self._command('formNetwork', parameters) + v = await self._command("formNetwork", parameters) if v[0] != t.EmberStatus.SUCCESS: - raise Exception("Failure forming network: %s" % (v, )) + raise Exception("Failure forming network: %s" % (v,)) v = await fut if v[0] != t.EmberStatus.NETWORK_UP: - raise Exception("Failure forming network: %s" % (v, )) + raise Exception("Failure forming network: %s" % (v,)) return v def __getattr__(self, name): if name not in self.COMMANDS: - raise AttributeError("%s not found in COMMANDS" % (name, )) + raise AttributeError("%s not found in COMMANDS" % (name,)) return functools.partial(self._command, name) @@ -203,7 +199,8 @@ def frame_received(self, data): LOGGER.debug( "Application frame %s (%s) received: %s", frame_id, - frame_name, binascii.hexlify(data) + frame_name, + binascii.hexlify(data), ) if sequence in self._awaiting: @@ -215,8 +212,8 @@ def frame_received(self, data): except asyncio.InvalidStateError as exc: LOGGER.debug( "Error processing %s response. %s command timed out?", - sequence, self.COMMANDS_BY_ID.get(expected_id, - [expected_id])[0] + sequence, + self.COMMANDS_BY_ID.get(expected_id, [expected_id])[0], ) else: schema = self.COMMANDS_BY_ID[frame_id][2] @@ -224,9 +221,6 @@ def frame_received(self, data): result, data = t.deserialize(data, schema) self.handle_callback(frame_name, result) - if frame_id == 0x00: - self.ezsp_version = result[0] - def add_callback(self, cb): id_ = hash(cb) while id_ in self._callbacks: @@ -256,3 +250,8 @@ def stop_ezsp(self): def is_ezsp_running(self): """Return True if EZSP is running.""" return self._ezsp_event.is_set() + + @property + def ezsp_version(self): + """Return protocol version.""" + return self._ezsp_version diff --git a/bellows/multicast.py b/bellows/multicast.py index 2b6a8bf7..048b2a57 100644 --- a/bellows/multicast.py +++ b/bellows/multicast.py @@ -7,6 +7,7 @@ class Multicast: """Multicast table controller for EZSP.""" + TABLE_SIZE = 16 def __init__(self, ezsp): @@ -21,8 +22,7 @@ async def _initialize(self) -> None: for i in range(0, self.TABLE_SIZE): status, entry = await e.getMulticastTableEntry(i) if status != t.EmberStatus.SUCCESS: - LOGGER.error("Couldn't get MulticastTableEntry #%s: %s", - i, status) + LOGGER.error("Couldn't get MulticastTableEntry #%s: %s", i, status) continue LOGGER.debug("MulticastTableEntry[%s] = %s", i, entry) if entry.endpoint != 0: @@ -40,8 +40,7 @@ async def startup(self, coordinator) -> None: async def subscribe(self, group_id) -> t.EmberStatus: if group_id in self._multicast: - LOGGER.debug("%s is already subscribed", - t.EmberMulticastId(group_id)) + LOGGER.debug("%s is already subscribed", t.EmberMulticastId(group_id)) return t.EmberStatus.SUCCESS try: @@ -57,13 +56,20 @@ async def subscribe(self, group_id) -> t.EmberStatus: if status[0] != t.EmberStatus.SUCCESS: LOGGER.warning( "Set MulticastTableEntry #%s for %s multicast id: %s", - idx, entry.multicastId, status) + idx, + entry.multicastId, + status, + ) self._available.add(idx) return status[0] self._multicast[entry.multicastId] = (entry, idx) - LOGGER.debug("Set MulticastTableEntry #%s for %s multicast id: %s", - idx, entry.multicastId, status) + LOGGER.debug( + "Set MulticastTableEntry #%s for %s multicast id: %s", + idx, + entry.multicastId, + status, + ) return status[0] async def unsubscribe(self, group_id) -> t.EmberStatus: @@ -71,8 +77,8 @@ async def unsubscribe(self, group_id) -> t.EmberStatus: entry, idx = self._multicast[group_id] except KeyError: LOGGER.error( - "Couldn't find MulticastTableEntry for %s multicast_id", - group_id) + "Couldn't find MulticastTableEntry for %s multicast_id", group_id + ) return t.EmberStatus.INDEX_OUT_OF_RANGE entry.endpoint = t.uint8_t(0) @@ -80,11 +86,18 @@ async def unsubscribe(self, group_id) -> t.EmberStatus: if status[0] != t.EmberStatus.SUCCESS: LOGGER.warning( "Set MulticastTableEntry #%s for %s multicast id: %s", - idx, entry.multicastId, status) + idx, + entry.multicastId, + status, + ) return status[0] self._multicast.pop(group_id) self._available.add(idx) - LOGGER.debug("Set MulticastTableEntry #%s for %s multicast id: %s", - idx, entry.multicastId, status) + LOGGER.debug( + "Set MulticastTableEntry #%s for %s multicast id: %s", + idx, + entry.multicastId, + status, + ) return status[0] diff --git a/bellows/thread.py b/bellows/thread.py index 6f39d48d..ca202ce5 100644 --- a/bellows/thread.py +++ b/bellows/thread.py @@ -10,7 +10,8 @@ class EventLoopThread: - ''' Run a parallel event loop in a separate thread ''' + """ Run a parallel event loop in a separate thread """ + def __init__(self): self.loop = None self.thread_complete = None @@ -36,9 +37,9 @@ async def start(self): if self.loop is not None and not self.loop.is_closed(): return - executor_opts = {'max_workers': 1} + executor_opts = {"max_workers": 1} if sys.version_info[:2] >= (3, 6): - executor_opts['thread_name_prefix'] = __name__ + executor_opts["thread_name_prefix"] = __name__ executor = ThreadPoolExecutor(**executor_opts) thread_started_future = current_loop.create_future() @@ -46,8 +47,11 @@ async def start(self): async def init_task(): current_loop.call_soon_threadsafe(thread_started_future.set_result, None) - # Use current loop so current loop has a reference to the long-running thread as one of its tasks - thread_complete = current_loop.run_in_executor(executor, self._thread_main, init_task()) + # Use current loop so current loop has a reference to the long-running thread + # as one of its tasks + thread_complete = current_loop.run_in_executor( + executor, self._thread_main, init_task() + ) self.thread_complete = thread_complete current_loop.call_soon(executor.shutdown, False) await thread_started_future @@ -59,10 +63,11 @@ def force_stop(self): class ThreadsafeProxy: - ''' Proxy class which enforces threadsafe non-blocking calls + """ Proxy class which enforces threadsafe non-blocking calls This class can be used to wrap an object to ensure any calls using that object's methods are done on a particular event loop - ''' + """ + def __init__(self, obj, obj_loop): self._obj = obj self._obj_loop = obj_loop @@ -70,8 +75,11 @@ def __init__(self, obj, obj_loop): def __getattr__(self, name): func = getattr(self._obj, name) if not callable(func): - raise TypeError("Can only use ThreadsafeProxy with callable attributes: {}.{}".format( - self._obj.__class__.__name__, name)) + raise TypeError( + "Can only use ThreadsafeProxy with callable attributes: {}.{}".format( + self._obj.__class__.__name__, name + ) + ) def func_wrapper(*args, **kwargs): loop = self._obj_loop @@ -87,12 +95,17 @@ def func_wrapper(*args, **kwargs): future = asyncio.run_coroutine_threadsafe(call(), loop) return asyncio.wrap_future(future, loop=curr_loop) else: + def check_result_wrapper(): result = call() if result is not None: - raise TypeError("ThreadsafeProxy can only wrap functions with no return value \ - \nUse an async method to return values: {}.{}".format( - self._obj.__class__.__name__, name)) + raise TypeError( + ( + "ThreadsafeProxy can only wrap functions with no return" + "value \nUse an async method to return values: {}.{}" + ).format(self._obj.__class__.__name__, name) + ) loop.call_soon_threadsafe(check_result_wrapper) + return func_wrapper diff --git a/bellows/types/__init__.py b/bellows/types/__init__.py index e8e2ebbe..4a25b948 100644 --- a/bellows/types/__init__.py +++ b/bellows/types/__init__.py @@ -12,4 +12,4 @@ def deserialize(data, schema): def serialize(data, schema): - return b''.join(t(v).serialize() for t, v in zip(schema, data)) + return b"".join(t(v).serialize() for t, v in zip(schema, data)) diff --git a/bellows/types/basic.py b/bellows/types/basic.py index 24330ad8..c5c13bbb 100644 --- a/bellows/types/basic.py +++ b/bellows/types/basic.py @@ -2,13 +2,13 @@ class int_t(int): # noqa: N801 _signed = True def serialize(self): - return self.to_bytes(self._size, 'little', signed=self._signed) + return self.to_bytes(self._size, "little", signed=self._signed) @classmethod def deserialize(cls, data): # Work around https://bugs.python.org/issue23640 - r = cls(int.from_bytes(data[:cls._size], 'little', signed=cls._signed)) - data = data[cls._size:] + r = cls(int.from_bytes(data[: cls._size], "little", signed=cls._signed)) + data = data[cls._size :] return r, data @@ -82,15 +82,13 @@ class uint64_t(uint_t): # noqa: N801 class LVBytes(bytes): def serialize(self): - return bytes([ - len(self), - ]) + self + return bytes([len(self)]) + self @classmethod def deserialize(cls, data): - bytes = int.from_bytes(data[:1], 'little') - s = data[1:bytes + 1] - return s, data[bytes + 1:] + bytes = int.from_bytes(data[:1], "little") + s = data[1 : bytes + 1] + return s, data[bytes + 1 :] class _List(list): @@ -98,7 +96,7 @@ class _List(list): def serialize(self): assert self._length is None or len(self) == self._length - return b''.join([self._itemtype(i).serialize() for i in self]) + return b"".join([self._itemtype(i).serialize() for i in self]) @classmethod def deserialize(cls, data): @@ -111,7 +109,7 @@ def deserialize(cls, data): class _LVList(_List): def serialize(self): - head = len(self).to_bytes(1, 'little') + head = len(self).to_bytes(1, "little") data = super().serialize() return head + data @@ -128,12 +126,14 @@ def deserialize(cls, data): def List(itemtype): # noqa: N802 class List(_List): _itemtype = itemtype + return List def LVList(itemtype): # noqa: N802 class LVList(_LVList): _itemtype = itemtype + return LVList @@ -159,7 +159,7 @@ class HexRepr: _hex_len = 2 def __repr__(self): - return ('0x{:0' + str(self._hex_len) + 'x}').format(self) + return ("0x{:0" + str(self._hex_len) + "x}").format(self) def __str__(self): - return ('0x{:0' + str(self._hex_len) + 'x}').format(self) + return ("0x{:0" + str(self._hex_len) + "x}").format(self) diff --git a/bellows/types/named.py b/bellows/types/named.py index 78542742..f0c76435 100644 --- a/bellows/types/named.py +++ b/bellows/types/named.py @@ -1146,7 +1146,7 @@ class EmberOutgoingMessageType(basic.uint8_t, enum.Enum): # Aliased multicast message. This value is passed to emberMessageSentHandler() only. # It may not be passed to emberSendUnicast(). OUTGOING_MULTICAST_WITH_ALIAS = 0x04 - # Aliased Broadcast message. This value is passed to emberMessageSentHandler() only. + # Aliased Broadcast message. This value is passed to emberMessageSentHandler() only. # It may not be passed to emberSendUnicast(). OUTGOING_BROADCAST_WITH_ALIAS = 0x05 # Broadcast message. This value is passed to emberMessageSentHandler() diff --git a/bellows/types/struct.py b/bellows/types/struct.py index d1346d6e..711b7652 100644 --- a/bellows/types/struct.py +++ b/bellows/types/struct.py @@ -10,7 +10,7 @@ def __init__(self, *args, **kwargs): setattr(self, field[0], getattr(args[0], field[0])) def serialize(self): - r = b'' + r = b"" for field in self._fields: r += getattr(self, field[0]).serialize() return r @@ -24,11 +24,11 @@ def deserialize(cls, data): return r, data def __repr__(self): - r = '<%s ' % (self.__class__.__name__, ) - r += ' '.join( - ['%s=%s' % (f[0], getattr(self, f[0], None)) for f in self._fields] + r = "<%s " % (self.__class__.__name__,) + r += " ".join( + ["%s=%s" % (f[0], getattr(self, f[0], None)) for f in self._fields] ) - r += '>' + r += ">" return r @@ -36,29 +36,29 @@ class EmberNetworkParameters(EzspStruct): # Network parameters. _fields = [ # The network's extended PAN identifier. - ('extendedPanId', basic.fixed_list(8, basic.uint8_t)), + ("extendedPanId", basic.fixed_list(8, basic.uint8_t)), # The network's PAN identifier. - ('panId', basic.uint16_t), + ("panId", basic.uint16_t), # A power setting, in dBm. - ('radioTxPower', basic.uint8_t), + ("radioTxPower", basic.uint8_t), # A radio channel. - ('radioChannel', basic.uint8_t), + ("radioChannel", basic.uint8_t), # The method used to initially join the network. - ('joinMethod', named.EmberJoinMethod), + ("joinMethod", named.EmberJoinMethod), # NWK Manager ID. The ID of the network manager in the current network. # This may only be set at joining when using USE_NWK_COMMISSIONING as # the join method. - ('nwkManagerId', named.EmberNodeId), + ("nwkManagerId", named.EmberNodeId), # NWK Update ID. The value of the ZigBee nwkUpdateId known by the # stack. This is used to determine the newest instance of the network # after a PAN ID or channel change. This may only be set at joining # when using USE_NWK_COMMISSIONING as the join method. - ('nwkUpdateId', basic.uint8_t), + ("nwkUpdateId", basic.uint8_t), # NWK channel mask. The list of preferred channels that the NWK manager # has told this device to use when searching for the network. This may # only be set at joining when using USE_NWK_COMMISSIONING as the join # method. - ('channels', basic.uint32_t), + ("channels", basic.uint32_t), ] @@ -66,17 +66,17 @@ class EmberZigbeeNetwork(EzspStruct): # The parameters of a ZigBee network. _fields = [ # The 802.15.4 channel associated with the network. - ('channel', basic.uint8_t), + ("channel", basic.uint8_t), # The network's PAN identifier. - ('panId', basic.uint16_t), + ("panId", basic.uint16_t), # The network's extended PAN identifier. - ('extendedPanId', basic.fixed_list(8, basic.uint8_t)), + ("extendedPanId", basic.fixed_list(8, basic.uint8_t)), # Whether the network is allowing MAC associations. - ('allowingJoin', named.Bool), + ("allowingJoin", named.Bool), # The Stack Profile associated with the network. - ('stackProfile', basic.uint8_t), + ("stackProfile", basic.uint8_t), # The instance of the Network. - ('nwkUpdateId', basic.uint8_t), + ("nwkUpdateId", basic.uint8_t), ] @@ -84,19 +84,19 @@ class EmberApsFrame(EzspStruct): # ZigBee APS frame parameters. _fields = [ # The application profile ID that describes the format of the message. - ('profileId', basic.uint16_t), + ("profileId", basic.uint16_t), # The cluster ID for this message. - ('clusterId', basic.uint16_t), + ("clusterId", basic.uint16_t), # The source endpoint. - ('sourceEndpoint', basic.uint8_t), + ("sourceEndpoint", basic.uint8_t), # The destination endpoint. - ('destinationEndpoint', basic.uint8_t), + ("destinationEndpoint", basic.uint8_t), # A bitmask of options. - ('options', named.EmberApsOption), + ("options", named.EmberApsOption), # The group ID for this message, if it is multicast mode. - ('groupId', basic.uint16_t), + ("groupId", basic.uint16_t), # The sequence number. - ('sequence', basic.uint8_t), + ("sequence", basic.uint8_t), ] @@ -104,23 +104,23 @@ class EmberBindingTableEntry(EzspStruct): # An entry in the binding table. _fields = [ # The type of binding. - ('type', named.EmberBindingType), + ("type", named.EmberBindingType), # The endpoint on the local node. - ('local', basic.uint8_t), + ("local", basic.uint8_t), # A cluster ID that matches one from the local endpoint's simple # descriptor. This cluster ID is set by the provisioning application to # indicate which part an endpoint's functionality is bound to this # particular remote node and is used to distinguish between unicast and # multicast bindings. Note that a binding can be used to send messages # with any cluster ID, not just that listed in the binding. - ('clusterId', basic.uint16_t), + ("clusterId", basic.uint16_t), # The endpoint on the remote node (specified by identifier). - ('remote', basic.uint8_t), + ("remote", basic.uint8_t), # A 64-bit identifier. This is either the destination EUI64 (for # unicasts) or the 64-bit group address (for multicasts). - ('identifier', named.EmberEUI64), + ("identifier", named.EmberEUI64), # The index of the network the binding belongs to. - ('networkIndex', basic.uint8_t), + ("networkIndex", basic.uint8_t), ] @@ -130,12 +130,12 @@ class EmberMulticastTableEntry(EzspStruct): # multicast group will receive messages sent to that multicast group. _fields = [ # The multicast group ID. - ('multicastId', named.EmberMulticastId), + ("multicastId", named.EmberMulticastId), # The endpoint that is a member, or 0 if this entry is not in use (the # ZDO is not a member of any multicast groups.) - ('endpoint', basic.uint8_t), + ("endpoint", basic.uint8_t), # The network index of the network the entry is related to. - ('networkIndex', basic.uint8_t), + ("networkIndex", basic.uint8_t), ] @@ -143,7 +143,7 @@ class EmberKeyData(EzspStruct): # A 128-bit key. _fields = [ # The key data. - ('contents', basic.fixed_list(16, basic.uint8_t)), + ("contents", basic.fixed_list(16, basic.uint8_t)) ] @@ -151,7 +151,7 @@ class EmberCertificateData(EzspStruct): # The implicit certificate used in CBKE. _fields = [ # The certificate data. - ('contents', basic.fixed_list(48, basic.uint8_t)), + ("contents", basic.fixed_list(48, basic.uint8_t)) ] @@ -159,7 +159,7 @@ class EmberPublicKeyData(EzspStruct): # The public key data used in CBKE. _fields = [ # The public key data. - ('contents', basic.fixed_list(22, basic.uint8_t)), + ("contents", basic.fixed_list(22, basic.uint8_t)) ] @@ -167,7 +167,7 @@ class EmberPrivateKeyData(EzspStruct): # The private key data used in CBKE. _fields = [ # The private key data. - ('contents', basic.fixed_list(21, basic.uint8_t)), + ("contents", basic.fixed_list(21, basic.uint8_t)) ] @@ -175,14 +175,14 @@ class EmberTransientKeyData(EzspStruct): # The transient key data structure _fields = [ # The IEEE address paired with the transient link key. - ('eui64', named.EmberEUI64), + ("eui64", named.EmberEUI64), # The key data structure matching the transient key. - ('keyData', EmberKeyData), + ("keyData", EmberKeyData), # The incoming frame counter associated with this key. - ('incomingFrameCounter', basic.uint32_t), + ("incomingFrameCounter", basic.uint32_t), # The number of milliseconds remaining before the key # is automatically timed out of the transient key table. - ('countdownTimerMs', basic.uint32_t), + ("countdownTimerMs", basic.uint32_t), ] @@ -190,7 +190,7 @@ class EmberSmacData(EzspStruct): # The Shared Message Authentication Code data used in CBKE. _fields = [ # The Shared Message Authentication Code data. - ('contents', basic.fixed_list(16, basic.uint8_t)), + ("contents", basic.fixed_list(16, basic.uint8_t)) ] @@ -198,7 +198,7 @@ class EmberSignatureData(EzspStruct): # An ECDSA signature _fields = [ # The signature data. - ('contents', basic.fixed_list(42, basic.uint8_t)), + ("contents", basic.fixed_list(42, basic.uint8_t)) ] @@ -206,7 +206,7 @@ class EmberCertificate283k1Data(EzspStruct): # The implicit certificate used in CBKE. _fields = [ # The 283k1 certificate data. - ('contents', basic.fixed_list(74, basic.uint8_t)), + ("contents", basic.fixed_list(74, basic.uint8_t)) ] @@ -214,7 +214,7 @@ class EmberPublicKey283k1Data(EzspStruct): # The public key data used in CBKE. _fields = [ # The 283k1 public key data. - ('contents', basic.fixed_list(37, basic.uint8_t)), + ("contents", basic.fixed_list(37, basic.uint8_t)) ] @@ -222,7 +222,7 @@ class EmberPrivateKey283k1Data(EzspStruct): # The private key data used in CBKE. _fields = [ # The 283k1 private key data. - ('contents', basic.fixed_list(36, basic.uint8_t)), + ("contents", basic.fixed_list(36, basic.uint8_t)) ] @@ -230,7 +230,7 @@ class EmberSignature283k1Data(EzspStruct): # An ECDSA signature _fields = [ # The 283k1 signature data. - ('contents', basic.fixed_list(72, basic.uint8_t)), + ("contents", basic.fixed_list(72, basic.uint8_t)) ] @@ -238,7 +238,7 @@ class EmberMessageDigest(EzspStruct): # The calculated digest of a message _fields = [ # The calculated digest of a message. - ('contents', basic.fixed_list(16, basic.uint8_t)), + ("contents", basic.fixed_list(16, basic.uint8_t)) ] @@ -246,9 +246,9 @@ class EmberAesMmoHashContext(EzspStruct): # The hash context for an ongoing hash operation. _fields = [ # The result of ongoing the hash operation. - ('result', basic.fixed_list(16, basic.uint8_t)), + ("result", basic.fixed_list(16, basic.uint8_t)), # The total length of the data that has been hashed so far. - ('length', basic.uint32_t), + ("length", basic.uint32_t), ] @@ -257,24 +257,24 @@ class EmberNeighborTableEntry(EzspStruct): # links to and from neighboring nodes. _fields = [ # The neighbor's two byte network id - ('shortId', basic.uint16_t), + ("shortId", basic.uint16_t), # An exponentially weighted moving average of the link quality values # of incoming packets from this neighbor as reported by the PHY. - ('averageLqi', basic.uint8_t), + ("averageLqi", basic.uint8_t), # The incoming cost for this neighbor, computed from the average LQI. # Values range from 1 for a good link to 7 for a bad link. - ('inCost', basic.uint8_t), + ("inCost", basic.uint8_t), # The outgoing cost for this neighbor, obtained from the most recently # received neighbor exchange message from the neighbor. A value of zero # means that a neighbor exchange message from the neighbor has not been # received recently enough, or that our id was not present in the most # recently received one. - ('outCost', basic.uint8_t), + ("outCost", basic.uint8_t), # The number of aging periods elapsed since a link status message was # last received from this neighbor. The aging period is 16 seconds. - ('age', basic.uint8_t), + ("age", basic.uint8_t), # The 8 byte EUI64 of the neighbor. - ('longId', named.EmberEUI64), + ("longId", named.EmberEUI64), ] @@ -284,22 +284,22 @@ class EmberRouteTableEntry(EzspStruct): _fields = [ # The short id of the destination. A value of 0xFFFF indicates the # entry is unused. - ('destination', basic.uint16_t), + ("destination", basic.uint16_t), # The short id of the next hop to this destination. - ('nextHop', basic.uint16_t), + ("nextHop", basic.uint16_t), # Indicates whether this entry is active (0), being discovered (1)), # unused (3), or validating (4). - ('status', basic.uint8_t), + ("status", basic.uint8_t), # The number of seconds since this route entry was last used to send a # packet. - ('age', basic.uint8_t), + ("age", basic.uint8_t), # Indicates whether this destination is a High RAM Concentrator (2), a # Low RAM Concentrator (1), or not a concentrator (0). - ('concentratorType', basic.uint8_t), + ("concentratorType", basic.uint8_t), # For a High RAM Concentrator, indicates whether a route record is # needed (2), has been sent (1), or is no long needed (0) because a # source routed message from the concentrator has been received. - ('routeRecordState', basic.uint8_t), + ("routeRecordState", basic.uint8_t), ] @@ -310,20 +310,20 @@ class EmberInitialSecurityState(EzspStruct): # A bitmask indicating the security state used to indicate what the # security configuration will be when the device forms or joins the # network. - ('bitmask', named.EmberInitialSecurityBitmask), + ("bitmask", named.EmberInitialSecurityBitmask), # The pre-configured Key data that should be used when forming or # joining the network. The security bitmask must be set with the # HAVE_PRECONFIGURED_KEY bit to indicate that the key contains valid # data. - ('preconfiguredKey', EmberKeyData), + ("preconfiguredKey", EmberKeyData), # The Network Key that should be used by the Trust Center when it forms # the network, or the Network Key currently in use by a joined device. # The security bitmask must be set with HAVE_NETWORK_KEY to indicate # that the key contains valid data. - ('networkKey', EmberKeyData), + ("networkKey", EmberKeyData), # The sequence number associated with the network key. This is only # valid if the HAVE_NETWORK_KEY has been set in the security bitmask. - ('networkKeySequenceNumber', basic.uint8_t), + ("networkKeySequenceNumber", basic.uint8_t), # This is the long address of the trust center on the network that will # be joined. It is usually NOT set prior to joining the network and # instead it is learned during the joining message exchange. This field @@ -331,7 +331,7 @@ class EmberInitialSecurityState(EzspStruct): # EmberInitialSecurityState::bitmask. Most devices should clear that # bit and leave this field alone. This field must be set when using # commissioning mode. - ('preconfiguredTrustCenterEui64', named.EmberEUI64), + ("preconfiguredTrustCenterEui64", named.EmberEUI64), ] @@ -340,9 +340,9 @@ class EmberCurrentSecurityState(EzspStruct): _fields = [ # A bitmask indicating the security options currently in use by a # device joined in the network. - ('bitmask', named.EmberCurrentSecurityBitmask), + ("bitmask", named.EmberCurrentSecurityBitmask), # The IEEE Address of the Trust Center device. - ('trustCenterLongAddress', named.EmberEUI64), + ("trustCenterLongAddress", named.EmberEUI64), ] @@ -351,19 +351,19 @@ class EmberKeyStruct(EzspStruct): _fields = [ # A bitmask indicating the presence of data within the various fields # in the structure. - ('bitmask', named.EmberKeyStructBitmask), + ("bitmask", named.EmberKeyStructBitmask), # The type of the key. - ('type', named.EmberKeyType), + ("type", named.EmberKeyType), # The actual key data. - ('key', EmberKeyData), + ("key", EmberKeyData), # The outgoing frame counter associated with the key. - ('outgoingFrameCounter', basic.uint32_t), + ("outgoingFrameCounter", basic.uint32_t), # The frame counter of the partner device associated with the key. - ('incomingFrameCounter', basic.uint32_t), + ("incomingFrameCounter", basic.uint32_t), # The sequence number associated with the key. - ('sequenceNumber', basic.uint8_t), + ("sequenceNumber", basic.uint8_t), # The IEEE address of the partner device also in possession of the key. - ('partnerEUI64', named.EmberEUI64), + ("partnerEUI64", named.EmberEUI64), ] @@ -371,7 +371,7 @@ class EmberNetworkInitStruct(EzspStruct): # Network Initialization parameters. _fields = [ # Configuration options for network init. - ('bitmask', named.EmberNetworkInitBitmask), + ("bitmask", named.EmberNetworkInitBitmask) ] @@ -379,11 +379,11 @@ class EmberZllSecurityAlgorithmData(EzspStruct): # Data associated with the ZLL security algorithm. _fields = [ # Transaction identifier. - ('transactionId', basic.uint32_t), + ("transactionId", basic.uint32_t), # Response identifier. - ('responseId', basic.uint32_t), + ("responseId", basic.uint32_t), # Bitmask. - ('bitmask', basic.uint16_t), + ("bitmask", basic.uint16_t), ] @@ -391,23 +391,23 @@ class EmberZllNetwork(EzspStruct): # The parameters of a ZLL network. _fields = [ # The parameters of a ZigBee network. - ('zigbeeNetwork', EmberZigbeeNetwork), + ("zigbeeNetwork", EmberZigbeeNetwork), # Data associated with the ZLL security algorithm. - ('securityAlgorithm', EmberZllSecurityAlgorithmData), + ("securityAlgorithm", EmberZllSecurityAlgorithmData), # Associated EUI64. - ('eui64', named.EmberEUI64), + ("eui64", named.EmberEUI64), # The node id. - ('nodeId', named.EmberNodeId), + ("nodeId", named.EmberNodeId), # The ZLL state. - ('state', named.EmberZllState), + ("state", named.EmberZllState), # The node type. - ('nodeType', named.EmberNodeType), + ("nodeType", named.EmberNodeType), # The number of sub devices. - ('numberSubDevices', basic.uint8_t), + ("numberSubDevices", basic.uint8_t), # The total number of group identifiers. - ('totalGroupIdentifiers', basic.uint8_t), + ("totalGroupIdentifiers", basic.uint8_t), # RSSI correction value. - ('rssiCorrection', basic.uint8_t), + ("rssiCorrection", basic.uint8_t), ] @@ -416,14 +416,14 @@ class EmberZllInitialSecurityState(EzspStruct): # used when forming or joining ZLL networks. _fields = [ # Unused bitmask; reserved for future use. - ('bitmask', basic.uint32_t), + ("bitmask", basic.uint32_t), # The key encryption algorithm advertised by the application. - ('keyIndex', named.EmberZllKeyIndex), + ("keyIndex", named.EmberZllKeyIndex), # The encryption key for use by algorithms that require it. - ('encryptionKey', EmberKeyData), + ("encryptionKey", EmberKeyData), # The pre-configured link key used during classical ZigBee # commissioning. - ('preconfiguredKey', EmberKeyData), + ("preconfiguredKey", EmberKeyData), ] @@ -431,17 +431,17 @@ class EmberZllDeviceInfoRecord(EzspStruct): # Information about a specific ZLL Device. _fields = [ # EUI64 associated with the device. - ('ieeeAddress', named.EmberEUI64), + ("ieeeAddress", named.EmberEUI64), # Endpoint id. - ('endpointId', basic.uint8_t), + ("endpointId", basic.uint8_t), # Profile id. - ('profileId', basic.uint16_t), + ("profileId", basic.uint16_t), # Device id. - ('deviceId', basic.uint16_t), + ("deviceId", basic.uint16_t), # Associated version. - ('version', basic.uint8_t), + ("version", basic.uint8_t), # Number of relevant group ids. - ('groupIdCount', basic.uint8_t), + ("groupIdCount", basic.uint8_t), ] @@ -449,19 +449,19 @@ class EmberZllAddressAssignment(EzspStruct): # ZLL address assignment data. _fields = [ # Relevant node id. - ('nodeId', named.EmberNodeId), + ("nodeId", named.EmberNodeId), # Minimum free node id. - ('freeNodeIdMin', named.EmberNodeId), + ("freeNodeIdMin", named.EmberNodeId), # Maximum free node id. - ('freeNodeIdMax', named.EmberNodeId), + ("freeNodeIdMax", named.EmberNodeId), # Minimum group id. - ('groupIdMin', named.EmberMulticastId), + ("groupIdMin", named.EmberMulticastId), # Maximum group id. - ('groupIdMax', named.EmberMulticastId), + ("groupIdMax", named.EmberMulticastId), # Minimum free group id. - ('freeGroupIdMin', named.EmberMulticastId), + ("freeGroupIdMin", named.EmberMulticastId), # Maximum free group id. - ('freeGroupIdMax', named.EmberMulticastId), + ("freeGroupIdMax", named.EmberMulticastId), ] @@ -469,19 +469,19 @@ class EmberTokTypeStackZllData(EzspStruct): # Public API for ZLL stack data token. _fields = [ # Token bitmask. - ('bitmask', basic.uint32_t), + ("bitmask", basic.uint32_t), # Minimum free node id. - ('freeNodeIdMin', basic.uint16_t), + ("freeNodeIdMin", basic.uint16_t), # Maximum free node id. - ('freeNodeIdMax', basic.uint16_t), + ("freeNodeIdMax", basic.uint16_t), # Local minimum group id. - ('myGroupIdMin', basic.uint16_t), + ("myGroupIdMin", basic.uint16_t), # Minimum free group id. - ('freeGroupIdMin', basic.uint16_t), + ("freeGroupIdMin", basic.uint16_t), # Maximum free group id. - ('freeGroupIdMax', basic.uint16_t), + ("freeGroupIdMax", basic.uint16_t), # RSSI correction value. - ('rssiCorrection', basic.uint8_t), + ("rssiCorrection", basic.uint8_t), ] @@ -489,13 +489,13 @@ class EmberTokTypeStackZllSecurity(EzspStruct): # Public API for ZLL stack security token. _fields = [ # Token bitmask. - ('bitmask', basic.uint32_t), + ("bitmask", basic.uint32_t), # Key index. - ('keyIndex', basic.uint8_t), + ("keyIndex", basic.uint8_t), # Encryption key. - ('encryptionKey', basic.fixed_list(16, basic.uint8_t)), + ("encryptionKey", basic.fixed_list(16, basic.uint8_t)), # Preconfigured key. - ('preconfiguredKey', basic.fixed_list(16, basic.uint8_t)), + ("preconfiguredKey", basic.fixed_list(16, basic.uint8_t)), ] @@ -504,9 +504,9 @@ class EmberRf4ceVendorInfo(EzspStruct): _fields = [ # The vendor identifier field shall contain the vendor identifier of # the node. - ('vendorId', basic.uint16_t), + ("vendorId", basic.uint16_t), # The vendor string field shall contain the vendor string of the node. - ('vendorString', basic.fixed_list(7, basic.uint8_t)), + ("vendorString", basic.fixed_list(7, basic.uint8_t)), ] @@ -515,16 +515,16 @@ class EmberRf4ceApplicationInfo(EzspStruct): _fields = [ # The application capabilities field shall contain information relating # to the capabilities of the application of the node. - ('capabilities', named.EmberRf4ceApplicationCapabilities), + ("capabilities", named.EmberRf4ceApplicationCapabilities), # The user string field shall contain the user specified identification # string. - ('userString', basic.fixed_list(15, basic.uint8_t)), + ("userString", basic.fixed_list(15, basic.uint8_t)), # The device type list field shall contain the list of device types # supported by the node. - ('deviceTypeList', basic.fixed_list(3, basic.uint8_t)), + ("deviceTypeList", basic.fixed_list(3, basic.uint8_t)), # The profile ID list field shall contain the list of profile # identifiers disclosed as supported by the node. - ('profileIdList', basic.fixed_list(7, basic.uint8_t)), + ("profileIdList", basic.fixed_list(7, basic.uint8_t)), ] @@ -532,31 +532,31 @@ class EmberRf4cePairingTableEntry(EzspStruct): # The internal representation of an RF4CE pairing table entry. _fields = [ # The link key to be used to secure this pairing link. - ('securityLinkKey', EmberKeyData), + ("securityLinkKey", EmberKeyData), # The IEEE address of the destination device. - ('destLongId', named.EmberEUI64), + ("destLongId", named.EmberEUI64), # The frame counter last received from the recipient node. - ('frameCounter', basic.uint32_t), + ("frameCounter", basic.uint32_t), # The network address to be assumed by the source device. - ('sourceNodeId', named.EmberNodeId), + ("sourceNodeId", named.EmberNodeId), # The PAN identifier of the destination device. - ('destPanId', named.EmberPanId), + ("destPanId", named.EmberPanId), # The network address of the destination device. - ('destNodeId', named.EmberNodeId), + ("destNodeId", named.EmberNodeId), # The vendor ID of the destination device. - ('destVendorId', basic.uint16_t), + ("destVendorId", basic.uint16_t), # The list of profiles supported by the destination device. - ('destProfileIdList', basic.fixed_list(7, basic.uint8_t)), + ("destProfileIdList", basic.fixed_list(7, basic.uint8_t)), # The length of the list of supported profiles. - ('destProfileIdListLength', basic.uint8_t), + ("destProfileIdListLength", basic.uint8_t), # Info byte. - ('info', basic.uint8_t), + ("info", basic.uint8_t), # The expected channel of the destination device. - ('channel', basic.uint8_t), + ("channel", basic.uint8_t), # The node capabilities of the recipient node. - ('capabilities', basic.uint8_t), + ("capabilities", basic.uint8_t), # Last MAC sequence number seen on this pairing link. - ('lastSeqn', basic.uint8_t), + ("lastSeqn", basic.uint8_t), ] @@ -564,13 +564,13 @@ class EmberGpAddress(EzspStruct): # A GP address structure. _fields = [ # The GPD's EUI64. - ('gpdIeeeAddress', named.EmberEUI64), + ("gpdIeeeAddress", named.EmberEUI64), # The GPD's source ID. - ('sourceId', basic.uint32_t), + ("sourceId", basic.uint32_t), # The GPD Application ID. - ('applicationId', basic.uint8_t), + ("applicationId", basic.uint8_t), # The GPD endpoint. - ('endpoint', basic.uint8_t), + ("endpoint", basic.uint8_t), ] @@ -578,9 +578,9 @@ class EmberGpSinkListEntry(EzspStruct): # A sink list entry _fields = [ # The sink list type. - ('type', basic.uint8_t), + ("type", basic.uint8_t), # The EUI64 of the target sink. - ('sinkEUI', named.EmberEUI64), + ("sinkEUI", named.EmberEUI64), # The short address of the target sink. - ('sinkNodeId', named.EmberNodeId), + ("sinkNodeId", named.EmberNodeId), ] diff --git a/bellows/uart.py b/bellows/uart.py index 5e980fd7..5d4c14fd 100644 --- a/bellows/uart.py +++ b/bellows/uart.py @@ -14,12 +14,12 @@ class Gateway(asyncio.Protocol): - FLAG = b'\x7E' # Marks end of frame - ESCAPE = b'\x7D' - XON = b'\x11' # Resume transmission - XOFF = b'\x13' # Stop transmission - SUBSTITUTE = b'\x18' - CANCEL = b'\x1A' # Terminates a frame in progress + FLAG = b"\x7E" # Marks end of frame + ESCAPE = b"\x7D" + XON = b"\x11" # Resume transmission + XOFF = b"\x13" # Stop transmission + SUBSTITUTE = b"\x18" + CANCEL = b"\x1A" # Terminates a frame in progress STUFF = 0x20 RANDOMIZE_START = 0x42 RANDOMIZE_SEQ = 0xB8 @@ -32,7 +32,7 @@ class Terminator: def __init__(self, application, connected_future=None, connection_done_future=None): self._send_seq = 0 self._rec_seq = 0 - self._buffer = b'' + self._buffer = b"" self._application = application self._reset_future = None self._connected_future = connected_future @@ -54,11 +54,11 @@ def data_received(self, data): # so far are discarded. In the case of a Substitute Byte, subsequent # bytes will also be discarded until the next Flag Byte. if self.CANCEL in data: - self._buffer = b'' - data = data[data.rfind(self.CANCEL) + 1:] + self._buffer = b"" + data = data[data.rfind(self.CANCEL) + 1 :] if self.SUBSTITUTE in data: - self._buffer = b'' - data = data[data.find(self.FLAG) + 1:] + self._buffer = b"" + data = data[data.find(self.FLAG) + 1 :] self._buffer += data while self._buffer: @@ -71,12 +71,17 @@ def _extract_frame(self, data): """Extract a frame from the data buffer""" if self.FLAG in data: place = data.find(self.FLAG) - frame = self._unstuff(data[:place + 1]) - rest = data[place + 1:] - crc = binascii.crc_hqx(frame[:-3], 0xffff) + frame = self._unstuff(data[: place + 1]) + rest = data[place + 1 :] + crc = binascii.crc_hqx(frame[:-3], 0xFFFF) crc = bytes([crc >> 8, crc % 256]) if crc != frame[-3:-1]: - LOGGER.error("CRC error in frame %s (%s != %s)", binascii.hexlify(frame), binascii.hexlify(frame[-3:-1]), binascii.hexlify(crc)) + LOGGER.error( + "CRC error in frame %s (%s != %s)", + binascii.hexlify(frame), + binascii.hexlify(frame[-3:-1]), + binascii.hexlify(crc), + ) self.write(self._nak_frame()) # Make sure that we also handle the next frame if it is already received return self._extract_frame(rest) @@ -130,8 +135,12 @@ def rstack_frame_received(self, data): self._rec_seq = 0 code, version = self._get_error_code(data) - LOGGER.debug("RSTACK Version: %d Reason: %s frame: %s", version, - code.name, binascii.hexlify(data)) + LOGGER.debug( + "RSTACK Version: %d Reason: %s frame: %s", + version, + code.name, + binascii.hexlify(data), + ) # not a reset we've requested. Signal application reset if code is not t.NcpResetCode.RESET_SOFTWARE: self._application.enter_failed_state(code) @@ -157,8 +166,12 @@ def _get_error_code(data): def error_frame_received(self, data): """Error frame receive handler.""" error_code, version = self._get_error_code(data) - LOGGER.debug("Error code: %s, Version: %d, frame: %s", error_code.name, - version, binascii.hexlify(data)) + LOGGER.debug( + "Error code: %s, Version: %d, frame: %s", + error_code.name, + version, + binascii.hexlify(data), + ) self._application.enter_failed_state(error_code) def write(self, data): @@ -189,13 +202,14 @@ async def reset(self): """Send a reset frame and init internal state.""" LOGGER.debug("Resetting ASH") if self._reset_future is not None: - LOGGER.error(("received new reset request while an existing " - "one is in progress")) + LOGGER.error( + ("received new reset request while an existing " "one is in progress") + ) return await self._reset_future self._send_seq = 0 self._rec_seq = 0 - self._buffer = b'' + self._buffer = b"" while not self._sendq.empty(): self._sendq.get_nowait() if self._pending[1]: @@ -252,21 +266,21 @@ def _ack_frame(self): """Construct a acknowledgement frame""" assert 0 <= self._rec_seq < 8 control = bytes([0b10000000 | (self._rec_seq & 0b00000111)]) - return self._frame(control, b'') + return self._frame(control, b"") def _nak_frame(self): """Construct a negative acknowledgement frame""" assert 0 <= self._rec_seq < 8 control = bytes([0b10100000 | (self._rec_seq & 0b00000111)]) - return self._frame(control, b'') + return self._frame(control, b"") def _rst_frame(self): """Construct a reset frame""" - return self.CANCEL + self._frame(b'\xC0', b'') + return self.CANCEL + self._frame(b"\xC0", b"") def _frame(self, control, data): """Construct a frame""" - crc = binascii.crc_hqx(control + data, 0xffff) + crc = binascii.crc_hqx(control + data, 0xFFFF) crc = bytes([crc >> 8, crc % 256]) return self._stuff(control + data + crc) + self.FLAG @@ -276,7 +290,7 @@ def _randomize(self, s): Used only in data frames """ rand = self.RANDOMIZE_START - out = b'' + out = b"" for c in s: out += bytes([c ^ rand]) if rand % 2: @@ -287,7 +301,7 @@ def _randomize(self, s): def _stuff(self, s): """Byte stuff (escape) a string for transmission""" - out = b'' + out = b"" for c in s: if c in self.RESERVED: out += self.ESCAPE + bytes([c ^ self.STUFF]) @@ -297,7 +311,7 @@ def _stuff(self, s): def _unstuff(self, s): """Unstuff (unescape) a string after receipt""" - out = b'' + out = b"" escaped = False for c in s: if escaped: @@ -338,7 +352,9 @@ async def connect(port, baudrate, application, use_thread=True): application = ThreadsafeProxy(application, asyncio.get_event_loop()) thread = EventLoopThread() await thread.start() - protocol, connection_done = await thread.run_coroutine_threadsafe(_connect(port, baudrate, application)) + protocol, connection_done = await thread.run_coroutine_threadsafe( + _connect(port, baudrate, application) + ) connection_done.add_done_callback(lambda _: thread.force_stop()) else: protocol, _ = await _connect(port, baudrate, application) diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index 9f6552bf..5c822355 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -17,6 +17,8 @@ import bellows.multicast APS_ACK_TIMEOUT = 120 +EZSP_DEFAULT_RADIUS = 0 +EZSP_MULTICAST_NON_MEMBER_RADIUS = 3 MAX_WATCHDOG_FAILURES = 4 RESET_ATTEMPT_BACKOFF_TIME = 5 WATCHDOG_WAKE_PERIOD = 10 @@ -64,10 +66,11 @@ async def initialize(self): await self._cfg(c.CONFIG_SECURITY_LEVEL, 5) await self._cfg(c.CONFIG_SUPPORTED_NETWORKS, 1) zdo = ( - t.EmberZdoConfigurationFlags.APP_RECEIVES_SUPPORTED_ZDO_REQUESTS | - t.EmberZdoConfigurationFlags.APP_HANDLES_UNSUPPORTED_ZDO_REQUESTS + t.EmberZdoConfigurationFlags.APP_RECEIVES_SUPPORTED_ZDO_REQUESTS + | t.EmberZdoConfigurationFlags.APP_HANDLES_UNSUPPORTED_ZDO_REQUESTS ) await self._cfg(c.CONFIG_APPLICATION_ZDO_FLAGS, zdo) + await self._cfg(c.CONFIG_PAN_ID_CONFLICT_REPORT_THRESHOLD, 2) await self._cfg(c.CONFIG_TRUST_CENTER_ADDRESS_CACHE_SIZE, 2) await self._cfg(c.CONFIG_ADDRESS_TABLE_SIZE, 16) await self._cfg(c.CONFIG_SOURCE_ROUTE_TABLE_SIZE, 8) @@ -75,14 +78,17 @@ async def initialize(self): await self._cfg(c.CONFIG_INDIRECT_TRANSMISSION_TIMEOUT, 7680) await self._cfg(c.CONFIG_KEY_TABLE_SIZE, 1) await self._cfg(c.CONFIG_TRANSIENT_KEY_TIMEOUT_S, 180, True) - 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, - self.multicast.TABLE_SIZE) - await self._cfg(c.CONFIG_PACKET_BUFFER_COUNT, 0xff) + if self._ezsp.ezsp_version >= 7: + await self._cfg(c.CONFIG_END_DEVICE_POLL_TIMEOUT, 8) + 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, self.multicast.TABLE_SIZE) + await self._cfg(c.CONFIG_PACKET_BUFFER_COUNT, 0xFF) status, count = await e.getConfigurationValue( - c.CONFIG_APS_UNICAST_MESSAGE_COUNT) + c.CONFIG_APS_UNICAST_MESSAGE_COUNT + ) assert status == t.EmberStatus.SUCCESS self._in_flight_msg = asyncio.Semaphore(count) LOGGER.debug("APS_UNICAST_MESSAGE_COUNT is set to %s", count) @@ -91,17 +97,25 @@ async def initialize(self): output_clusters=[zigpy.zcl.clusters.security.IasZone.cluster_id] ) - async def add_endpoint(self, endpoint=1, - profile_id=zigpy.profiles.zha.PROFILE_ID, - device_id=0xbeef, - app_flags=0x00, - input_clusters=[], - output_clusters=[]): + async def add_endpoint( + self, + endpoint=1, + profile_id=zigpy.profiles.zha.PROFILE_ID, + device_id=0xBEEF, + app_flags=0x00, + input_clusters=[], + output_clusters=[], + ): """Add endpoint.""" res = await self._ezsp.addEndpoint( - endpoint, profile_id, device_id, app_flags, - len(input_clusters), len(output_clusters), - input_clusters, output_clusters + endpoint, + profile_id, + device_id, + app_flags, + len(input_clusters), + len(output_clusters), + input_clusters, + output_clusters, ) LOGGER.debug("Ezsp adding endpoint: %s", res) @@ -156,7 +170,7 @@ async def form_network(self, channel=15, pan_id=None, extended_pan_id=None): channel = t.uint8_t(channel) if pan_id is None: - pan_id = t.uint16_t.from_bytes(os.urandom(2), 'little') + pan_id = t.uint16_t.from_bytes(os.urandom(2), "little") pan_id = t.uint16_t(pan_id) if extended_pan_id is None: @@ -188,8 +202,7 @@ async def _policy(self): """Set up the policies for what the NCP should do""" e = self._ezsp v = await e.setPolicy( - t.EzspPolicyId.TC_KEY_REQUEST_POLICY, - t.EzspDecisionId.DENY_TC_KEY_REQUESTS, + t.EzspPolicyId.TC_KEY_REQUEST_POLICY, t.EzspDecisionId.DENY_TC_KEY_REQUESTS ) assert v[0] == t.EmberStatus.SUCCESS # TODO: Better check v = await e.setPolicy( @@ -210,24 +223,36 @@ async def force_remove(self, dev): def ezsp_callback_handler(self, frame_name, args): LOGGER.debug("Received %s frame with %s", frame_name, args) - if frame_name == 'incomingMessageHandler': + if frame_name == "incomingMessageHandler": self._handle_frame(*args) - elif frame_name == 'messageSentHandler': + elif frame_name == "messageSentHandler": if args[4] != t.EmberStatus.SUCCESS: self._handle_frame_failure(*args) else: self._handle_frame_sent(*args) - elif frame_name == 'trustCenterJoinHandler': + elif frame_name == "trustCenterJoinHandler": if args[2] == t.EmberDeviceUpdate.DEVICE_LEFT: self.handle_leave(args[0], args[1]) else: self.handle_join(args[0], args[1], args[4]) - elif frame_name == '_reset_controller_application': + elif frame_name == "_reset_controller_application": self._handle_reset_request(*args) - def _handle_frame(self, message_type, aps_frame, lqi, rssi, sender, binding_index, address_index, message): - if aps_frame.clusterId == zdo_t.ZDOCmd.Device_annce and \ - aps_frame.destinationEndpoint == 0: + def _handle_frame( + self, + message_type, + aps_frame, + lqi, + rssi, + sender, + binding_index, + address_index, + message, + ): + if ( + aps_frame.clusterId == zdo_t.ZDOCmd.Device_annce + and aps_frame.destinationEndpoint == 0 + ): nwk, rest = t.uint16_t.deserialize(message[1:]) ieee, _ = t.EmberEUI64.deserialize(rest) LOGGER.info("ZDO Device announce: 0x%04x, %s", nwk, ieee) @@ -239,27 +264,54 @@ def _handle_frame(self, message_type, aps_frame, lqi, rssi, sender, binding_inde return device.radio_details(lqi, rssi) - self.handle_message(device, aps_frame.profileId, aps_frame.clusterId, aps_frame.sourceEndpoint, aps_frame.destinationEndpoint, message) + self.handle_message( + device, + aps_frame.profileId, + aps_frame.clusterId, + aps_frame.sourceEndpoint, + aps_frame.destinationEndpoint, + message, + ) - def _handle_frame_failure(self, message_type, destination, aps_frame, message_tag, status, message): + def _handle_frame_failure( + self, message_type, destination, aps_frame, message_tag, status, message + ): try: request = self._pending[message_tag] - request.result.set_result((status, 'message send failure')) + request.result.set_result((status, "message send failure")) except KeyError: - LOGGER.debug("Unexpected message send failure for message tag %s", message_tag) - except asyncio.futures.InvalidStateError as exc: - LOGGER.debug("Invalid state on future for message tag %s - probably duplicate response: %s", - message_tag, exc) - - def _handle_frame_sent(self, message_type, destination, aps_frame, message_tag, status, message): + LOGGER.debug( + "Unexpected message send failure for message tag %s", message_tag + ) + except asyncio.InvalidStateError as exc: + LOGGER.debug( + ( + "Invalid state on future for message tag %s " + "- probably duplicate response: %s" + ), + message_tag, + exc, + ) + + def _handle_frame_sent( + self, message_type, destination, aps_frame, message_tag, status, message + ): try: request = self._pending[message_tag] - request.result.set_result((t.EmberStatus.SUCCESS, "message sent successfully")) + request.result.set_result( + (t.EmberStatus.SUCCESS, "message sent successfully") + ) except KeyError: LOGGER.debug("Unexpected message send notification tag: %s", message_tag) - except asyncio.futures.InvalidStateError as exc: - LOGGER.debug("Invalid state on future for message tag %s - probably duplicate response: %s", - message_tag, exc) + except asyncio.InvalidStateError as exc: + LOGGER.debug( + ( + "Invalid state on future for message tag %s " + "- probably duplicate response: %s" + ), + message_tag, + exc, + ) def _handle_reset_request(self, error): """Reinitialize application controller.""" @@ -279,8 +331,7 @@ async def _reset_controller_loop(self): await self._reset_controller() break except (asyncio.TimeoutError, SerialException) as exc: - LOGGER.debug( - "ControllerApplication reset unsuccessful: %s", str(exc)) + LOGGER.debug("ControllerApplication reset unsuccessful: %s", str(exc)) await asyncio.sleep(RESET_ATTEMPT_BACKOFF_TIME) self._reset_task = None @@ -293,8 +344,72 @@ async def _reset_controller(self): await self._ezsp.reconnect() await self.startup() - async def request(self, device, profile, cluster, src_ep, dst_ep, sequence, data, - expect_reply=True, use_ieee=False): + async def mrequest( + self, + group_id, + profile, + cluster, + src_ep, + sequence, + data, + *, + hops=EZSP_DEFAULT_RADIUS, + non_member_radius=EZSP_MULTICAST_NON_MEMBER_RADIUS + ): + """Submit and send data out as a multicast transmission. + + :param group_id: destination multicast address + :param profile: Zigbee Profile ID to use for outgoing message + :param cluster: cluster id where the message is being sent + :param src_ep: source endpoint id + :param sequence: transaction sequence number of the message + :param data: Zigbee message payload + :param hops: the message will be delivered to all nodes within this number of + hops of the sender. A value of zero is converted to MAX_HOPS + :param non_member_radius: the number of hops that the message will be forwarded + by devices that are not members of the group. A value + of 7 or greater is treated as infinite + :returns: return a tuple of a status and an error_message. Original requestor + has more context to provide a more meaningful error message + """ + if not self.is_controller_running: + raise ControllerError("ApplicationController is not running") + + aps_frame = t.EmberApsFrame() + aps_frame.profileId = t.uint16_t(profile) + aps_frame.clusterId = t.uint16_t(cluster) + aps_frame.sourceEndpoint = t.uint8_t(src_ep) + aps_frame.destinationEndpoint = t.uint8_t(src_ep) + aps_frame.options = t.EmberApsOption( + t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY + ) + aps_frame.groupId = t.uint16_t(group_id) + aps_frame.sequence = t.uint8_t(sequence) + message_tag = self.get_sequence() + + with self._pending.new(message_tag) as req: + async with self._in_flight_msg: + res = await self._ezsp.sendMulticast( + aps_frame, hops, non_member_radius, message_tag, data + ) + if res[0] != t.EmberStatus.SUCCESS: + return res[0], "EZSP sendMulticast failure: %s" % (res[0],) + + res = await asyncio.wait_for(req.result, APS_ACK_TIMEOUT) + return res + + async def request( + self, + device, + profile, + cluster, + src_ep, + dst_ep, + sequence, + data, + expect_reply=True, + use_ieee=False, + ): """Submit and send data out as an unicast transmission. :param device: destination device @@ -318,25 +433,27 @@ async def request(self, device, profile, cluster, src_ep, dst_ep, sequence, data aps_frame.sourceEndpoint = t.uint8_t(src_ep) aps_frame.destinationEndpoint = t.uint8_t(dst_ep) aps_frame.options = t.EmberApsOption( - t.EmberApsOption.APS_OPTION_RETRY | - t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY + t.EmberApsOption.APS_OPTION_RETRY + | t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY ) aps_frame.groupId = t.uint16_t(0) aps_frame.sequence = t.uint8_t(sequence) message_tag = self.get_sequence() if use_ieee: - LOGGER.warning(("EUI64 addressing is not currently supported, " - "reverting to NWK")) + LOGGER.warning( + ("EUI64 addressing is not currently supported, " "reverting to NWK") + ) if expect_reply and device.node_desc.is_end_device in (True, None): LOGGER.debug("Extending timeout for %s/0x%04x", device.ieee, device.nwk) await self._ezsp.setExtendedTimeout(device.ieee, True) with self._pending.new(message_tag) as req: async with self._in_flight_msg: - res = await self._ezsp.sendUnicast(self.direct, device.nwk, aps_frame, - message_tag, data) + res = await self._ezsp.sendUnicast( + self.direct, device.nwk, aps_frame, message_tag, data + ) if res[0] != t.EmberStatus.SUCCESS: - return res[0], "EZSP sendUnicast failure: %s" % (res[0], ) + return res[0], "EZSP sendUnicast failure: %s" % (res[0],) res = await asyncio.wait_for(req.result, APS_ACK_TIMEOUT) return res @@ -353,7 +470,10 @@ async def permit_with_key(self, node, code, time_s=60): if key is None: raise Exception("Invalid install code") - v = await self._ezsp.addTransientLinkKey(node, key) + link_key = t.EmberKeyData() + link_key.contents = key + v = await self._ezsp.addTransientLinkKey(node, link_key) + if v[0] != t.EmberStatus.SUCCESS: raise Exception("Failed to set link key") @@ -362,13 +482,24 @@ async def permit_with_key(self, node, code, time_s=60): t.EzspDecisionId.GENERATE_NEW_TC_LINK_KEY, ) if v[0] != t.EmberStatus.SUCCESS: - raise Exception("Failed to change policy to allow generation of new trust center keys") - - return self._ezsp.permitJoining(time_s, True) - - async def broadcast(self, profile, cluster, src_ep, dst_ep, grpid, radius, - sequence, data, - broadcast_address=BroadcastAddress.RX_ON_WHEN_IDLE): + raise Exception( + "Failed to change policy to allow generation of new trust center keys" + ) + + return await self.permit(time_s) + + async def broadcast( + self, + profile, + cluster, + src_ep, + dst_ep, + grpid, + radius, + sequence, + data, + broadcast_address=BroadcastAddress.RX_ON_WHEN_IDLE, + ): """Submit and send data out as an unicast transmission. :param profile: Zigbee Profile ID to use for outgoing message @@ -392,24 +523,21 @@ async def broadcast(self, profile, cluster, src_ep, dst_ep, grpid, radius, aps_frame.clusterId = t.uint16_t(cluster) aps_frame.sourceEndpoint = t.uint8_t(src_ep) aps_frame.destinationEndpoint = t.uint8_t(dst_ep) - aps_frame.options = t.EmberApsOption( - t.EmberApsOption.APS_OPTION_NONE - ) + aps_frame.options = t.EmberApsOption(t.EmberApsOption.APS_OPTION_NONE) aps_frame.groupId = t.uint16_t(grpid) aps_frame.sequence = t.uint8_t(sequence) message_tag = self.get_sequence() with self._pending.new(message_tag) as req: async with self._in_flight_msg: - res = await self._ezsp.sendBroadcast(broadcast_address, - aps_frame, radius, - message_tag, data) + res = await self._ezsp.sendBroadcast( + broadcast_address, aps_frame, radius, message_tag, data + ) if res[0] != t.EmberStatus.SUCCESS: return res[0], "broadcast send failure" # Wait for messageSentHandler message - res = await asyncio.wait_for(req.result, - timeout=APS_ACK_TIMEOUT) + res = await asyncio.wait_for(req.result, timeout=APS_ACK_TIMEOUT) return res async def _watchdog(self): @@ -419,8 +547,9 @@ async def _watchdog(self): await asyncio.sleep(WATCHDOG_WAKE_PERIOD) while True: try: - await asyncio.wait_for(self.controller_event.wait(), - timeout=WATCHDOG_WAKE_PERIOD * 2) + await asyncio.wait_for( + self.controller_event.wait(), timeout=WATCHDOG_WAKE_PERIOD * 2 + ) await self._ezsp.nop() failures = 0 except (asyncio.TimeoutError, EzspError) as exc: @@ -431,15 +560,15 @@ async def _watchdog(self): await asyncio.sleep(WATCHDOG_WAKE_PERIOD) self._handle_reset_request( - "Watchdog timeout. Heartbeat timeouts: {}".format(failures)) + "Watchdog timeout. Heartbeat timeouts: {}".format(failures) + ) class EZSPCoordinator(CustomDevice): """Zigpy Device representing Coordinator.""" class EZSPEndpoint(CustomEndpoint): - async def add_to_group(self, grp_id: int, - name: str = None) -> t.EmberStatus: + async def add_to_group(self, grp_id: int, name: str = None) -> t.EmberStatus: if grp_id in self.member_of: return t.EmberStatus.SUCCESS @@ -467,20 +596,18 @@ async def remove_from_group(self, grp_id: int) -> t.EmberStatus: return status signature = { - 'endpoints': { + "endpoints": { 1: { - 'profile_id': 0x0104, - 'device_type': 0xbeef, - 'input_clusters': [], - 'output_clusters': [zigpy.zcl.clusters.security.IasZone.cluster_id] - }, - }, + "profile_id": 0x0104, + "device_type": 0xBEEF, + "input_clusters": [], + "output_clusters": [zigpy.zcl.clusters.security.IasZone.cluster_id], + } + } } replacement = { - 'manufacturer': 'Silicon Labs', - 'model': 'EZSP', - 'endpoints': { - 1: (EZSPEndpoint, {}) - } + "manufacturer": "Silicon Labs", + "model": "EZSP", + "endpoints": {1: (EZSPEndpoint, {})}, } diff --git a/bellows/zigbee/util.py b/bellows/zigbee/util.py index 6227901e..ebf0144f 100644 --- a/bellows/zigbee/util.py +++ b/bellows/zigbee/util.py @@ -8,29 +8,25 @@ def zha_security(controller=False): empty_key_data.contents = t.fixed_list(16, t.uint8_t)([t.uint8_t(0)] * 16) zha_key = t.EmberKeyData() zha_key.contents = t.fixed_list(16, t.uint8_t)( - [t.uint8_t(c) for c in b'ZigBeeAlliance09'] + [t.uint8_t(c) for c in b"ZigBeeAlliance09"] ) isc = t.EmberInitialSecurityState() isc.bitmask = t.uint16_t( - t.EmberInitialSecurityBitmask.HAVE_PRECONFIGURED_KEY | - t.EmberInitialSecurityBitmask.REQUIRE_ENCRYPTED_KEY + t.EmberInitialSecurityBitmask.HAVE_PRECONFIGURED_KEY + | t.EmberInitialSecurityBitmask.REQUIRE_ENCRYPTED_KEY ) isc.preconfiguredKey = zha_key isc.networkKey = empty_key_data isc.networkKeySequenceNumber = t.uint8_t(0) - isc.preconfiguredTrustCenterEui64 = t.EmberEUI64( - [t.uint8_t(0)] * 8 - ) + isc.preconfiguredTrustCenterEui64 = t.EmberEUI64([t.uint8_t(0)] * 8) if controller: isc.bitmask |= ( - t.EmberInitialSecurityBitmask.TRUST_CENTER_GLOBAL_LINK_KEY | - t.EmberInitialSecurityBitmask.HAVE_NETWORK_KEY + t.EmberInitialSecurityBitmask.TRUST_CENTER_GLOBAL_LINK_KEY + | t.EmberInitialSecurityBitmask.HAVE_NETWORK_KEY ) isc.bitmask = t.uint16_t(isc.bitmask) - random_key = t.fixed_list(16, t.uint8_t)( - [t.uint8_t(x) for x in os.urandom(16)] - ) + random_key = t.fixed_list(16, t.uint8_t)([t.uint8_t(x) for x in os.urandom(16)]) isc.networkKey = random_key return isc diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..3d9f9e1a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,31 @@ +[flake8] +exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build +# To work with Black +max-line-length = 88 +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring +ignore = + W503, + E203, + D202 + +[isort] +# https://github.com/timothycrosley/isort +# https://github.com/timothycrosley/isort/wiki/isort-Settings +# splits long import on multiple lines indented by 4 spaces +multi_line_output = 3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 +indent = " " +# by default isort don't check module indexes +not_skip = __init__.py +# will group `import x` and `from x import` of the same module. +force_sort_within_sections = true +sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER +default_section = THIRDPARTY +known_first_party = homeassistant,tests +forced_separate = tests +combine_as_imports = true diff --git a/setup.py b/setup.py index c06c7766..142bcdb9 100644 --- a/setup.py +++ b/setup.py @@ -12,21 +12,15 @@ author="Russell Cloran", author_email="rcloran@gmail.com", license="GPL-3.0", - packages=find_packages(exclude=['*.tests']), - entry_points={ - 'console_scripts': ['bellows=bellows.cli.main:main'], - }, + packages=find_packages(exclude=["*.tests"]), + entry_points={"console_scripts": ["bellows=bellows.cli.main:main"]}, install_requires=[ - 'Click', - 'click-log==0.2.0', - 'pure_pcapy3==1.0.1', - 'pyserial-asyncio', - 'zigpy-homeassistant>=0.9.0', - ], - dependency_links=[ - 'https://github.com/rcloran/pure-pcapy-3/archive/e289c7d7566306dc02d8f4eb30c0358b41f40f26.zip#egg=pure_pcapy3-1.0.1', - ], - tests_require=[ - 'pytest', + "Click", + "click-log==0.2.0", + "pure_pcapy3==1.0.1", + "pyserial-asyncio", + "zigpy-homeassistant>=0.9.0", ], + dependency_links=["https://codeload.github.com/rcloran/pure-pcapy-3/zip/master"], + tests_require=["pytest"], ) diff --git a/tests/test_application.py b/tests/test_application.py index dd441562..d3139da5 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -15,7 +15,7 @@ def app(monkeypatch): ezsp = mock.MagicMock() type(ezsp).is_ezsp_running = mock.PropertyMock(return_value=True) ctrl = bellows.zigbee.application.ControllerApplication(ezsp) - monkeypatch.setattr(bellows.zigbee.application, 'APS_ACK_TIMEOUT', 0.1) + monkeypatch.setattr(bellows.zigbee.application, "APS_ACK_TIMEOUT", 0.1) ctrl._ctrl_event.set() ctrl._in_flight_msg = asyncio.Semaphore() ctrl.handle_message = mock.MagicMock() @@ -45,7 +45,7 @@ async def mock_coro(*args, **kwargs): return mock.Mock(wraps=mock_coro) -def _test_startup(app, nwk_type, ieee, auto_form=False, init=0): +def _test_startup(app, nwk_type, ieee, auto_form=False, init=0, ezsp_version=4): async def mockezsp(*args, **kwargs): return [0, nwk_type] @@ -53,6 +53,7 @@ async def mockinit(*args, **kwargs): return [init] app._in_flight_msg = None + type(app._ezsp).ezsp_version = mock.PropertyMock(return_value=ezsp_version) app._ezsp._command = mockezsp app._ezsp.addEndpoint = mockezsp app._ezsp.setConfigurationValue = mockezsp @@ -61,13 +62,15 @@ async def mockinit(*args, **kwargs): app._ezsp.setPolicy = mockezsp app._ezsp.getNodeId = mockezsp app._ezsp.getEui64.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=[ieee])) + mock.MagicMock(return_value=[ieee]) + ) app._ezsp.leaveNetwork = mockezsp app.form_network = mock.MagicMock(side_effect=asyncio.coroutine(mock.MagicMock())) app._ezsp.reset.side_effect = asyncio.coroutine(mock.MagicMock()) app._ezsp.version.side_effect = asyncio.coroutine(mock.MagicMock()) app._ezsp.getConfigurationValue.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=(0, 1))) + mock.MagicMock(return_value=(0, 1)) + ) app.multicast._initialize = mockezsp loop = asyncio.get_event_loop() @@ -78,6 +81,10 @@ def test_startup(app, ieee): return _test_startup(app, t.EmberNodeType.COORDINATOR, ieee) +def test_startup_ezsp_ver7(app, ieee): + return _test_startup(app, t.EmberNodeType.COORDINATOR, ieee, ezsp_version=7) + + def test_startup_no_status(app, ieee): with pytest.raises(Exception): return _test_startup(app, None, ieee, init=1) @@ -107,14 +114,13 @@ def test_form_network(app): loop.run_until_complete(app.form_network()) -def _frame_handler(app, aps, ieee, src_ep, cluster=0, data=b'\x01\x00\x00'): +def _frame_handler(app, aps, ieee, src_ep, cluster=0, data=b"\x01\x00\x00"): if ieee not in app.devices: app.add_device(ieee, 3) aps.sourceEndpoint = src_ep aps.clusterId = cluster app.ezsp_callback_handler( - 'incomingMessageHandler', - [None, aps, 1, 2, 3, 4, 5, data] + "incomingMessageHandler", [None, aps, 1, 2, 3, 4, 5, data] ) @@ -128,7 +134,7 @@ def test_frame_handler_unknown_device(app, aps, ieee): def test_frame_handler(app, aps, ieee): app.handle_join = mock.MagicMock() - data = b'\x18\x19\x22\xaa\x55' + data = b"\x18\x19\x22\xaa\x55" _frame_handler(app, aps, ieee, 0, data=data) assert app.handle_message.call_count == 1 assert app.handle_message.call_args[0][5] is data @@ -138,8 +144,8 @@ def test_frame_handler(app, aps, ieee): def test_frame_handler_zdo_annce(app, aps, ieee): aps.destinationEndpoint = 0 app.handle_join = mock.MagicMock() - nwk = t.uint16_t(0xaa55) - data = b'\x18' + nwk.serialize() + ieee.serialize() + nwk = t.uint16_t(0xAA55) + data = b"\x18" + nwk.serialize() + ieee.serialize() _frame_handler(app, aps, ieee, 0, cluster=0x0013, data=data) assert app.handle_message.call_count == 1 assert app.handle_message.call_args[0][5] is data @@ -151,8 +157,7 @@ def test_frame_handler_zdo_annce(app, aps, ieee): def test_send_failure(app, aps, ieee): req = app._pending[254] = mock.MagicMock() app.ezsp_callback_handler( - 'messageSentHandler', - [None, 0xbeed, aps, 254, mock.sentinel.status, b''] + "messageSentHandler", [None, 0xBEED, aps, 254, mock.sentinel.status, b""] ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 @@ -161,27 +166,22 @@ def test_send_failure(app, aps, ieee): def test_dup_send_failure(app, aps, ieee): req = app._pending[254] = mock.MagicMock() - req.result.set_result.side_effect = asyncio.futures.InvalidStateError() + req.result.set_result.side_effect = asyncio.InvalidStateError() app.ezsp_callback_handler( - 'messageSentHandler', - [None, 0xbeed, aps, 254, mock.sentinel.status, b''] + "messageSentHandler", [None, 0xBEED, aps, 254, mock.sentinel.status, b""] ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 def test_send_failure_unexpected(app, aps, ieee): - app.ezsp_callback_handler( - 'messageSentHandler', - [None, 0xbeed, aps, 257, 1, b''] - ) + app.ezsp_callback_handler("messageSentHandler", [None, 0xBEED, aps, 257, 1, b""]) def test_send_success(app, aps, ieee): req = app._pending[253] = mock.MagicMock() app.ezsp_callback_handler( - 'messageSentHandler', - [None, 0xbeed, aps, 253, mock.sentinel.success, b''] + "messageSentHandler", [None, 0xBEED, aps, 253, mock.sentinel.success, b""] ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 @@ -189,37 +189,27 @@ def test_send_success(app, aps, ieee): def test_unexpected_send_success(app, aps, ieee): - app.ezsp_callback_handler( - 'messageSentHandler', - [None, 0xbeed, aps, 253, 0, b''] - ) + app.ezsp_callback_handler("messageSentHandler", [None, 0xBEED, aps, 253, 0, b""]) def test_dup_send_success(app, aps, ieee): req = app._pending[253] = mock.MagicMock() - req.result.set_result.side_effect = asyncio.futures.InvalidStateError() - app.ezsp_callback_handler( - 'messageSentHandler', - [None, 0xbeed, aps, 253, 0, b''] - ) + req.result.set_result.side_effect = asyncio.InvalidStateError() + app.ezsp_callback_handler("messageSentHandler", [None, 0xBEED, aps, 253, 0, b""]) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 def test_join_handler(app, ieee): # Calls device.initialize, leaks a task - app.ezsp_callback_handler( - 'trustCenterJoinHandler', - [1, ieee, None, None, None], - ) + app.ezsp_callback_handler("trustCenterJoinHandler", [1, ieee, None, None, None]) assert ieee in app.devices def test_leave_handler(app, ieee): app.devices[ieee] = mock.sentinel.device app.ezsp_callback_handler( - 'trustCenterJoinHandler', - [1, ieee, t.EmberDeviceUpdate.DEVICE_LEFT, None, None] + "trustCenterJoinHandler", [1, ieee, t.EmberDeviceUpdate.DEVICE_LEFT, None, None] ) assert ieee in app.devices @@ -246,30 +236,48 @@ def test_permit(app): def test_permit_with_key(app): app._ezsp.addTransientLinkKey = get_mock_coro([0]) app._ezsp.setPolicy = get_mock_coro([0]) + app.permit = get_mock_coro([0]) loop = asyncio.get_event_loop() - loop.run_until_complete(app.permit_with_key(bytes([1, 2, 3, 4, 5, 6, 7, 8]), bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), 60)) + loop.run_until_complete( + app.permit_with_key( + bytes([1, 2, 3, 4, 5, 6, 7, 8]), + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, + ) + ) assert app._ezsp.addTransientLinkKey.call_count == 1 - assert app._ezsp.permitJoining.call_count == 1 + assert app.permit.call_count == 1 def test_permit_with_key_ieee(app, ieee): app._ezsp.addTransientLinkKey = get_mock_coro([0]) app._ezsp.setPolicy = get_mock_coro([0]) + app.permit = get_mock_coro([0]) loop = asyncio.get_event_loop() - loop.run_until_complete(app.permit_with_key(ieee, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), 60)) + loop.run_until_complete( + app.permit_with_key( + ieee, + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, + ) + ) assert app._ezsp.addTransientLinkKey.call_count == 1 - assert app._ezsp.permitJoining.call_count == 1 + assert app.permit.call_count == 1 def test_permit_with_key_invalid_install_code(app, ieee): loop = asyncio.get_event_loop() with pytest.raises(Exception): - loop.run_until_complete(app.permit_with_key(ieee, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]), 60)) + loop.run_until_complete( + app.permit_with_key( + ieee, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]), 60 + ) + ) def test_permit_with_key_failed_add_key(app, ieee): @@ -277,7 +285,13 @@ def test_permit_with_key_failed_add_key(app, ieee): loop = asyncio.get_event_loop() with pytest.raises(Exception): - loop.run_until_complete(app.permit_with_key(ieee, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), 60)) + loop.run_until_complete( + app.permit_with_key( + ieee, + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, + ) + ) def test_permit_with_key_failed_set_policy(app, ieee): @@ -286,21 +300,34 @@ def test_permit_with_key_failed_set_policy(app, ieee): loop = asyncio.get_event_loop() with pytest.raises(Exception): - loop.run_until_complete(app.permit_with_key(ieee, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), 60)) + loop.run_until_complete( + app.permit_with_key( + ieee, + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, + ) + ) @pytest.mark.asyncio -async def _request(app, send_success=True, send_ack_received=True, send_ack_success=True, - ezsp_operational=True, is_end_device=False, **kwargs): +async def _request( + app, + send_success=True, + send_ack_received=True, + send_ack_success=True, + ezsp_operational=True, + is_end_device=False, + **kwargs +): async def mocksend(method, nwk, aps_frame, seq, data): if not ezsp_operational: raise EzspError req = app._pending[seq] if send_ack_received: if send_ack_success: - req.result.set_result((0, 'success')) + req.result.set_result((0, "success")) else: - req.result.set_result((102, 'failure')) + req.result.set_result((102, "failure")) if send_success: return [0] return [2] @@ -310,7 +337,7 @@ async def mocksend(method, nwk, aps_frame, seq, data): app._ezsp.setExtendedTimeout.side_effect = asyncio.coroutine(mock.MagicMock()) device = mock.MagicMock() device.node_desc.is_end_device = is_end_device - res = await app.request(device, 9, 8, 7, 6, 5, b'', **kwargs) + res = await app.request(device, 9, 8, 7, 6, 5, b"", **kwargs) assert len(app._pending) == 0 return res @@ -378,10 +405,18 @@ async def test_request_extended_timeout(app): @pytest.mark.asyncio -async def _test_broadcast(app, broadcast_success=True, send_timeout=False, - ezsp_running=True): +async def _test_broadcast( + app, broadcast_success=True, send_timeout=False, ezsp_running=True +): (profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data) = ( - 0x260, 1, 2, 3, 0x0100, 0x06, 210, b'\x02\x01\x00' + 0x260, + 1, + 2, + 3, + 0x0100, + 0x06, + 210, + b"\x02\x01\x00", ) async def mock_send(nwk, aps, radius, tsn, data): @@ -389,7 +424,7 @@ async def mock_send(nwk, aps, radius, tsn, data): raise EzspError if broadcast_success: if not send_timeout: - app._pending[tsn].result.set_result((0, 'sendBroadcast failure')) + app._pending[tsn].result.set_result((0, "sendBroadcast failure")) return [0] else: return [t.EmberStatus.ERR_FATAL] @@ -398,7 +433,8 @@ async def mock_send(nwk, aps, radius, tsn, data): app.get_sequence = mock.MagicMock(return_value=mock.sentinel.msg_tag) res = await app.broadcast( - profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data) + profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data + ) assert app._ezsp.sendBroadcast.call_count == 1 assert app._ezsp.sendBroadcast.call_args[0][2] == radius assert app._ezsp.sendBroadcast.call_args[0][3] == mock.sentinel.msg_tag @@ -438,7 +474,14 @@ async def test_broadcast_ezsp_fail(app): async def test_broadcast_ctrl_not_running(app): app._ctrl_event.clear() (profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data) = ( - 0x260, 1, 2, 3, 0x0100, 0x06, 210, b'\x02\x01\x00' + 0x260, + 1, + 2, + 3, + 0x0100, + 0x06, + 210, + b"\x02\x01\x00", ) async def mocksend(nwk, aps, radiusm, tsn, data): @@ -447,8 +490,7 @@ async def mocksend(nwk, aps, radiusm, tsn, data): app._ezsp.sendBroadcast.side_effect = mocksend with pytest.raises(ControllerError): - await app.broadcast( - profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data) + await app.broadcast(profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data) assert len(app._pending) == 0 assert app._ezsp.sendBroadcast.call_count == 0 @@ -472,10 +514,8 @@ def test_is_controller_running(app): def test_reset_frame(app): - app._handle_reset_request = mock.MagicMock( - spec_set=app._handle_reset_request) - app.ezsp_callback_handler('_reset_controller_application', - (mock.sentinel.error, )) + app._handle_reset_request = mock.MagicMock(spec_set=app._handle_reset_request) + app.ezsp_callback_handler("_reset_controller_application", (mock.sentinel.error,)) assert app._handle_reset_request.call_count == 1 assert app._handle_reset_request.call_args[0][0] is mock.sentinel.error @@ -520,7 +560,7 @@ async def test_handle_reset_req_existing_preempt(app): async def test_reset_controller_loop(app, monkeypatch): from bellows.zigbee import application - monkeypatch.setattr(application, 'RESET_ATTEMPT_BACKOFF_TIME', 0.1) + monkeypatch.setattr(application, "RESET_ATTEMPT_BACKOFF_TIME", 0.1) app._watchdog_task = asyncio.Future() reset_succ_on_try = reset_call_count = 2 @@ -559,7 +599,8 @@ async def test_reset_controller_routine(app): @pytest.mark.asyncio async def test_watchdog(app, monkeypatch): from bellows.zigbee import application - monkeypatch.setattr(application, 'WATCHDOG_WAKE_PERIOD', 0.1) + + monkeypatch.setattr(application, "WATCHDOG_WAKE_PERIOD", 0.1) nop_success = 3 async def nop_mock(): @@ -603,7 +644,7 @@ def coordinator(app, ieee): dev = Device(app, ieee, 0x0000) ep = dev.add_endpoint(1) ep.profile_id = 0x0104 - ep.device_type = 0xbeef + ep.device_type = 0xBEEF ep.add_output_cluster(security.IasZone.cluster_id) return bellows.zigbee.application.EZSPCoordinator(app, ieee, 0x0000, dev) @@ -764,3 +805,70 @@ async def test_ezsp_remove_from_group_fail_ep(coordinator): assert ret is not None assert mc.unsubscribe.call_count == 1 assert mc.unsubscribe.call_args[0][0] == grp_id + + +@pytest.mark.asyncio +async def _mrequest( + app, + send_success=True, + send_ack_received=True, + send_ack_success=True, + ezsp_operational=True, + **kwargs +): + async def mocksend(method, nwk, aps_frame, seq, data): + if not ezsp_operational: + raise EzspError + req = app._pending[seq] + if send_ack_received: + if send_ack_success: + req.result.set_result((0, "success")) + else: + req.result.set_result((102, "failure")) + if send_success: + return [0] + return [2] + + app._ezsp.sendMulticast = mocksend + res = await app.mrequest(0x1234, 9, 8, 7, 6, b"", **kwargs) + assert len(app._pending) == 0 + return res + + +@pytest.mark.asyncio +async def test_mrequest(app): + res = await _mrequest(app) + assert res[0] == 0 + + +@pytest.mark.asyncio +async def test_mrequest_ack_timeout(app, aps): + with pytest.raises(asyncio.TimeoutError): + await _mrequest(app, send_ack_received=False) + + +@pytest.mark.asyncio +async def test_mrequest_send_unicast_fail(app): + res = await _mrequest(app, send_success=False) + assert res[0] != 0 + + +@pytest.mark.asyncio +async def test_mrequest_ezsp_failed(app): + with pytest.raises(EzspError): + await _mrequest(app, ezsp_operational=False) + assert len(app._pending) == 0 + + +@pytest.mark.asyncio +async def test_mrequest_send_timeout(app): + with pytest.raises(asyncio.TimeoutError): + await _mrequest(app, send_ack_received=False) + assert app._pending == {} + + +@pytest.mark.asyncio +async def test_mrequest_ctrl_not_running(app): + app._ctrl_event.clear() + with pytest.raises(ControllerError): + await _mrequest(app) diff --git a/tests/test_commands.py b/tests/test_commands.py index 39a7b9a3..4a7565f4 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -26,7 +26,7 @@ def test_parms(): def test_handlers(): """Test that handler methods only have responses""" for command, parms in commands.COMMANDS.items(): - if not command.endswith('Handler'): + if not command.endswith("Handler"): continue assert len(parms[1]) == 0, command assert len(parms[2]) > 0, command diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index bcbbd2ae..a8f9cbe23 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -22,16 +22,14 @@ async def mockconnect(*args, **kwargs): nonlocal connected connected = True - monkeypatch.setattr(uart, 'connect', mockconnect) + monkeypatch.setattr(uart, "connect", mockconnect) ezsp_f._gw = None loop = asyncio.get_event_loop() - loop.run_until_complete(ezsp_f.connect(mock.sentinel.port, - mock.sentinel.speed)) + loop.run_until_complete(ezsp_f.connect(mock.sentinel.port, mock.sentinel.speed)) assert connected - ezsp_f.connect = mock.MagicMock( - side_effect=asyncio.coroutine(mock.MagicMock())) + ezsp_f.connect = mock.MagicMock(side_effect=asyncio.coroutine(mock.MagicMock())) loop.run_until_complete(ezsp_f.reconnect()) assert ezsp_f.connect.call_count == 1 assert ezsp_f.connect.call_args[0][0] is mock.sentinel.port @@ -90,32 +88,31 @@ def test_non_existent_attr_with_list(ezsp_f): def test_command(ezsp_f): ezsp_f._gw = mock.MagicMock() ezsp_f.start_ezsp() - ezsp_f._command('version') + ezsp_f._command("version") assert ezsp_f._gw.data.call_count == 1 def test_command_ezsp_stopped(ezsp_f): with pytest.raises(EzspError): - ezsp_f._command('version') + ezsp_f._command("version") def _test_list_command(ezsp_f, mockcommand): loop = asyncio.get_event_loop() ezsp_f._command = mockcommand - return loop.run_until_complete(ezsp_f._list_command( - 'startScan', - ['networkFoundHandler'], - 'scanCompleteHandler', - 1, - )) + return loop.run_until_complete( + ezsp_f._list_command( + "startScan", ["networkFoundHandler"], "scanCompleteHandler", 1 + ) + ) def test_list_command(ezsp_f): async def mockcommand(name, *args): - assert name == 'startScan' - ezsp_f.frame_received(b'\x01\x00\x1b') - ezsp_f.frame_received(b'\x02\x00\x1b') - ezsp_f.frame_received(b'\x03\x00\x1c') + assert name == "startScan" + ezsp_f.frame_received(b"\x01\x00\x1b") + ezsp_f.frame_received(b"\x02\x00\x1b") + ezsp_f.frame_received(b"\x03\x00\x1c") return [0] @@ -125,7 +122,7 @@ async def mockcommand(name, *args): def test_list_command_initial_failure(ezsp_f): async def mockcommand(name, *args): - assert name == 'startScan' + assert name == "startScan" return [1] with pytest.raises(Exception): @@ -134,10 +131,10 @@ async def mockcommand(name, *args): def test_list_command_later_failure(ezsp_f): async def mockcommand(name, *args): - assert name == 'startScan' - ezsp_f.frame_received(b'\x01\x00\x1b') - ezsp_f.frame_received(b'\x02\x00\x1b') - ezsp_f.frame_received(b'\x03\x00\x1c\x01\x01') + assert name == "startScan" + ezsp_f.frame_received(b"\x01\x00\x1b") + ezsp_f.frame_received(b"\x02\x00\x1b") + ezsp_f.frame_received(b"\x03\x00\x1c\x01\x01") return [0] @@ -147,8 +144,8 @@ async def mockcommand(name, *args): def _test_form_network(ezsp_f, initial_result, final_result): async def mockcommand(name, *args): - assert name == 'formNetwork' - ezsp_f.frame_received(b'\x01\x00\x19' + final_result) + assert name == "formNetwork" + ezsp_f.frame_received(b"\x01\x00\x19" + final_result) return initial_result ezsp_f._command = mockcommand @@ -158,30 +155,36 @@ async def mockcommand(name, *args): def test_form_network(ezsp_f): - _test_form_network(ezsp_f, [0], b'\x90') + _test_form_network(ezsp_f, [0], b"\x90") def test_form_network_fail(ezsp_f): with pytest.raises(Exception): - _test_form_network(ezsp_f, [1], b'\x90') + _test_form_network(ezsp_f, [1], b"\x90") def test_form_network_fail_stack_status(ezsp_f): with pytest.raises(Exception): - _test_form_network(ezsp_f, [0], b'\x00') + _test_form_network(ezsp_f, [0], b"\x00") def test_receive_new(ezsp_f): ezsp_f.handle_callback = mock.MagicMock() - ezsp_f.frame_received(b'\x00\xff\x00\x04\x05\x06') + ezsp_f.frame_received(b"\x00\xff\x00\x04\x05\x06") + assert ezsp_f.handle_callback.call_count == 1 + + +def test_receive_protocol_5(ezsp_f): + ezsp_f.handle_callback = mock.MagicMock() + ezsp_f.frame_received(b"\x01\x80\xff\x00\x00\x06\x02\x00") assert ezsp_f.handle_callback.call_count == 1 def test_receive_reply(ezsp_f): ezsp_f.handle_callback = mock.MagicMock() callback_mock = mock.MagicMock(spec_set=asyncio.Future) - ezsp_f._awaiting[0] = (0, ezsp_f.COMMANDS['version'][2], callback_mock) - ezsp_f.frame_received(b'\x00\xff\x00\x04\x05\x06') + ezsp_f._awaiting[0] = (0, ezsp_f.COMMANDS["version"][2], callback_mock) + ezsp_f.frame_received(b"\x00\xff\x00\x04\x05\x06") assert 0 not in ezsp_f._awaiting assert callback_mock.set_exception.call_count == 0 @@ -194,8 +197,8 @@ def test_receive_reply_after_timeout(ezsp_f): ezsp_f.handle_callback = mock.MagicMock() callback_mock = mock.MagicMock(spec_set=asyncio.Future) callback_mock.set_result.side_effect = asyncio.InvalidStateError() - ezsp_f._awaiting[0] = (0, ezsp_f.COMMANDS['version'][2], callback_mock) - ezsp_f.frame_received(b'\x00\xff\x00\x04\x05\x06') + ezsp_f._awaiting[0] = (0, ezsp_f.COMMANDS["version"][2], callback_mock) + ezsp_f.frame_received(b"\x00\xff\x00\x04\x05\x06") assert 0 not in ezsp_f._awaiting assert callback_mock.set_exception.call_count == 0 @@ -230,11 +233,9 @@ def test_callback_multi(ezsp_f): ezsp_f.remove_callback(cbid1) ezsp_f.handle_callback(4, 5, 6) - testcb.assert_has_calls([ - mock.call(1, 2, 3), - mock.call(1, 2, 3), - mock.call(4, 5, 6), - ]) + testcb.assert_has_calls( + [mock.call(1, 2, 3), mock.call(1, 2, 3), mock.call(4, 5, 6)] + ) def test_callback_exc(ezsp_f): @@ -246,29 +247,20 @@ def test_callback_exc(ezsp_f): assert testcb.call_count == 1 -def test_version_5(ezsp_f): - ezsp_f._gw = mock.MagicMock() - ezsp_f.start_ezsp() - - ezsp_f.frame_received(b'\x00\x00\xff\x00\x00\x05\x05\x06') - assert ezsp_f.ezsp_version == 5 - - ezsp_f.getValue(1) - ezsp_f._gw.data.assert_called_once_with(bytes([0x00, 0x00, 0xFF, 0x00, 0xAA, 0x01])) - - def test_change_version(ezsp_f): loop = asyncio.get_event_loop() + version = 5 def mockcommand(name, *args): - assert name == 'version' - ezsp_f.frame_received(b'\x01\x00\x1b') + assert name == "version" + ezsp_f.frame_received(b"\x01\x00\x1b") fut = asyncio.Future() - fut.set_result([5]) + fut.set_result([version, 2, 2046]) return fut ezsp_f._command = mockcommand loop.run_until_complete(ezsp_f.version()) + assert ezsp_f.ezsp_version == version def test_stop_ezsp(ezsp_f): @@ -284,8 +276,7 @@ def test_start_ezsp(ezsp_f): def test_connection_lost(ezsp_f): - ezsp_f.enter_failed_state = mock.MagicMock( - spec_set=ezsp_f.enter_failed_state) + ezsp_f.enter_failed_state = mock.MagicMock(spec_set=ezsp_f.enter_failed_state) ezsp_f.connection_lost(mock.sentinel.exc) assert ezsp_f.enter_failed_state.call_count == 1 @@ -297,3 +288,13 @@ def test_enter_failed_state(ezsp_f): assert ezsp_f.stop_ezsp.call_count == 1 assert ezsp_f.handle_callback.call_count == 1 assert ezsp_f.handle_callback.call_args[0][1][0] == mock.sentinel.error + + +def test_ezsp_frame(ezsp_f): + ezsp_f._seq = 0x22 + data = ezsp_f._ezsp_frame("version", 6) + assert data == b"\x22\x00\x00\x06" + + ezsp_f._ezsp_version = 5 + data = ezsp_f._ezsp_frame("version", 6) + assert data == b"\x22\x00\xff\x00\x00\x06" diff --git a/tests/test_multicast.py b/tests/test_multicast.py index a3f6525b..247db5ab 100644 --- a/tests/test_multicast.py +++ b/tests/test_multicast.py @@ -65,12 +65,8 @@ async def test_startup(multicast): coordinator = mock.MagicMock() 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 - } + ep1.member_of = [mock.sentinel.grp, mock.sentinel.grp, mock.sentinel.grp] + coordinator.endpoints = {0: mock.sentinel.ZDO, 1: ep1} multicast._initialize = mock.MagicMock() multicast._initialize.side_effect = asyncio.coroutine(mock.MagicMock()) multicast.subscribe = mock.MagicMock() diff --git a/tests/test_thread.py b/tests/test_thread.py index a2d122ae..8421bb45 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -15,16 +15,8 @@ async def test_thread_start(monkeypatch): current_loop = asyncio.get_event_loop() loopmock = mock.MagicMock() - monkeypatch.setattr( - asyncio, - 'new_event_loop', - lambda: loopmock - ) - monkeypatch.setattr( - asyncio, - 'set_event_loop', - lambda loop: None - ) + monkeypatch.setattr(asyncio, "new_event_loop", lambda: loopmock) + monkeypatch.setattr(asyncio, "set_event_loop", lambda loop: None) def mockrun(task): future = asyncio.run_coroutine_threadsafe(task, loop=current_loop) @@ -45,7 +37,7 @@ def __init__(self): self.exceptions = [] def __call__(self, thread_loop, context): - exc = context.get('exception') or Exception(context['message']) + exc = context.get("exception") or Exception(context["message"]) self.exceptions.append(exc) @@ -54,13 +46,15 @@ def __call__(self, thread_loop, context): async def thread(): thread = EventLoopThread() await thread.start() - thread.loop.call_soon_threadsafe(thread.loop.set_exception_handler, ExceptionCollector()) + thread.loop.call_soon_threadsafe( + thread.loop.set_exception_handler, ExceptionCollector() + ) await yield_(thread) thread.force_stop() if thread.thread_complete is not None: await asyncio.wait_for(thread.thread_complete, 1) - [t.join(1) for t in threading.enumerate() if 'bellows' in t.name] - threads = [t for t in threading.enumerate() if 'bellows' in t.name] + [t.join(1) for t in threading.enumerate() if "bellows" in t.name] + threads = [t for t in threading.enumerate() if "bellows" in t.name] assert len(threads) == 0 @@ -87,7 +81,7 @@ async def test_thread_double_start(thread): previous_loop = thread.loop await thread.start() if sys.version_info[:2] >= (3, 6): - threads = [t for t in threading.enumerate() if 'bellows' in t.name] + threads = [t for t in threading.enumerate() if "bellows" in t.name] assert len(threads) == 1 assert thread.loop is previous_loop diff --git a/tests/test_types.py b/tests/test_types.py index 1731f58a..65da7389 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -2,70 +2,71 @@ def test_basic(): - assert t.uint8_t.deserialize(b'\x08') == (8, b'') + assert t.uint8_t.deserialize(b"\x08") == (8, b"") def test_extra_data(): - assert t.uint8_t.deserialize(b'\x08extra') == (8, b'extra') + assert t.uint8_t.deserialize(b"\x08extra") == (8, b"extra") def test_multibyte(): - assert t.uint16_t.deserialize(b'\x08\x01') == (0x0108, b'') + assert t.uint16_t.deserialize(b"\x08\x01") == (0x0108, b"") def test_lvbytes(): - d, r = t.LVBytes.deserialize(b'\x0412345') - assert r == b'5' - assert d == b'1234' + d, r = t.LVBytes.deserialize(b"\x0412345") + assert r == b"5" + assert d == b"1234" - assert t.LVBytes.serialize(d) == b'\x041234' + assert t.LVBytes.serialize(d) == b"\x041234" def test_lvlist(): - d, r = t.LVList(t.uint8_t).deserialize(b'\x0412345') - assert r == b'5' - assert d == list(map(ord, '1234')) - assert t.LVList(t.uint8_t).serialize(d) == b'\x041234' + d, r = t.LVList(t.uint8_t).deserialize(b"\x0412345") + assert r == b"5" + assert d == list(map(ord, "1234")) + assert t.LVList(t.uint8_t).serialize(d) == b"\x041234" def test_list(): - expected = list(map(ord, '\x0123')) - assert t.List(t.uint8_t).deserialize(b'\x0123') == (expected, b'') + expected = list(map(ord, "\x0123")) + assert t.List(t.uint8_t).deserialize(b"\x0123") == (expected, b"") def test_struct(): class TestStruct(t.EzspStruct): - _fields = [('a', t.uint8_t), ('b', t.uint8_t)] + _fields = [("a", t.uint8_t), ("b", t.uint8_t)] ts = TestStruct() - ts.a = t.uint8_t(0xaa) - ts.b = t.uint8_t(0xbb) + ts.a = t.uint8_t(0xAA) + ts.b = t.uint8_t(0xBB) ts2 = TestStruct(ts) assert ts2.a == ts.a assert ts2.b == ts.b r = repr(ts) - assert 'TestStruct' in r - assert r.startswith('<') and r.endswith('>') + assert "TestStruct" in r + assert r.startswith("<") and r.endswith(">") s = ts2.serialize() - assert s == b'\xaa\xbb' + assert s == b"\xaa\xbb" def test_str(): - assert str(t.EzspStatus.deserialize(b'\0')[0]) == 'EzspStatus.SUCCESS' + assert str(t.EzspStatus.deserialize(b"\0")[0]) == "EzspStatus.SUCCESS" def test_ember_eui64(): - ser = b'\x00\x01\x02\x03\x04\x05\x06\x07' + ser = b"\x00\x01\x02\x03\x04\x05\x06\x07" eui64, data = t.EmberEUI64.deserialize(ser) - assert data == b'' + assert data == b"" assert eui64.serialize() == ser def test_hex_repr(): class NwkAsHex(t.HexRepr, t.uint16_t): _hex_len = 4 + nwk = NwkAsHex(0x1234) - assert str(nwk) == '0x1234' - assert repr(nwk) == '0x1234' + assert str(nwk) == "0x1234" + assert repr(nwk) == "0x1234" diff --git a/tests/test_uart.py b/tests/test_uart.py index 6b1b9c60..4fa3917d 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -19,14 +19,10 @@ async def mockconnect(loop, protocol_factory, **kwargs): loop.call_soon(protocol.connection_made, transport) return None, protocol - monkeypatch.setattr( - serial_asyncio, - 'create_serial_connection', - mockconnect, - ) + monkeypatch.setattr(serial_asyncio, "create_serial_connection", mockconnect) await uart.connect(portmock, 115200, appmock, use_thread=False) - threads = [t for t in threading.enumerate() if 'bellows' in t.name] + threads = [t for t in threading.enumerate() if "bellows" in t.name] assert len(threads) == 0 @@ -42,11 +38,7 @@ async def mockconnect(loop, protocol_factory, **kwargs): loop.call_soon(protocol.connection_made, transport) return None, protocol - monkeypatch.setattr( - serial_asyncio, - 'create_serial_connection', - mockconnect, - ) + monkeypatch.setattr(serial_asyncio, "create_serial_connection", mockconnect) def on_transport_close(): gw.connection_lost(None) @@ -58,8 +50,8 @@ def on_transport_close(): gw.close() # Ensure all threads are cleaned up - [t.join(1) for t in threading.enumerate() if 'bellows' in t.name] - threads = [t for t in threading.enumerate() if 'bellows' in t.name] + [t.join(1) for t in threading.enumerate() if "bellows" in t.name] + threads = [t for t in threading.enumerate() if "bellows" in t.name] assert len(threads) == 0 @@ -71,79 +63,85 @@ def gw(): def test_randomize(gw): - assert gw._randomize(b'\x00\x00\x00\x00\x00') == b'\x42\x21\xa8\x54\x2a' - assert gw._randomize(b'\x42\x21\xa8\x54\x2a') == b'\x00\x00\x00\x00\x00' + assert gw._randomize(b"\x00\x00\x00\x00\x00") == b"\x42\x21\xa8\x54\x2a" + assert gw._randomize(b"\x42\x21\xa8\x54\x2a") == b"\x00\x00\x00\x00\x00" def test_stuff(gw): - orig = b'\x00\x7E\x01\x7D\x02\x11\x03\x13\x04\x18\x05\x1a\x06' - stuff = b'\x00\x7D\x5E\x01\x7D\x5D\x02\x7D\x31\x03\x7D\x33\x04\x7D\x38\x05\x7D\x3a\x06' + orig = b"\x00\x7E\x01\x7D\x02\x11\x03\x13\x04\x18\x05\x1a\x06" + stuff = ( + b"\x00\x7D\x5E\x01\x7D\x5D\x02\x7D\x31\x03\x7D\x33\x04\x7D\x38\x05\x7D\x3a\x06" + ) assert gw._stuff(orig) == stuff def test_unstuff(gw): - orig = b'\x00\x7E\x01\x7D\x02\x11\x03\x13\x04\x18\x05\x1a\x06' - stuff = b'\x00\x7D\x5E\x01\x7D\x5D\x02\x7D\x31\x03\x7D\x33\x04\x7D\x38\x05\x7D\x3a\x06' + orig = b"\x00\x7E\x01\x7D\x02\x11\x03\x13\x04\x18\x05\x1a\x06" + stuff = ( + b"\x00\x7D\x5E\x01\x7D\x5D\x02\x7D\x31\x03\x7D\x33\x04\x7D\x38\x05\x7D\x3a\x06" + ) assert gw._unstuff(stuff) == orig def test_rst(gw): - assert gw._rst_frame() == b'\x1a\xc0\x38\xbc\x7e' + assert gw._rst_frame() == b"\x1a\xc0\x38\xbc\x7e" def test_data_frame(gw): - expected = b'\x42\x21\xa8\x54\x2a' - assert gw._data_frame(b'\x00\x00\x00\x00\x00', 0, False)[1:-3] == expected + expected = b"\x42\x21\xa8\x54\x2a" + assert gw._data_frame(b"\x00\x00\x00\x00\x00", 0, False)[1:-3] == expected def test_cancel_received(gw): gw.rst_frame_received = mock.MagicMock() - gw.data_received(b'garbage') - gw.data_received(b'\x1a\xc0\x38\xbc\x7e') + gw.data_received(b"garbage") + gw.data_received(b"\x1a\xc0\x38\xbc\x7e") assert gw.rst_frame_received.call_count == 1 - assert gw._buffer == b'' + assert gw._buffer == b"" def test_substitute_received(gw): gw.rst_frame_received = mock.MagicMock() - gw.data_received(b'garbage') - gw.data_received(b'\x18\x38\xbc\x7epart') - gw.data_received(b'ial') + gw.data_received(b"garbage") + gw.data_received(b"\x18\x38\xbc\x7epart") + gw.data_received(b"ial") gw.rst_frame_received.assert_not_called() - assert gw._buffer == b'partial' + assert gw._buffer == b"partial" def test_partial_data_received(gw): gw.write = mock.MagicMock() - gw.data_received(b'\x54\x79\xa1\xb0') - gw.data_received(b'\x50\xf2\x6e\x7e') + gw.data_received(b"\x54\x79\xa1\xb0") + gw.data_received(b"\x50\xf2\x6e\x7e") assert gw.write.call_count == 1 assert gw._application.frame_received.call_count == 1 def test_crc_error(gw): gw.write = mock.MagicMock() - gw.data_received(b'L\xa1\x8e\x03\xcd\x07\xb9Y\xfbG%\xae\xbd~') + gw.data_received(b"L\xa1\x8e\x03\xcd\x07\xb9Y\xfbG%\xae\xbd~") assert gw.write.call_count == 1 assert gw._application.frame_received.call_count == 0 def test_crc_error_and_valid_frame(gw): gw.write = mock.MagicMock() - gw.data_received(b'L\xa1\x8e\x03\xcd\x07\xb9Y\xfbG%\xae\xbd~\x54\x79\xa1\xb0\x50\xf2\x6e\x7e') + gw.data_received( + b"L\xa1\x8e\x03\xcd\x07\xb9Y\xfbG%\xae\xbd~\x54\x79\xa1\xb0\x50\xf2\x6e\x7e" + ) assert gw.write.call_count == 2 assert gw._application.frame_received.call_count == 1 def test_data_frame_received(gw): gw.write = mock.MagicMock() - gw.data_received(b'\x54\x79\xa1\xb0\x50\xf2\x6e\x7e') + gw.data_received(b"\x54\x79\xa1\xb0\x50\xf2\x6e\x7e") assert gw.write.call_count == 1 assert gw._application.frame_received.call_count == 1 def test_ack_frame_received(gw): - gw.data_received(b'\x86\x10\xbe\x7e') + gw.data_received(b"\x86\x10\xbe\x7e") def test_nak_frame_received(gw): @@ -151,44 +149,45 @@ def test_nak_frame_received(gw): def test_rst_frame_received(gw): - gw.data_received(b'garbage\x1a\xc0\x38\xbc\x7e') + gw.data_received(b"garbage\x1a\xc0\x38\xbc\x7e") def test_rstack_frame_received(gw): gw._reset_future = mock.MagicMock() gw._reset_future.done = mock.MagicMock(return_value=False) - gw.data_received(b'\xc1\x02\x0b\nR\x7e') + gw.data_received(b"\xc1\x02\x0b\nR\x7e") assert gw._reset_future.done.call_count == 1 assert gw._reset_future.set_result.call_count == 1 def test_wrong_rstack_frame_received(gw): gw._reset_future = mock.MagicMock() - gw.data_received(b'\xc1\x02\x01\xab\x18\x7e') + gw.data_received(b"\xc1\x02\x01\xab\x18\x7e") assert gw._reset_future.set_result.call_count == 0 def test_error_rstack_frame_received(gw): gw._reset_future = mock.MagicMock() - gw.data_received(b'\xc1\x02\x81\x3a\x90\x7e') + gw.data_received(b"\xc1\x02\x81\x3a\x90\x7e") assert gw._reset_future.set_result.call_count == 0 def test_rstack_frame_received_nofut(gw): - gw.data_received(b'\xc1\x02\x0b\nR\x7e') + gw.data_received(b"\xc1\x02\x0b\nR\x7e") def test_rstack_frame_received_out_of_order(gw): gw._reset_future = mock.MagicMock() gw._reset_future.done = mock.MagicMock(return_value=True) - gw.data_received(b'\xc1\x02\x0b\nR\x7e') + gw.data_received(b"\xc1\x02\x0b\nR\x7e") assert gw._reset_future.done.call_count == 1 assert gw._reset_future.set_result.call_count == 0 def test_error_frame_received(gw): from bellows.types import NcpResetCode - gw.frame_received(b'\xc2\x02\x03\xd2\x0a\x7e') + + gw.frame_received(b"\xc2\x02\x03\xd2\x0a\x7e") efs = gw._application.enter_failed_state assert efs.call_count == 1 assert efs.call_args[0][0] == NcpResetCode.RESET_WATCHDOG @@ -210,7 +209,8 @@ async def test_reset(gw): fut = asyncio.Future() gw._pending = (mock.sentinel.seq, fut) gw._transport.write.side_effect = lambda *args: gw._reset_future.set_result( - mock.sentinel.reset_result) + mock.sentinel.reset_result + ) reset_result = await gw.reset() assert gw._transport.write.call_count == 1 @@ -227,7 +227,7 @@ async def test_reset(gw): @pytest.mark.asyncio async def test_reset_timeout(gw, monkeypatch): gw._loop = asyncio.get_event_loop() - monkeypatch.setattr(uart, 'RESET_TIMEOUT', 0.1) + monkeypatch.setattr(uart, "RESET_TIMEOUT", 0.1) with pytest.raises(asyncio.TimeoutError): await gw.reset() @@ -248,7 +248,7 @@ def test_data(gw): def mockwrite(data): nonlocal loop, write_call_count - if data == b'\x10 @\xda}^Z~': + if data == b"\x10 @\xda}^Z~": print(write_call_count) loop.call_soon(gw._handle_nak, gw._pending[0]) else: @@ -257,9 +257,9 @@ def mockwrite(data): gw.write = mockwrite - gw.data(b'foo') - gw.data(b'bar') - gw.data(b'baz') + gw.data(b"foo") + gw.data(b"bar") + gw.data(b"baz") gw._sendq.put_nowait(gw.Terminator) loop = asyncio.get_event_loop() diff --git a/tox.ini b/tox.ini index f1c6f0dc..780f6f5f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py35, py36, py37, lint +envlist = py36, py37, py38, lint, black skip_missing_interpreters = True [testenv] @@ -26,6 +26,10 @@ deps = pep8-naming==0.4.1 commands = flake8 -[flake8] -ignore = E501 -max-complexity = 16 +[testenv:black] +deps=black +setenv = + LC_ALL=C.UTF-8 + LANG=C.UTF-8 +commands= + black --check --fast {toxinidir}/bellows {toxinidir}/tests {toxinidir}