Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
author_email="sanyatuning@gmail.com",
license="GPL-3.0",
packages=find_packages(exclude=["*.tests"]),
install_requires=["pyserial-asyncio", "zigpy-homeassistant>=0.10.0"],
tests_require=["pytest", "pytest-asyncio", "zha-quirks"],
install_requires=["pyserial-asyncio", "zigpy>=0.20.a1"],
tests_require=["asynctest", "pytest", "pytest-asyncio"],
)
32 changes: 24 additions & 8 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import asyncio
from unittest import mock
from asynctest import mock

import pytest

import zigpy_cc.api
import zigpy_cc.exception
from zigpy_cc import types as t, uart
import zigpy_cc.api
import zigpy_cc.config
from zigpy_cc.definition import Definition
import zigpy_cc.exception
from zigpy_cc.uart import UnpiFrame
import zigpy_cc.uart
from zigpy_cc.zpi_object import ZpiObject

DEVICE_CONFIG = zigpy_cc.config.SCHEMA_DEVICE(
{zigpy_cc.config.CONF_DEVICE_PATH: "/dev/null"}
)


@pytest.fixture
def api():
api = zigpy_cc.api.API()
api = zigpy_cc.api.API(DEVICE_CONFIG)
api._uart = mock.MagicMock()
return api

Expand All @@ -25,18 +31,18 @@ def test_set_application(api):

@pytest.mark.asyncio
async def test_connect(monkeypatch):
api = zigpy_cc.api.API()
dev = mock.MagicMock()
api = zigpy_cc.api.API(DEVICE_CONFIG)
monkeypatch.setattr(
uart, "connect", mock.MagicMock(side_effect=asyncio.coroutine(mock.MagicMock()))
)
await api.connect(dev, 115200)
await api.connect()


def test_close(api):
api._uart.close = mock.MagicMock()
uart = api._uart
api.close()
assert api._uart.close.call_count == 1
assert uart.close.call_count == 1


@pytest.mark.skip("TODO")
Expand Down Expand Up @@ -221,3 +227,13 @@ async def asd():
#
# def test_handle_version(api):
# api._handle_version([mock.sentinel.version])


@pytest.mark.asyncio
@mock.patch.object(zigpy_cc.uart, "connect")
async def test_api_new(conn_mck):
"""Test new class method."""
api = await zigpy_cc.api.API.new(mock.sentinel.application, DEVICE_CONFIG)
assert isinstance(api, zigpy_cc.api.API)
assert conn_mck.call_count == 1
assert conn_mck.await_count == 1
23 changes: 17 additions & 6 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@
from unittest import mock

import pytest

import zigpy.zdo.types as zdo_t
import zigpy_cc.zigbee.application as application
from zigpy.types import EUI64
import zigpy.zdo.types as zdo_t

from zigpy_cc import types as t
from zigpy_cc.api import API
import zigpy_cc.config as config
import zigpy_cc.zigbee.application as application
from zigpy_cc.zpi_object import ZpiObject

APP_CONFIG = {
config.CONF_DEVICE: {
config.CONF_DEVICE_PATH: "/dev/null",
config.CONF_DEVICE_BAUDRATE: 115200,
},
config.CONF_DATABASE: None,
}


@pytest.fixture
def app(monkeypatch, database_file=None):
app = application.ControllerApplication(API(), database_file=database_file)
def app():
app = application.ControllerApplication(APP_CONFIG)
app._api = API(APP_CONFIG[config.CONF_DEVICE])
app._api.set_application(app)
return app


Expand Down Expand Up @@ -228,7 +239,7 @@ async def nested():
await asyncio.sleep(0)
app._api.data_received(frame)

await asyncio.wait([device.get_node_descriptor(), nested(),], timeout=0.2)
await asyncio.wait([device.get_node_descriptor(), nested()], timeout=0.2)

assert isinstance(device.node_desc, zdo_t.NodeDescriptor)
assert 1234 == device.node_desc.manufacturer_code
Expand Down
29 changes: 26 additions & 3 deletions tests/test_uart.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from unittest import mock
from asynctest import mock

