diff --git a/pyproject.toml b/pyproject.toml index e322a90..8bec19f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ license = {text = "GPL-3.0"} requires-python = ">=3.8" dependencies = [ "voluptuous", - "zigpy>=0.60.0", + "zigpy>=0.60.2", 'async-timeout; python_version<"3.11"', ] diff --git a/tests/test_api.py b/tests/test_api.py index 2edfe10..81efb01 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -993,3 +993,17 @@ async def test_cb3_device_state_callback_bug(api, mock_command_rsp): await asyncio.sleep(0.01) assert api._device_state == device_state + + +def test_get_command_priority(api): + assert ( + api._get_command_priority( + deconz_api.Command(command_id=deconz_api.CommandId.write_parameter) + ) + > api._get_command_priority( + deconz_api.Command(command_id=deconz_api.CommandId.update_neighbor) + ) + > api._get_command_priority( + deconz_api.Command(command_id=deconz_api.CommandId.aps_data_request) + ) + ) diff --git a/zigpy_deconz/api.py b/zigpy_deconz/api.py index 226bfe1..0c048af 100644 --- a/zigpy_deconz/api.py +++ b/zigpy_deconz/api.py @@ -14,6 +14,7 @@ from asyncio import timeout as asyncio_timeout # pragma: no cover from zigpy.config import CONF_DEVICE_PATH +from zigpy.datastructures import PriorityLock from zigpy.types import ( APSStatus, Bool, @@ -416,7 +417,7 @@ def __init__(self, app: Callable, device_config: dict[str, Any]): """Init instance.""" self._app = app self._awaiting = {} - self._command_lock = asyncio.Lock() + self._command_lock = PriorityLock() self._config = device_config self._device_state = DeviceState( network_state=NetworkState2.OFFLINE, @@ -486,6 +487,16 @@ def close(self): self._uart.close() self._uart = None + def _get_command_priority(self, command: Command) -> int: + return { + # The watchdog is fed using `write_parameter` and `get_device_state` so they + # must take priority + CommandId.write_parameter: 999, + CommandId.device_state: 999, + # APS data requests are retried and can be deprioritized + CommandId.aps_data_request: -1, + }.get(command.command_id, 0) + async def _command(self, cmd, **kwargs): payload = [] tx_schema, _ = COMMAND_SCHEMAS[cmd] @@ -547,7 +558,7 @@ async def _command(self, cmd, **kwargs): # connection was lost raise CommandError(Status.ERROR, "API is not running") - async with self._command_lock: + async with self._command_lock(priority=self._get_command_priority(command)): seq = self._seq LOGGER.debug("Sending %s%s (seq=%s)", cmd, kwargs, seq) diff --git a/zigpy_deconz/zigbee/application.py b/zigpy_deconz/zigbee/application.py index 1b3185e..ffad2fc 100644 --- a/zigpy_deconz/zigbee/application.py +++ b/zigpy_deconz/zigbee/application.py @@ -64,7 +64,7 @@ class ControllerApplication(zigpy.application.ControllerApplication): {zigpy.config.CONF_DEVICE_BAUDRATE: 115200}, ] - _watchdog_period = 600 * 0.75 + _watchdog_period = 30 def __init__(self, config: dict[str, Any]): """Initialize instance.""" @@ -85,7 +85,7 @@ async def _watchdog_feed(self): and self._api.firmware_version <= 0x26450900 ): await self._api.write_parameter( - NetworkParameter.watchdog_ttl, int(self._watchdog_period / 0.75) + NetworkParameter.watchdog_ttl, int(2 * self._watchdog_period) ) else: await self._api.get_device_state()