diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa96b81..4e81b50 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,35 +1,34 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v6.0.0 hooks: - id: debug-statements - repo: https://github.com/psf/black - rev: 23.10.1 + rev: 26.3.1 hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.3.0 hooks: - id: flake8 - entry: pflake8 - additional_dependencies: - - pyproject-flake8==6.1.0 - - flake8-bugbear==23.1.20 - - flake8-comprehensions==3.10.1 - - flake8_2020==1.7.0 + additional_dependencies: + - Flake8-pyproject + - flake8-bugbear==25.11.29 + - flake8-comprehensions==3.17.0 + - flake8_2020==1.8.1 - mccabe==0.7.0 - - pycodestyle==2.11.1 - - pyflakes==3.1.0 + - pycodestyle==2.14.0 + - pyflakes==3.4.0 - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 8.0.1 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.6.1 + rev: v1.20.1 hooks: - id: mypy additional_dependencies: @@ -37,7 +36,7 @@ repos: - types-setuptools - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + rev: v3.21.2 hooks: - id: pyupgrade diff --git a/pyproject.toml b/pyproject.toml index 46749e9..c7fd9af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,13 +12,12 @@ authors = [ ] readme = "README.md" license = {text = "GPL-3.0"} -requires-python = ">=3.8" +requires-python = ">=3.11" dependencies = [ "voluptuous", - "zigpy>=0.70.0", + "zigpy>=1.3.0", "pyusb>=1.1.0", "gpiozero", - 'async-timeout; python_version<"3.11"', ] [tool.setuptools.packages.find] diff --git a/tests/test_api.py b/tests/test_api.py index 20ad188..38b8f56 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2,7 +2,6 @@ from unittest.mock import AsyncMock, MagicMock, patch, sentinel import pytest -import serial_asyncio_fast import zigpy.config as config from zigpy_zigate import api as zigate_api @@ -32,7 +31,7 @@ async def mock_conn(loop, protocol_factory, **kwargs): loop.call_soon(protocol.connection_made, None) return None, protocol - monkeypatch.setattr(serial_asyncio_fast, "create_serial_connection", mock_conn) + monkeypatch.setattr(zigpy_zigate.uart, "create_serial_connection", mock_conn) await api.connect() diff --git a/tests/test_application.py b/tests/test_application.py index b587eb4..b786746 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -229,7 +229,7 @@ async def test_energy_scanning(app, caplog): channels=zigpy_t.Channels.ALL_CHANNELS, duration_exp=2, count=5 ) - assert scan_results == {c: 0 for c in zigpy_t.Channels.ALL_CHANNELS} + assert scan_results == dict.fromkeys(zigpy_t.Channels.ALL_CHANNELS, 0) # We never send a request when scanning assert len(app._api.raw_aps_data_request.mock_calls) == 0 diff --git a/tests/test_types.py b/tests/test_types.py index 1c43097..9c9612b 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -5,7 +5,7 @@ def test_deserialize(): - extra = b"\xBE\xEF" + extra = b"\xbe\xef" data = b"\x00\x01\x00\x02" schema = RESPONSES[0x8000] result, rest = t.deserialize(data + extra, schema) @@ -15,7 +15,7 @@ def test_deserialize(): assert result[2] == 0x0002 assert result[3] == extra - extra = b"\xBE\xEF" + extra = b"\xbe\xef" data = b"\x00\x00\x01\x00\x01\x01\x01\x02\x12\x34\x02\xab\xcd\x01\x00" schema = RESPONSES[0x8002] result, rest = t.deserialize(data + extra, schema) @@ -26,7 +26,7 @@ def test_deserialize(): assert result[4] == 0x01 assert result[5] == t.Address(address_mode=t.AddressMode.NWK, address=t.NWK(0x1234)) assert result[6] == t.Address(address_mode=t.AddressMode.NWK, address=t.NWK(0xABCD)) - assert result[7] == b"\x01\x00\xBE\xEF" + assert result[7] == b"\x01\x00\xbe\xef" assert rest == b"" data = b"\x00\x01\x01\x02\x12\x34\xff" diff --git a/tests/test_uart.py b/tests/test_uart.py index bd39def..4f9740b 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -2,8 +2,7 @@ import gpiozero import pytest -import serial.tools.list_ports -import serial_asyncio_fast +from serialx import SerialPortInfo import zigpy.config from zigpy_zigate import common, uart @@ -32,7 +31,7 @@ async def mock_conn(loop, protocol_factory, url, **kwargs): assert url.startswith("/") is True return None, protocol - monkeypatch.setattr(serial_asyncio_fast, "create_serial_connection", mock_conn) + monkeypatch.setattr(uart, "create_serial_connection", mock_conn) monkeypatch.setattr(common, "set_pizigate_running_mode", AsyncMock()) DEVICE_CONFIG = zigpy.config.SCHEMA_DEVICE({zigpy.config.CONF_DEVICE_PATH: port}) @@ -81,7 +80,7 @@ def test_data_received_incomplete_frame(gw): def test_data_received_runt_frame(gw): - data = b"\x02\x44\xC0" + data = b"\x02\x44\xc0" gw.data_received(data) assert gw._api.data_received.call_count == 0 @@ -137,16 +136,21 @@ def test_is_not_pizigate(): def test_is_zigatedin(monkeypatch): - def mock_grep(*args, **kwargs): - device = MagicMock() - device.description = "ZiGate" - device.manufacturer = "FTDI" - return iter([device]) - - monkeypatch.setattr(serial.tools.list_ports, "grep", mock_grep) port = "/dev/ttyUSB1" - r = common.is_zigate_din(port) - assert r is True + device = SerialPortInfo( + device=port, + resolved_device=port, + vid=None, + pid=None, + serial_number=None, + manufacturer="FTDI", + product="ZiGate", + bcd_device=None, + interface_description=None, + interface_num=None, + ) + monkeypatch.setattr(common, "list_serial_ports", lambda: [device]) + assert common.is_zigate_din(port) is True @pytest.mark.parametrize( @@ -154,15 +158,20 @@ def mock_grep(*args, **kwargs): ("/dev/ttyUSB1", "/dev/ttyAMA0", "/dev/serial0"), ) def test_is_not_zigatedin(port, monkeypatch): - def mock_grep(*args, **kwargs): - device = MagicMock() - device.description = "Other" - device.manufacturer = "FTDI" - return iter([device]) - - monkeypatch.setattr(serial.tools.list_ports, "grep", mock_grep) - r = common.is_zigate_din(port) - assert r is False + device = SerialPortInfo( + device=port, + resolved_device=port, + vid=None, + pid=None, + serial_number=None, + manufacturer="FTDI", + product="Other", + bcd_device=None, + interface_description=None, + interface_num=None, + ) + monkeypatch.setattr(common, "list_serial_ports", lambda: [device]) + assert common.is_zigate_din(port) is False def test_is_zigate_wifi(): diff --git a/zigpy_zigate/common.py b/zigpy_zigate/common.py index 8322652..3469c95 100644 --- a/zigpy_zigate/common.py +++ b/zigpy_zigate/common.py @@ -5,8 +5,7 @@ import time from gpiozero import OutputDevice -import serial -import serial.tools.list_ports +from serialx import SerialException, list_serial_ports LOGGER = logging.getLogger(__name__) @@ -35,18 +34,19 @@ def __init__( def discover_port(): """discover zigate port""" - devices = list(serial.tools.list_ports.grep("ZiGate")) + ports = list_serial_ports() + devices = [p for p in ports if p.product and "ZiGate" in p.product] if devices: port = devices[0].device LOGGER.info("ZiGate found at %s", port) else: - devices = list(serial.tools.list_ports.grep("067b:2303|CP2102")) + devices = [p for p in ports if re.search(r"067b:2303|CP2102", p.product or "")] if devices: port = devices[0].device LOGGER.info("ZiGate probably found at %s", port) else: LOGGER.error("Unable to find ZiGate using auto mode") - raise serial.SerialException("Unable to find Zigate using auto mode") + raise SerialException("Unable to find Zigate using auto mode") return port @@ -63,12 +63,10 @@ def is_zigate_din(port): """detect zigate din""" port = os.path.realpath(port) if re.match(r"/dev/ttyUSB\d+", port): - try: - device = next(serial.tools.list_ports.grep(port)) - # Suppose zigate din /dev/ttyUSBx - return device.description == "ZiGate" and device.manufacturer == "FTDI" - except StopIteration: - pass + for device in list_serial_ports(): + if device.device == port: + # Suppose zigate din /dev/ttyUSBx + return device.product == "ZiGate" and device.manufacturer == "FTDI" return False diff --git a/zigpy_zigate/tools/cli.py b/zigpy_zigate/tools/cli.py index 11003c3..0e413f2 100644 --- a/zigpy_zigate/tools/cli.py +++ b/zigpy_zigate/tools/cli.py @@ -1,5 +1,5 @@ """ - Simple CLI ZiGate tool +Simple CLI ZiGate tool """ import argparse diff --git a/zigpy_zigate/uart.py b/zigpy_zigate/uart.py index 0ecdda7..929e87e 100644 --- a/zigpy_zigate/uart.py +++ b/zigpy_zigate/uart.py @@ -7,14 +7,14 @@ from typing import Any import zigpy.config -import zigpy.serial +from zigpy.serial import SerialProtocol, create_serial_connection from . import common as c LOGGER = logging.getLogger(__name__) -class Gateway(zigpy.serial.SerialProtocol): +class Gateway(SerialProtocol): START = b"\x01" END = b"\x03" @@ -126,7 +126,7 @@ async def connect(device_config: dict[str, Any], api, loop=None): await c.async_set_zigatedin_running_mode() protocol = Gateway(api) - _, protocol = await zigpy.serial.create_serial_connection( + _, protocol = await create_serial_connection( loop, lambda: protocol, url=port, diff --git a/zigpy_zigate/zigbee/application.py b/zigpy_zigate/zigbee/application.py index 9bbf6ee..7011367 100644 --- a/zigpy_zigate/zigbee/application.py +++ b/zigpy_zigate/zigbee/application.py @@ -202,7 +202,7 @@ async def energy_scan( """Runs an energy detection scan and returns the per-channel scan results.""" LOGGER.warning("Coordinator does not support energy scanning") - return {c: 0 for c in channels} + return dict.fromkeys(channels, 0) async def force_remove(self, dev): await self._api.remove_device(self.state.node_info.ieee, dev.ieee)