mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-12-01 21:58:17 +00:00
Create common interfaces for remaining device types (#895)
Introduce common module interfaces across smart and iot devices and provide better typing implementation for getting modules to support this.
This commit is contained in:
@@ -19,14 +19,15 @@ import functools
|
||||
import inspect
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Mapping, Sequence, cast, overload
|
||||
from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast
|
||||
|
||||
from ..device import Device, WifiNetwork
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..emeterstatus import EmeterStatus
|
||||
from ..exceptions import KasaException
|
||||
from ..feature import Feature
|
||||
from ..module import ModuleT
|
||||
from ..module import Module
|
||||
from ..modulemapping import ModuleMapping, ModuleName
|
||||
from ..protocol import BaseProtocol
|
||||
from .iotmodule import IotModule
|
||||
from .modules import Emeter, Time
|
||||
@@ -190,7 +191,7 @@ class IotDevice(Device):
|
||||
self._supported_modules: dict[str, IotModule] | None = None
|
||||
self._legacy_features: set[str] = set()
|
||||
self._children: Mapping[str, IotDevice] = {}
|
||||
self._modules: dict[str, IotModule] = {}
|
||||
self._modules: dict[str | ModuleName[Module], IotModule] = {}
|
||||
|
||||
@property
|
||||
def children(self) -> Sequence[IotDevice]:
|
||||
@@ -198,38 +199,20 @@ class IotDevice(Device):
|
||||
return list(self._children.values())
|
||||
|
||||
@property
|
||||
def modules(self) -> dict[str, IotModule]:
|
||||
def modules(self) -> ModuleMapping[IotModule]:
|
||||
"""Return the device modules."""
|
||||
if TYPE_CHECKING:
|
||||
return cast(ModuleMapping[IotModule], self._modules)
|
||||
return self._modules
|
||||
|
||||
@overload
|
||||
def get_module(self, module_type: type[ModuleT]) -> ModuleT | None: ...
|
||||
|
||||
@overload
|
||||
def get_module(self, module_type: str) -> IotModule | None: ...
|
||||
|
||||
def get_module(
|
||||
self, module_type: type[ModuleT] | str
|
||||
) -> ModuleT | IotModule | None:
|
||||
"""Return the module from the device modules or None if not present."""
|
||||
if isinstance(module_type, str):
|
||||
module_name = module_type.lower()
|
||||
elif issubclass(module_type, IotModule):
|
||||
module_name = module_type.__name__.lower()
|
||||
else:
|
||||
return None
|
||||
if module_name in self.modules:
|
||||
return self.modules[module_name]
|
||||
return None
|
||||
|
||||
def add_module(self, name: str, module: IotModule):
|
||||
def add_module(self, name: str | ModuleName[Module], module: IotModule):
|
||||
"""Register a module."""
|
||||
if name in self.modules:
|
||||
_LOGGER.debug("Module %s already registered, ignoring..." % name)
|
||||
return
|
||||
|
||||
_LOGGER.debug("Adding module %s", module)
|
||||
self.modules[name] = module
|
||||
self._modules[name] = module
|
||||
|
||||
def _create_request(
|
||||
self, target: str, cmd: str, arg: dict | None = None, child_ids=None
|
||||
@@ -291,11 +274,11 @@ class IotDevice(Device):
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
def supported_modules(self) -> list[str]:
|
||||
def supported_modules(self) -> list[str | ModuleName[Module]]:
|
||||
"""Return a set of modules supported by the device."""
|
||||
# TODO: this should rather be called `features`, but we don't want to break
|
||||
# the API now. Maybe just deprecate it and point the users to use this?
|
||||
return list(self.modules.keys())
|
||||
return list(self._modules.keys())
|
||||
|
||||
@property # type: ignore
|
||||
@requires_update
|
||||
@@ -324,10 +307,11 @@ class IotDevice(Device):
|
||||
self._last_update = response
|
||||
self._set_sys_info(response["system"]["get_sysinfo"])
|
||||
|
||||
await self._modular_update(req)
|
||||
|
||||
if not self._features:
|
||||
await self._initialize_features()
|
||||
|
||||
await self._modular_update(req)
|
||||
self._set_sys_info(self._last_update["system"]["get_sysinfo"])
|
||||
|
||||
async def _initialize_features(self):
|
||||
@@ -352,6 +336,11 @@ class IotDevice(Device):
|
||||
)
|
||||
)
|
||||
|
||||
for module in self._modules.values():
|
||||
module._initialize_features()
|
||||
for module_feat in module._module_features.values():
|
||||
self._add_feature(module_feat)
|
||||
|
||||
async def _modular_update(self, req: dict) -> None:
|
||||
"""Execute an update query."""
|
||||
if self.has_emeter:
|
||||
@@ -364,17 +353,15 @@ class IotDevice(Device):
|
||||
# making separate handling for this unnecessary
|
||||
if self._supported_modules is None:
|
||||
supported = {}
|
||||
for module in self.modules.values():
|
||||
for module in self._modules.values():
|
||||
if module.is_supported:
|
||||
supported[module._module] = module
|
||||
for module_feat in module._module_features.values():
|
||||
self._add_feature(module_feat)
|
||||
|
||||
self._supported_modules = supported
|
||||
|
||||
request_list = []
|
||||
est_response_size = 1024 if "system" in req else 0
|
||||
for module in self.modules.values():
|
||||
for module in self._modules.values():
|
||||
if not module.is_supported:
|
||||
_LOGGER.debug("Module %s not supported, skipping" % module)
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user