import pytest
import serial_asyncio

from zigpy_cc import uart
import zigpy_cc.config

DEVICE_CONFIG = zigpy_cc.config.SCHEMA_DEVICE(
{zigpy_cc.config.CONF_DEVICE_PATH: "/dev/null"}
)


def eq(a, b):
Expand All @@ -20,7 +25,6 @@ def gw():
@pytest.mark.asyncio
async def test_connect(monkeypatch):
api = mock.MagicMock()
portmock = mock.MagicMock()
transport = mock.MagicMock()

async def mock_conn(loop, protocol_factory, **kwargs):
Expand All @@ -30,7 +34,7 @@ async def mock_conn(loop, protocol_factory, **kwargs):

monkeypatch.setattr(serial_asyncio, "create_serial_connection", mock_conn)

await uart.connect(portmock, 57600, api)
await uart.connect(DEVICE_CONFIG, api)


def test_write(gw):
Expand Down Expand Up @@ -123,3 +127,22 @@ def test_checksum():
checksum = 166
r = uart.UnpiFrame.calculate_checksum(data)
assert r == checksum


@pytest.mark.asyncio
@pytest.mark.parametrize(
"control, xonxoff, rtscts",
(("software", True, False), ("hardware", False, True), (None, False, False)),
)
@mock.patch.object(serial_asyncio, "create_serial_connection")
async def test_flow_control(conn_mock, control, xonxoff, rtscts):
async def set_connected(loop, proto_factory, **kwargs):
proto_factory()._connected_future.set_result(True)
return mock.sentinel.a, mock.MagicMock()

conn_mock.side_effect = set_connected
await uart.connect(
{**DEVICE_CONFIG, zigpy_cc.config.CONF_FLOW_CONTROL: control}, mock.MagicMock()
)
assert conn_mock.call_args[1]["xonxoff"] == xonxoff
assert conn_mock.call_args[1]["rtscts"] == rtscts
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
# and then run "tox" from this directory.

[tox]
envlist = py35, py36, py37, lint, black
envlist = py36, py37, lint, black
skip_missing_interpreters = True

[testenv]
setenv = PYTHONPATH = {toxinidir}
install_command = pip install {opts} {packages}
commands = py.test --cov --cov-report=
deps =
asynctest
coveralls
pytest
pytest-cov
Expand Down
33 changes: 18 additions & 15 deletions zigpy_cc/api.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import asyncio
import logging
from typing import List
from typing import Any, Dict, List

from zigpy_cc.exception import CommandError

from . import uart
from .definition import Definition
from .types import Repr, Subsystem, CommandType, Timeouts
from .types import CommandType, Repr, Subsystem, Timeouts
from .zpi_object import ZpiObject

LOGGER = logging.getLogger(__name__)

COMMAND_TIMEOUT = 3
CC_BAUDRATE = 115200


class Matcher(Repr):
Expand Down Expand Up @@ -63,34 +63,37 @@ def match(self, obj: ZpiObject):


class API:
def __init__(self):
def __init__(self, device_config: Dict[str, Any]):
self._uart = None
self._config = device_config
self._seq = 1
self._waiters: List[Waiter] = []
self._app = None
self._device = None
self._baudrare = None
self._proto_ver = None

@property
def protocol_version(self):
"""Protocol Version."""
return self._proto_ver

@classmethod
async def new(cls, application, config: Dict[str, Any]) -> "API":
api = cls(config)
await api.connect()
api.set_application(application)
return api

def set_application(self, app):
self._app = app

async def connect(self, device, baudrate=CC_BAUDRATE):
async def connect(self):
assert self._uart is None
self._device = device
self._baudrare = baudrate
self._uart = await uart.connect(device, baudrate, self)

async def reconnect(self):
self._uart = await uart.connect(self._device, self._baudrare, self)
self._uart = await uart.connect(self._config, self)

