Skip to content

Commit b6f7384

Browse files
committedDec 25, 2019
add test for uart
1 parent 8e3a202 commit b6f7384

16 files changed

+503
-210
lines changed
 

‎.coveragerc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[run]
2+
source = zigpy_cc

‎.pre-commit-config.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
repos:
2+
- repo: https://github.com/psf/black
3+
rev: 19.3b0
4+
hooks:
5+
- id: black
6+
args:
7+
- --safe
8+
- --quiet
9+
- repo: https://gitlab.com/pycqa/flake8
10+
rev: 3.7.8
11+
hooks:
12+
- id: flake8

‎.travis.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: python
2+
matrix:
3+
fast_finish: true
4+
include:
5+
- python: "3.6"
6+
env: TOXENV=lint
7+
- python: "3.6"
8+
env: TOXENV=py36
9+
- python: "3.7"
10+
env: TOXENV=py37
11+
- python: "3.8"
12+
env: TOXENV=py38
13+
install: pip install -U setuptools tox coveralls
14+
script: tox
15+
after_success: coveralls

‎COPYING

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
zigpy-cc
2+
Copyright (C) 2019 Balázs Sándor
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.

‎Contributors.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Contributors
2+
- [Balázs Sándor] (https://github.com/sanyatuning)

‎README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# zigpy-cc
2+
3+
[zigpy-cc](https://github.com/sanyatuning/zigpy-cc) is a Python 3 implementation for the [Zigpy](https://github.com/zigpy/) project to implement Texas Instruments CC2531 based [Zigbee](https://www.zigbee.org) radio devices.
4+
5+
The goal of this project to add native support for the CC25xx based ZigBee modules in Home Assistant via [Zigpy](https://github.com/zigpy/).
6+
7+
This library is a port of [zigbee-herdsman](https://github.com/Koenkk/zigbee-herdsman).
8+
9+
# Releases via PyPI
10+
Tagged versions are also released via PyPI
11+
12+
- TODO
13+
14+
# External documentation and reference
15+
16+
TODO
17+
18+
# How to contribute
19+
20+
If you are looking to make a contribution to this project we suggest that you follow the steps in these guides:
21+
- https://github.com/firstcontributions/first-contributions/blob/master/README.md
22+
- https://github.com/firstcontributions/first-contributions/blob/master/github-desktop-tutorial.md

‎setup.cfg

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[flake8]
2+
exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
3+
# To work with Black
4+
max-line-length = 88
5+
# W503: Line break occurred before a binary operator
6+
# E203: Whitespace before ':'
7+
# D202 No blank lines allowed after function docstring
8+
ignore =
9+
W503,
10+
E203,
11+
D202
12+
13+
[isort]
14+
# https://github.com/timothycrosley/isort
15+
# https://github.com/timothycrosley/isort/wiki/isort-Settings
16+
# splits long import on multiple lines indented by 4 spaces
17+
multi_line_output = 3
18+
include_trailing_comma=True
19+
force_grid_wrap=0
20+
use_parentheses=True
21+
line_length=88
22+
indent = " "
23+
# by default isort don't check module indexes
24+
not_skip = __init__.py
25+
# will group `import x` and `from x import` of the same module.
26+
force_sort_within_sections = true
27+
sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
28+
default_section = THIRDPARTY
29+
known_first_party = zigpy_cc,tests
30+
forced_separate = tests
31+
combine_as_imports = true

‎setup.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Setup module for zigpy-cc"""
2+
3+
from setuptools import find_packages, setup
4+
5+
import zigpy_cc
6+
7+
setup(
8+
name="zigpy-cc",
9+
version=zigpy_cc.__version__,
10+
description="A library which communicates with Texas Instruments CC2531 radios for zigpy",
11+
url="http://github.com/sanyatuning/zigpy-cc",
12+
author="Balázs Sándor",
13+
author_email="sanyatuning@gmail.com",
14+
license="GPL-3.0",
15+
packages=find_packages(exclude=["*.tests"]),
16+
install_requires=["serial", "pyserial-asyncio", "zigpy-homeassistant>=0.10.0"],
17+
tests_require=["pytest"],
18+
)

‎tests/test_uart.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from unittest import mock
2+
3+
import pytest
4+
import serial_asyncio
5+
6+
from zigpy_cc import uart
7+
8+
9+
def eq(a, b):
10+
assert str(a) == str(b)
11+
12+
@pytest.fixture(scope="function")
13+
def gw():
14+
gw = uart.Gateway(mock.MagicMock())
15+
gw._transport = mock.MagicMock()
16+
return gw
17+
18+
19+
@pytest.mark.asyncio
20+
async def test_connect(monkeypatch):
21+
api = mock.MagicMock()
22+
portmock = mock.MagicMock()
23+
transport = mock.MagicMock()
24+
25+
async def mock_conn(loop, protocol_factory, **kwargs):
26+
protocol = protocol_factory()
27+
loop.call_soon(protocol.connection_made, transport)
28+
return None, protocol
29+
30+
monkeypatch.setattr(serial_asyncio, "create_serial_connection", mock_conn)
31+
32+
await uart.connect(portmock, 57600, api)
33+
34+
35+
def test_write(gw):
36+
data = b"\x00"
37+
gw.write(data)
38+
assert gw._transport.write.call_count == 1
39+
assert gw._transport.write.called_once_with(data)
40+
41+
42+
def test_close(gw):
43+
gw.close()
44+
assert gw._transport.close.call_count == 1
45+
46+
47+
def test_data_received_chunk_frame(gw):
48+
data = b"\xfe\x0ea\x02\x02\x00\x02\x06\x03\x90\x154\x01\x02\x00\x00\x00\x00\xda"
49+
gw.data_received(data[:-4])
50+
assert gw._api.data_received.call_count == 0
51+
gw.data_received(data[-4:])
52+
assert gw._api.data_received.call_count == 1
53+
eq(gw._api.data_received.call_args[0][0], uart.UnpiFrame(3, 1, 2, bytearray(data[4:-1]), 14, 218))
54+
55+
56+
def test_data_received_full_frame(gw):
57+
data = b"\xfe\x0ea\x02\x02\x00\x02\x06\x03\x90\x154\x01\x02\x01\x00\x00\x00\xdb"
58+
gw.data_received(data)
59+
assert gw._api.data_received.call_count == 1
60+
eq(gw._api.data_received.call_args[0][0], uart.UnpiFrame(3, 1, 2, bytearray(data[4:-1]), 14, 219))
61+
62+
63+
def test_data_received_incomplete_frame(gw):
64+
data = b"~\x00\x00"
65+
gw.data_received(data)
66+
assert gw._api.data_received.call_count == 0
67+
68+
69+
def test_data_received_runt_frame(gw):
70+
data = b"\x02\x44\xC0"
71+
gw.data_received(data)
72+
assert gw._api.data_received.call_count == 0
73+
74+
75+
def test_data_received_extra(gw):
76+
data = b"\xfe\x0ea\x02\x02\x00\x02\x06\x03\x90\x154\x01\x02\x01\x00\x00\x00\xdb\xfe\x00"
77+
gw.data_received(data)
78+
assert gw._api.data_received.call_count == 1
79+
assert gw._parser.buffer == b"\xfe\x00"
80+
81+
82+
def test_data_received_wrong_checksum(gw):
83+
data = b"\xfe\x0ea\x02\x02\x00\x02\x06\x03\x90\x154\x01\x02\x01\x00\x00\x00\xdc"
84+
gw.data_received(data)
85+
assert gw._api.data_received.call_count == 0
86+
87+
@pytest.mark.skip("TODO")
88+
def test_unescape(gw):
89+
data = b"\x00\xDB\xDC\x00\xDB\xDD\x00\x00\x00"
90+
data_unescaped = b"\x00\xC0\x00\xDB\x00\x00\x00"
91+
r = gw._unescape(data)
92+
assert r == data_unescaped
93+
94+
@pytest.mark.skip("TODO")
95+
def test_unescape_error(gw):
96+
data = b"\x00\xDB\xDC\x00\xDB\xDD\x00\x00\x00\xDB"
97+
r = gw._unescape(data)
98+
assert r is None
99+
100+
@pytest.mark.skip("TODO")
101+
def test_escape(gw):
102+
data = b"\x00\xC0\x00\xDB\x00\x00\x00"
103+
data_escaped = b"\x00\xDB\xDC\x00\xDB\xDD\x00\x00\x00"
104+
r = gw._escape(data)
105+
assert r == data_escaped
106+
107+
108+
def test_checksum():
109+
data = b"\x07\x01\x00\x08\x00\xaa\x00\x02"
110+
checksum = 166
111+
r = uart.UnpiFrame.calculate_checksum(data)
112+
assert r == checksum

‎tox.ini

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Tox (http://tox.testrun.org/) is a tool for running tests
2+
# in multiple virtualenvs. This configuration file will run the
3+
# test suite on all supported python versions. To use it, "pip install tox"
4+
# and then run "tox" from this directory.
5+
6+
[tox]
7+
envlist = py35, py36, py37, lint, black
8+
skip_missing_interpreters = True
9+
10+
[testenv]
11+
setenv = PYTHONPATH = {toxinidir}
12+
install_command = pip install {opts} {packages}
13+
commands = py.test --cov --cov-report=
14+
deps =
15+
coveralls
16+
pytest
17+
pytest-cov
18+
pytest-asyncio
19+
20+
[testenv:lint]
21+
basepython = python3
22+
deps = flake8
23+
commands = flake8
24+
25+
[testenv:black]
26+
deps=black
27+
setenv =
28+
LC_ALL=C.UTF-8
29+
LANG=C.UTF-8
30+
commands=
31+
black --check --fast {toxinidir}/zigpy_cc {toxinidir}/tests {toxinidir}/setup.py

‎zigpy_cc/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
MAJOR_VERSION = 0
22
MINOR_VERSION = 1
3-
PATCH_VERSION = '0'
4-
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
5-
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
3+
PATCH_VERSION = "0"
4+
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
5+
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)

‎zigpy_cc/api.py

+10-15
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ def close(self):
4646
return self._uart.close()
4747

4848
async def _command(self, subsystem, command, payload) -> uart.ZpiObject:
49-
cmd = self.createRequest(subsystem, command, payload)
49+
cmd = next(c for c in Definition[subsystem] if c["name"] == command)
50+
cmd = uart.ZpiObject(
51+
cmd["type"], subsystem, command, cmd["ID"], payload, cmd["request"]
52+
)
5053
LOGGER.debug("Command %s", cmd)
5154

5255
frame = cmd.to_unpi_frame()
@@ -62,8 +65,6 @@ async def _command(self, subsystem, command, payload) -> uart.ZpiObject:
6265
self._awaiting.pop(seq)
6366
raise
6467

65-
66-
6768
def data_received(self, frame):
6869
object = uart.ZpiObject.from_unpi_frame(frame)
6970
# print('data_received', object)
@@ -75,25 +76,19 @@ def data_received(self, frame):
7576
if solicited and seq in self._awaiting:
7677
fut = self._awaiting.pop(seq)
7778
fut.set_result(object)
79+
getattr(self, "_handle_%s" % (object.command,))(object)
7880

7981
async def version(self):
8082
version = await self._command(Subsystem.SYS, "version", {})
81-
# if (
82-
# self.protocol_version >= MIN_PROTO_VERSION
83-
# and (version[0] & 0x0000FF00) == 0x00000500
84-
# ):
85-
# self._aps_data_ind_flags = 0x04
83+
# todo check version
8684
return version.payload
8785

8886
def _handle_version(self, data):
89-
LOGGER.debug("Version response: %x", data[0])
90-
91-
92-
93-
87+
LOGGER.debug("Version response: %s", data.payload)
9488

9589
def createRequest(self, subsystem, command, payload):
9690
cmd = next(c for c in Definition[subsystem] if c["name"] == command)
9791

98-
return uart.ZpiObject(cmd["type"], subsystem, command, cmd["ID"], payload, cmd["request"])
99-
92+
return uart.ZpiObject(
93+
cmd["type"], subsystem, command, cmd["ID"], payload, cmd["request"]
94+
)

‎zigpy_cc/definition.py

+20-21
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,41 @@
66
Definition = {
77
Subsystem.SYS: [
88
{
9-
"name": 'version',
9+
"name": "version",
1010
"ID": 2,
1111
"type": CommandType.SREQ,
12-
"request": [
13-
],
12+
"request": [],
1413
"response": [
15-
{"name": 'transportrev', "parameterType": ParameterType.UINT8},
16-
{"name": 'product', "parameterType": ParameterType.UINT8},
17-
{"name": 'majorrel', "parameterType": ParameterType.UINT8},
18-
{"name": 'minorrel', "parameterType": ParameterType.UINT8},
19-
{"name": 'maintrel', "parameterType": ParameterType.UINT8},
20-
{"name": 'revision', "parameterType": ParameterType.UINT32},
14+
{"name": "transportrev", "parameterType": ParameterType.UINT8},
15+
{"name": "product", "parameterType": ParameterType.UINT8},
16+
{"name": "majorrel", "parameterType": ParameterType.UINT8},
17+
{"name": "minorrel", "parameterType": ParameterType.UINT8},
18+
{"name": "maintrel", "parameterType": ParameterType.UINT8},
19+
{"name": "revision", "parameterType": ParameterType.UINT32},
2120
],
2221
}
2322
],
2423
Subsystem.ZDO: [
2524
{
26-
"name": 'endDeviceAnnceInd',
25+
"name": "endDeviceAnnceInd",
2726
"ID": 193,
2827
"type": CommandType.AREQ,
2928
"request": [
30-
{"name": 'srcaddr', "parameterType": ParameterType.UINT16},
31-
{"name": 'nwkaddr', "parameterType": ParameterType.UINT16},
32-
{"name": 'ieeeaddr', "parameterType": ParameterType.IEEEADDR},
33-
{"name": 'capabilities', "parameterType": ParameterType.UINT8},
29+
{"name": "srcaddr", "parameterType": ParameterType.UINT16},
30+
{"name": "nwkaddr", "parameterType": ParameterType.UINT16},
31+
{"name": "ieeeaddr", "parameterType": ParameterType.IEEEADDR},
32+
{"name": "capabilities", "parameterType": ParameterType.UINT8},
3433
],
3534
},
3635
{
37-
"name": 'tcDeviceInd',
36+
"name": "tcDeviceInd",
3837
"ID": 202,
3938
"type": CommandType.AREQ,
4039
"request": [
41-
{"name": 'nwkaddr', "parameterType": ParameterType.UINT16},
42-
{"name": 'extaddr', "parameterType": ParameterType.IEEEADDR},
43-
{"name": 'parentaddr', "parameterType": ParameterType.UINT16},
40+
{"name": "nwkaddr", "parameterType": ParameterType.UINT16},
41+
{"name": "extaddr", "parameterType": ParameterType.IEEEADDR},
42+
{"name": "parentaddr", "parameterType": ParameterType.UINT16},
4443
],
45-
}
46-
]
47-
}
44+
},
45+
],
46+
}

0 commit comments

Comments
 (0)
Please sign in to comment.