Add Device.supports_module() and stop querying HomeKit for iot devices (#1808)

Some legacy iot firmware stops responding when an unsupported module follows `system` in a request, which made every update time out once the HomeKit query was added.

This stops querying HomeKit in `update()` for IOT devices and adds `Device.supports_module()` API to allow checking if some module is supported by the device.
This commit is contained in:
Teemu R.
2026-10-24 16:22:33 +02:00
committed by GitHub
parent e29864bfdc
commit 30f5e4a1b9
6 changed files with 130 additions and 18 deletions

View File

@@ -19,13 +19,13 @@ import inspect
import logging
from collections.abc import Callable, Mapping, Sequence
from datetime import datetime, timedelta, tzinfo
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any, ClassVar, cast
from warnings import warn
from ..device import Device, DeviceInfo, WifiNetwork
from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig
from ..exceptions import KasaException, UnsupportedDeviceError
from ..exceptions import KasaException, TimeoutError, UnsupportedDeviceError
from ..feature import Feature
from ..module import Module
from ..modulemapping import ModuleMapping, ModuleName
@@ -182,6 +182,13 @@ class IotDevice(Device):
emeter_type = "emeter"
#: Modules that are only queried by supports_module(), not by update().
_OPTIONAL_MODULES: ClassVar[
dict[str | ModuleName[Any], tuple[type[IotModule], str]]
] = {
Module.IotHomeKit: (HomeKit, "smartlife.iot.homekit"),
}
def __init__(
self,
host: str,
@@ -198,6 +205,7 @@ class IotDevice(Device):
self._children: Mapping[str, IotDevice] = {}
self._modules: dict[str | ModuleName[Module], IotModule] = {}
self._on_since: datetime | None = None
self._optional_module_support: dict[str | ModuleName[Module], bool] = {}
@property
def children(self) -> Sequence[IotDevice]:
@@ -330,8 +338,6 @@ class IotDevice(Device):
async def _initialize_modules(self) -> None:
"""Initialize modules not added in init."""
self.add_module(Module.IotHomeKit, HomeKit(self, "smartlife.iot.homekit"))
if self.has_emeter:
_LOGGER.debug(
"The device has emeter, querying its information along sysinfo"
@@ -444,6 +450,27 @@ class IotDevice(Device):
self._supported_modules = supported
async def supports_module(self, module: ModuleName[Module] | str) -> bool:
"""Return True if the device supports the module.
Optional modules are checked with a separate query, the result is remembered.
"""
if (optional := self._OPTIONAL_MODULES.get(module)) is None:
return await super().supports_module(module)
if module not in self._optional_module_support:
module_class, key = optional
query = module_class(self, key).query()
try:
response = await self.protocol.query(query, retry_count=0)
except TimeoutError:
return False
data = response.get(key)
self._optional_module_support[module] = (
isinstance(data, dict) and "err_code" not in data
)
return self._optional_module_support[module]
def update_from_discover_info(self, info: dict[str, Any]) -> None:
"""Update state from info from the discover call."""
self._discovery_info = info

View File

@@ -29,7 +29,6 @@ from .modules import (
Cloud,
Countdown,
Emeter,
HomeKit,
Led,
Schedule,
Time,
@@ -122,7 +121,6 @@ class IotStrip(IotDevice):
self.add_module(Module.IotCountdown, Countdown(self, "countdown"))
self.add_module(Module.Led, Led(self, "system"))
self.add_module(Module.IotCloud, Cloud(self, "cnCloud"))
self.add_module(Module.IotHomeKit, HomeKit(self, "smartlife.iot.homekit"))
if self.has_emeter:
_LOGGER.debug(
"The device has emeter, querying its information along sysinfo"