mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
Add core device, child and camera modules to smartcamera (#1193)
Co-authored-by: Teemu R. <tpr@iki.fi>
This commit is contained in:
@@ -36,17 +36,19 @@ class SmartChildDevice(SmartDevice):
|
||||
def __init__(
|
||||
self,
|
||||
parent: SmartDevice,
|
||||
info,
|
||||
component_info,
|
||||
info: dict,
|
||||
component_info: dict,
|
||||
*,
|
||||
config: DeviceConfig | None = None,
|
||||
protocol: SmartProtocol | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent.host, config=parent.config, protocol=parent.protocol)
|
||||
super().__init__(parent.host, config=parent.config, protocol=protocol)
|
||||
self._parent = parent
|
||||
self._update_internal_state(info)
|
||||
self._components = component_info
|
||||
self._id = info["device_id"]
|
||||
self.protocol = _ChildProtocolWrapper(self._id, parent.protocol)
|
||||
# wrap device protocol if no protocol is given
|
||||
self.protocol = protocol or _ChildProtocolWrapper(self._id, parent.protocol)
|
||||
|
||||
async def update(self, update_children: bool = True):
|
||||
"""Update child module info.
|
||||
@@ -79,9 +81,27 @@ class SmartChildDevice(SmartDevice):
|
||||
self._last_update_time = now
|
||||
|
||||
@classmethod
|
||||
async def create(cls, parent: SmartDevice, child_info, child_components):
|
||||
"""Create a child device based on device info and component listing."""
|
||||
child: SmartChildDevice = cls(parent, child_info, child_components)
|
||||
async def create(
|
||||
cls,
|
||||
parent: SmartDevice,
|
||||
child_info: dict,
|
||||
child_components: dict,
|
||||
protocol: SmartProtocol | None = None,
|
||||
*,
|
||||
last_update: dict | None = None,
|
||||
) -> SmartDevice:
|
||||
"""Create a child device based on device info and component listing.
|
||||
|
||||
If creating a smart child from a different protocol, i.e. a camera hub,
|
||||
protocol: SmartProtocol and last_update should be provided as per the
|
||||
FIRST_UPDATE_MODULES expected by the update cycle as these cannot be
|
||||
derived from the parent.
|
||||
"""
|
||||
child: SmartChildDevice = cls(
|
||||
parent, child_info, child_components, protocol=protocol
|
||||
)
|
||||
if last_update:
|
||||
child._last_update = last_update
|
||||
await child._initialize_modules()
|
||||
return child
|
||||
|
||||
|
@@ -37,15 +37,15 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# same issue, homekit perhaps?
|
||||
NON_HUB_PARENT_ONLY_MODULES = [DeviceModule, Time, Firmware, Cloud]
|
||||
|
||||
# Modules that are called as part of the init procedure on first update
|
||||
FIRST_UPDATE_MODULES = {DeviceModule, ChildDevice, Cloud}
|
||||
|
||||
|
||||
# Device must go last as the other interfaces also inherit Device
|
||||
# and python needs a consistent method resolution order.
|
||||
class SmartDevice(Device):
|
||||
"""Base class to represent a SMART protocol based device."""
|
||||
|
||||
# Modules that are called as part of the init procedure on first update
|
||||
FIRST_UPDATE_MODULES = {DeviceModule, ChildDevice, Cloud}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
@@ -67,6 +67,7 @@ class SmartDevice(Device):
|
||||
self._last_update = {}
|
||||
self._last_update_time: float | None = None
|
||||
self._on_since: datetime | None = None
|
||||
self._info: dict[str, Any] = {}
|
||||
|
||||
async def _initialize_children(self):
|
||||
"""Initialize children for power strips."""
|
||||
@@ -154,6 +155,18 @@ class SmartDevice(Device):
|
||||
if "child_device" in self._components and not self.children:
|
||||
await self._initialize_children()
|
||||
|
||||
def _update_children_info(self) -> None:
|
||||
"""Update the internal child device info from the parent info."""
|
||||
if child_info := self._try_get_response(
|
||||
self._last_update, "get_child_device_list", {}
|
||||
):
|
||||
for info in child_info["child_device_list"]:
|
||||
self._children[info["device_id"]]._update_internal_state(info)
|
||||
|
||||
def _update_internal_info(self, info_resp: dict) -> None:
|
||||
"""Update the internal device info."""
|
||||
self._info = self._try_get_response(info_resp, "get_device_info")
|
||||
|
||||
async def update(self, update_children: bool = False):
|
||||
"""Update the device."""
|
||||
if self.credentials is None and self.credentials_hash is None:
|
||||
@@ -172,11 +185,7 @@ class SmartDevice(Device):
|
||||
|
||||
resp = await self._modular_update(first_update, now)
|
||||
|
||||
if child_info := self._try_get_response(
|
||||
self._last_update, "get_child_device_list", {}
|
||||
):
|
||||
for info in child_info["child_device_list"]:
|
||||
self._children[info["device_id"]]._update_internal_state(info)
|
||||
self._update_children_info()
|
||||
# Call child update which will only update module calls, info is updated
|
||||
# from get_child_device_list. update_children only affects hub devices, other
|
||||
# devices will always update children to prevent errors on module access.
|
||||
@@ -227,10 +236,10 @@ class SmartDevice(Device):
|
||||
mq = {
|
||||
module: query
|
||||
for module in self._modules.values()
|
||||
if module.disabled is False and (query := module.query())
|
||||
if (first_update or module.disabled is False) and (query := module.query())
|
||||
}
|
||||
for module, query in mq.items():
|
||||
if first_update and module.__class__ in FIRST_UPDATE_MODULES:
|
||||
if first_update and module.__class__ in self.FIRST_UPDATE_MODULES:
|
||||
module._last_update_time = update_time
|
||||
continue
|
||||
if (
|
||||
@@ -256,7 +265,7 @@ class SmartDevice(Device):
|
||||
|
||||
info_resp = self._last_update if first_update else resp
|
||||
self._last_update.update(**resp)
|
||||
self._info = self._try_get_response(info_resp, "get_device_info")
|
||||
self._update_internal_info(info_resp)
|
||||
|
||||
# Call handle update for modules that want to update internal data
|
||||
for module in self._modules.values():
|
||||
@@ -570,7 +579,7 @@ class SmartDevice(Device):
|
||||
"""Return all the internal state data."""
|
||||
return self._last_update
|
||||
|
||||
def _update_internal_state(self, info):
|
||||
def _update_internal_state(self, info: dict) -> None:
|
||||
"""Update the internal info state.
|
||||
|
||||
This is used by the parent to push updates to its children.
|
||||
|
@@ -80,7 +80,7 @@ class SmartModule(Module):
|
||||
# other classes can inherit from smartmodule and not be registered
|
||||
if cls.__module__.split(".")[-2] == "modules":
|
||||
_LOGGER.debug("Registering %s", cls)
|
||||
cls.REGISTERED_MODULES[cls.__name__] = cls
|
||||
cls.REGISTERED_MODULES[cls._module_name()] = cls
|
||||
|
||||
def _set_error(self, err: Exception | None):
|
||||
if err is None:
|
||||
@@ -118,10 +118,14 @@ class SmartModule(Module):
|
||||
"""Return true if the module is disabled due to errors."""
|
||||
return self._error_count >= self.DISABLE_AFTER_ERROR_COUNT
|
||||
|
||||
@classmethod
|
||||
def _module_name(cls):
|
||||
return getattr(cls, "NAME", cls.__name__)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Name of the module."""
|
||||
return getattr(self, "NAME", self.__class__.__name__)
|
||||
return self._module_name()
|
||||
|
||||
async def _post_update_hook(self): # noqa: B027
|
||||
"""Perform actions after a device update.
|
||||
|
Reference in New Issue
Block a user