Add core device, child and camera modules to smartcamera (#1193)

Co-authored-by: Teemu R. <tpr@iki.fi>
This commit is contained in:
Steven B.
2024-10-24 17:22:45 +01:00
committed by GitHub
parent 8ee8c17bdc
commit 28361c1727
11 changed files with 427 additions and 47 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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.