def close(self):
return self._uart.close()
if self._uart:
self._uart.close()
self._uart = None

def connection_lost(self):
self._app.connection_lost()
Expand Down Expand Up @@ -136,7 +139,7 @@ async def request_raw(self, obj: ZpiObject, expectedStatus=None):
"transid": obj.payload["transid"],
}
waiter = self.wait_for(
CommandType.AREQ, Subsystem.AF, "dataConfirm", payload,
CommandType.AREQ, Subsystem.AF, "dataConfirm", payload
)
LOGGER.debug("waiting for dataConfirm")
result = await waiter.wait()
Expand Down
25 changes: 25 additions & 0 deletions zigpy_cc/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import voluptuous as vol
from zigpy.config import ( # noqa: F401 pylint: disable=unused-import
CONF_DATABASE,
CONF_DEVICE,
CONF_DEVICE_PATH,
CONFIG_SCHEMA,
cv_boolean,
)

CONF_DEVICE_BAUDRATE = "baudrate"
CONF_DEVICE_BAUDRATE_DEFAULT = 115200
CONF_FLOW_CONTROL = "flow_control"
CONF_FLOW_CONTROL_DEFAULT = None

SCHEMA_DEVICE = vol.Schema(
{
vol.Required(CONF_DEVICE_PATH): vol.Any(vol.PathExists(), "auto"),
vol.Optional(CONF_DEVICE_BAUDRATE, default=CONF_DEVICE_BAUDRATE_DEFAULT): int,
vol.Optional(CONF_FLOW_CONTROL, default=CONF_FLOW_CONTROL_DEFAULT): vol.In(
("hardware", "software", None)
),
}
)

CONFIG_SCHEMA = CONFIG_SCHEMA.extend({vol.Required(CONF_DEVICE): SCHEMA_DEVICE})
20 changes: 14 additions & 6 deletions zigpy_cc/uart.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import asyncio
import logging
from typing import Any, Dict

import serial
import serial.tools.list_ports
import serial_asyncio

from zigpy_cc.config import CONF_DEVICE_BAUDRATE, CONF_DEVICE_PATH, CONF_FLOW_CONTROL
import zigpy_cc.types as t

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -76,9 +78,7 @@ def from_buffer(cls, length, fcs_position, buffer):
if checksum == fcs:
return cls(command_type, subsystem, command_id, data, length, fcs)
else:
LOGGER.warning(
"Invalid checksum: 0x%s, data: 0x%s", checksum, buffer,
)
LOGGER.warning("Invalid checksum: 0x%s, data: 0x%s", checksum, buffer)
return None

@staticmethod
Expand Down Expand Up @@ -152,20 +152,28 @@ def connection_lost(self, exc):
self._api.connection_lost()


async def connect(port, baudrate, api, loop=None):
async def connect(config: Dict[str, Any], api, loop=None) -> Gateway:
if loop is None:
loop = asyncio.get_event_loop()

connected_future = loop.create_future()
protocol = Gateway(api, connected_future)

port, baudrate = config[CONF_DEVICE_PATH], config[CONF_DEVICE_BAUDRATE]
if port == "auto":
devices = list(serial.tools.list_ports.grep("0451:"))
if devices:
port = devices[0].device
LOGGER.info("%s found at %s", devices[0].product, port)
else:
LOGGER.error("Unable to find TI CC device using auto mode")
raise serial.SerialException("Unable to find TI CC device using auto mode")

xonxoff, rtscts = False, False
if config[CONF_FLOW_CONTROL] == "hardware":
xonxoff, rtscts = False, True
elif config[CONF_FLOW_CONTROL] == "software":
xonxoff, rtscts = True, False

_, protocol = await serial_asyncio.create_serial_connection(
loop,
Expand All @@ -174,8 +182,8 @@ async def connect(port, baudrate, api, loop=None):
baudrate=baudrate,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
xonxoff=False,
rtscts=False,
xonxoff=xonxoff,
rtscts=rtscts,
)

await connected_future
Expand Down
Loading