Skip to content

Commit

Permalink
Sync up with zigpy 0.60.0
Browse files Browse the repository at this point in the history
  • Loading branch information
puddly committed Nov 16, 2023
1 parent 626bce6 commit 60ce36a
Show file tree
Hide file tree
Showing 8 changed files with 32 additions and 141 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ readme = "README.md"
license = {text = "GPL-3.0"}
requires-python = ">=3.8"
dependencies = [
"zigpy>=0.56.0",
"zigpy>=0.60.0",
]

[tool.setuptools.packages.find]
Expand Down
55 changes: 6 additions & 49 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
"""Tests for API."""

import asyncio
import logging

import pytest
import serial
import zigpy.config
import zigpy.exceptions
import zigpy.types as t

from zigpy_xbee import api as xbee_api, types as xbee_t, uart
import zigpy_xbee.config
from zigpy_xbee.exceptions import ATCommandError, ATCommandException, InvalidCommand
from zigpy_xbee.zigbee.application import ControllerApplication

import tests.async_mock as mock

DEVICE_CONFIG = zigpy_xbee.config.SCHEMA_DEVICE(
{zigpy_xbee.config.CONF_DEVICE_PATH: "/dev/null"}
DEVICE_CONFIG = zigpy.config.SCHEMA_DEVICE(
{
zigpy.config.CONF_DEVICE_PATH: "/dev/null",
zigpy.config.CONF_DEVICE_BAUDRATE: 57600,
}
)


Expand Down Expand Up @@ -602,51 +604,6 @@ def test_handle_many_to_one_rri(api):
api._handle_many_to_one_rri(ieee, nwk, 0)


async def test_reconnect_multiple_disconnects(monkeypatch, caplog):
"""Test reconnect with multiple disconnects."""
api = xbee_api.XBee(DEVICE_CONFIG)
connect_mock = mock.AsyncMock(return_value=True)
monkeypatch.setattr(uart, "connect", connect_mock)

await api.connect()

caplog.set_level(logging.DEBUG)
connect_mock.reset_mock()
connect_mock.side_effect = [OSError, mock.sentinel.uart_reconnect]
api.connection_lost("connection lost")
await asyncio.sleep(0.3)
api.connection_lost("connection lost 2")
await asyncio.sleep(0.3)

assert "Cancelling reconnection attempt" in caplog.messages
assert api._uart is mock.sentinel.uart_reconnect
assert connect_mock.call_count == 2


async def test_reconnect_multiple_attempts(monkeypatch, caplog):
"""Test reconnect with multiple attempts."""
api = xbee_api.XBee(DEVICE_CONFIG)
connect_mock = mock.AsyncMock(return_value=True)
monkeypatch.setattr(uart, "connect", connect_mock)

await api.connect()

caplog.set_level(logging.DEBUG)
connect_mock.reset_mock()
connect_mock.side_effect = [
asyncio.TimeoutError,
OSError,
mock.sentinel.uart_reconnect,
]

with mock.patch("asyncio.sleep"):
api.connection_lost("connection lost")
await api._conn_lost_task

assert api._uart is mock.sentinel.uart_reconnect
assert connect_mock.call_count == 3


@mock.patch.object(xbee_api.XBee, "_at_command", new_callable=mock.AsyncMock)
@mock.patch.object(uart, "connect", return_value=mock.MagicMock())
async def test_probe_success(mock_connect, mock_at_cmd):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import asyncio

import pytest
import zigpy.config as config
import zigpy.exceptions
import zigpy.state
import zigpy.types as t
import zigpy.zdo
import zigpy.zdo.types as zdo_t

from zigpy_xbee.api import XBee
import zigpy_xbee.config as config
from zigpy_xbee.exceptions import InvalidCommand
import zigpy_xbee.types as xbee_t
from zigpy_xbee.zigbee import application
Expand Down
9 changes: 6 additions & 3 deletions tests/test_uart.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@

import pytest
import serial_asyncio
import zigpy.config

from zigpy_xbee import uart
import zigpy_xbee.config

DEVICE_CONFIG = zigpy_xbee.config.SCHEMA_DEVICE(
{zigpy_xbee.config.CONF_DEVICE_PATH: "/dev/null"}
DEVICE_CONFIG = zigpy.config.SCHEMA_DEVICE(
{
zigpy.config.CONF_DEVICE_PATH: "/dev/null",
zigpy.config.CONF_DEVICE_BAUDRATE: 57600,
}
)


Expand Down
58 changes: 4 additions & 54 deletions zigpy_xbee/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from typing import Any, Dict, Optional

import serial
from zigpy.config import CONF_DEVICE_PATH, SCHEMA_DEVICE
from zigpy.exceptions import APIException, DeliveryError
import zigpy.types as t

import zigpy_xbee
from zigpy_xbee.config import CONF_DEVICE_BAUDRATE, CONF_DEVICE_PATH, SCHEMA_DEVICE
from zigpy_xbee.exceptions import (
ATCommandError,
ATCommandException,
Expand Down Expand Up @@ -287,7 +287,6 @@ def __init__(self, device_config: Dict[str, Any]) -> None:
self._awaiting = {}
self._app = None
self._cmd_mode_future: Optional[asyncio.Future] = None
self._conn_lost_task: Optional[asyncio.Task] = None
self._reset: asyncio.Event = asyncio.Event()
self._running: asyncio.Event = asyncio.Event()

Expand Down Expand Up @@ -323,64 +322,15 @@ async def connect(self) -> None:
assert self._uart is None
self._uart = await uart.connect(self._config, self)

def reconnect(self):
"""Reconnect using saved parameters."""
LOGGER.debug(
"Reconnecting '%s' serial port using %s",
self._config[CONF_DEVICE_PATH],
self._config[CONF_DEVICE_BAUDRATE],
)
return self.connect()

def connection_lost(self, exc: Exception) -> None:
"""Lost serial connection."""
LOGGER.warning(
"Serial '%s' connection lost unexpectedly: %s",
self._config[CONF_DEVICE_PATH],
exc,
)
self._uart = None
if self._conn_lost_task and not self._conn_lost_task.done():
self._conn_lost_task.cancel()
self._conn_lost_task = asyncio.create_task(self._connection_lost())

async def _connection_lost(self) -> None:
"""Reconnect serial port."""
try:
await self._reconnect_till_done()
except asyncio.CancelledError:
LOGGER.debug("Cancelling reconnection attempt")
raise

async def _reconnect_till_done(self) -> None:
attempt = 1
while True:
try:
await asyncio.wait_for(self.reconnect(), timeout=10)
break
except (asyncio.TimeoutError, OSError) as exc:
wait = 2 ** min(attempt, 5)
attempt += 1
LOGGER.debug(
"Couldn't re-open '%s' serial port, retrying in %ss: %s",
self._config[CONF_DEVICE_PATH],
wait,
str(exc),
)
await asyncio.sleep(wait)
if self._app is not None:
self._app.connection_lost(exc)

LOGGER.debug(
"Reconnected '%s' serial port after %s attempts",
self._config[CONF_DEVICE_PATH],
attempt,
)
self.close()

def close(self):
"""Close the connection."""
if self._conn_lost_task:
self._conn_lost_task.cancel()
self._conn_lost_task = None

if self._uart:
self._uart.close()
self._uart = None
Expand Down
19 changes: 0 additions & 19 deletions zigpy_xbee/config.py

This file was deleted.

7 changes: 3 additions & 4 deletions zigpy_xbee/uart.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import logging
from typing import Any, Dict

import zigpy.config
import zigpy.serial

from zigpy_xbee.config import CONF_DEVICE_BAUDRATE, CONF_DEVICE_PATH

LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -178,8 +177,8 @@ async def connect(device_config: Dict[str, Any], api, loop=None) -> Gateway:
transport, protocol = await zigpy.serial.create_serial_connection(
loop,
lambda: protocol,
url=device_config[CONF_DEVICE_PATH],
baudrate=device_config[CONF_DEVICE_BAUDRATE],
url=device_config[zigpy.config.CONF_DEVICE_PATH],
baudrate=device_config[zigpy.config.CONF_DEVICE_BAUDRATE],
xonxoff=False,
)

Expand Down
21 changes: 11 additions & 10 deletions zigpy_xbee/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import zigpy.application
import zigpy.config
from zigpy.config import CONF_DEVICE
import zigpy.device
import zigpy.exceptions
import zigpy.quirks
Expand All @@ -22,7 +23,6 @@

import zigpy_xbee
import zigpy_xbee.api
from zigpy_xbee.config import CONF_DEVICE, CONFIG_SCHEMA, SCHEMA_DEVICE
from zigpy_xbee.exceptions import InvalidCommand
from zigpy_xbee.types import EUI64, UNKNOWN_IEEE, UNKNOWN_NWK, TXOptions, TXStatus

Expand All @@ -42,17 +42,15 @@
class ControllerApplication(zigpy.application.ControllerApplication):
"""Implementation of Zigpy ControllerApplication for XBee devices."""

SCHEMA = CONFIG_SCHEMA
SCHEMA_DEVICE = SCHEMA_DEVICE

probe = zigpy_xbee.api.XBee.probe

def __init__(self, config: dict[str, Any]):
"""Initialize instance."""
super().__init__(config=zigpy.config.ZIGPY_SCHEMA(config))
self._api: zigpy_xbee.api.XBee | None = None
self.topology.add_listener(self)

async def _watchdog_feed(self):
await self._api._at_command("VR")

async def disconnect(self):
"""Shutdown application."""
if self._api:
Expand Down Expand Up @@ -136,6 +134,13 @@ async def load_network_info(self, *, load_devices=False):
LOGGER.warning("CE command failed, assuming node is coordinator")
node_info.logical_type = zdo_t.LogicalType.Coordinator

# TODO: Feature detect the XBee's exact model
node_info.model = "XBee"
node_info.manufacturer = "Digi"

version = await self._api._at_command("VR")
node_info.version = f"{int(version):#06x}"

# Load network info
pan_id = await self._api._at_command("OI")
extended_pan_id = await self._api._at_command("ID")
Expand Down Expand Up @@ -345,10 +350,6 @@ async def permit_with_link_key(
# 1 = Install Code With CRC (I? command of the joining device)
await self._api.register_joining_device(node, reserved, key_type, link_key)

async def permit_with_key(self, node: EUI64, code: bytes, time_s=500):
"""Permits a new device to join with the given IEEE and Install Code."""
await self.permit_with_link_key(node, code, time_s, key_type=1)

def handle_modem_status(self, status):
"""Handle changed Modem Status of the device."""
LOGGER.info("Modem status update: %s (%s)", status.name, status.value)
Expand Down

0 comments on commit 60ce36a

Please sign in to comment.