Pass raw components to SmartChildDevice init (#1363)

Clean up and consolidate the processing of raw component query responses and simplify the code paths for creating smartcam child devices when supported.
This commit is contained in:
Steven B. 2024-12-11 14:38:38 +00:00 committed by GitHub
parent 8cb5c2e180
commit f8a46f74cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 51 additions and 39 deletions

View File

@ -9,7 +9,7 @@ from typing import Any
from ..device_type import DeviceType from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
from ..protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper from ..protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
from .smartdevice import SmartDevice from .smartdevice import ComponentsRaw, SmartDevice
from .smartmodule import SmartModule from .smartmodule import SmartModule
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,7 +37,7 @@ class SmartChildDevice(SmartDevice):
self, self,
parent: SmartDevice, parent: SmartDevice,
info: dict, info: dict,
component_info: dict, component_info_raw: ComponentsRaw,
*, *,
config: DeviceConfig | None = None, config: DeviceConfig | None = None,
protocol: SmartProtocol | None = None, protocol: SmartProtocol | None = None,
@ -47,7 +47,8 @@ class SmartChildDevice(SmartDevice):
super().__init__(parent.host, config=parent.config, protocol=_protocol) super().__init__(parent.host, config=parent.config, protocol=_protocol)
self._parent = parent self._parent = parent
self._update_internal_state(info) self._update_internal_state(info)
self._components = component_info self._components_raw = component_info_raw
self._components = self._parse_components(self._components_raw)
async def update(self, update_children: bool = True) -> None: async def update(self, update_children: bool = True) -> None:
"""Update child module info. """Update child module info.
@ -84,7 +85,7 @@ class SmartChildDevice(SmartDevice):
cls, cls,
parent: SmartDevice, parent: SmartDevice,
child_info: dict, child_info: dict,
child_components: dict, child_components_raw: ComponentsRaw,
protocol: SmartProtocol | None = None, protocol: SmartProtocol | None = None,
*, *,
last_update: dict | None = None, last_update: dict | None = None,
@ -97,7 +98,7 @@ class SmartChildDevice(SmartDevice):
derived from the parent. derived from the parent.
""" """
child: SmartChildDevice = cls( child: SmartChildDevice = cls(
parent, child_info, child_components, protocol=protocol parent, child_info, child_components_raw, protocol=protocol
) )
if last_update: if last_update:
child._last_update = last_update child._last_update = last_update

View File

@ -7,7 +7,7 @@ import logging
import time import time
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from datetime import UTC, datetime, timedelta, tzinfo from datetime import UTC, datetime, timedelta, tzinfo
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, TypeAlias, cast
from ..device import Device, WifiNetwork, _DeviceInfo from ..device import Device, WifiNetwork, _DeviceInfo
from ..device_type import DeviceType from ..device_type import DeviceType
@ -40,6 +40,8 @@ _LOGGER = logging.getLogger(__name__)
# same issue, homekit perhaps? # same issue, homekit perhaps?
NON_HUB_PARENT_ONLY_MODULES = [DeviceModule, Time, Firmware, Cloud] NON_HUB_PARENT_ONLY_MODULES = [DeviceModule, Time, Firmware, Cloud]
ComponentsRaw: TypeAlias = dict[str, list[dict[str, int | str]]]
# Device must go last as the other interfaces also inherit Device # Device must go last as the other interfaces also inherit Device
# and python needs a consistent method resolution order. # and python needs a consistent method resolution order.
@ -61,7 +63,7 @@ class SmartDevice(Device):
) )
super().__init__(host=host, config=config, protocol=_protocol) super().__init__(host=host, config=config, protocol=_protocol)
self.protocol: SmartProtocol self.protocol: SmartProtocol
self._components_raw: dict[str, Any] | None = None self._components_raw: ComponentsRaw | None = None
self._components: dict[str, int] = {} self._components: dict[str, int] = {}
self._state_information: dict[str, Any] = {} self._state_information: dict[str, Any] = {}
self._modules: dict[str | ModuleName[Module], SmartModule] = {} self._modules: dict[str | ModuleName[Module], SmartModule] = {}
@ -82,10 +84,8 @@ class SmartDevice(Device):
self.internal_state.update(resp) self.internal_state.update(resp)
children = self.internal_state["get_child_device_list"]["child_device_list"] children = self.internal_state["get_child_device_list"]["child_device_list"]
children_components = { children_components_raw = {
child["device_id"]: { child["device_id"]: child
comp["id"]: int(comp["ver_code"]) for comp in child["component_list"]
}
for child in self.internal_state["get_child_device_component_list"][ for child in self.internal_state["get_child_device_component_list"][
"child_component_list" "child_component_list"
] ]
@ -96,7 +96,7 @@ class SmartDevice(Device):
child_info["device_id"]: await SmartChildDevice.create( child_info["device_id"]: await SmartChildDevice.create(
parent=self, parent=self,
child_info=child_info, child_info=child_info,
child_components=children_components[child_info["device_id"]], child_components_raw=children_components_raw[child_info["device_id"]],
) )
for child_info in children for child_info in children
} }
@ -131,6 +131,13 @@ class SmartDevice(Device):
f"{request} not found in {responses} for device {self.host}" f"{request} not found in {responses} for device {self.host}"
) )
@staticmethod
def _parse_components(components_raw: ComponentsRaw) -> dict[str, int]:
return {
str(comp["id"]): int(comp["ver_code"])
for comp in components_raw["component_list"]
}
async def _negotiate(self) -> None: async def _negotiate(self) -> None:
"""Perform initialization. """Perform initialization.
@ -151,12 +158,9 @@ class SmartDevice(Device):
self._info = self._try_get_response(resp, "get_device_info") self._info = self._try_get_response(resp, "get_device_info")
# Create our internal presentation of available components # Create our internal presentation of available components
self._components_raw = cast(dict, resp["component_nego"]) self._components_raw = cast(ComponentsRaw, resp["component_nego"])
self._components = { self._components = self._parse_components(self._components_raw)
comp["id"]: int(comp["ver_code"])
for comp in self._components_raw["component_list"]
}
if "child_device" in self._components and not self.children: if "child_device" in self._components and not self.children:
await self._initialize_children() await self._initialize_children()

View File

@ -3,13 +3,14 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any from typing import Any, cast
from ..device import _DeviceInfo from ..device import _DeviceInfo
from ..device_type import DeviceType from ..device_type import DeviceType
from ..module import Module from ..module import Module
from ..protocols.smartcamprotocol import _ChildCameraProtocolWrapper from ..protocols.smartcamprotocol import _ChildCameraProtocolWrapper
from ..smart import SmartChildDevice, SmartDevice from ..smart import SmartChildDevice, SmartDevice
from ..smart.smartdevice import ComponentsRaw
from .modules import ChildDevice, DeviceModule from .modules import ChildDevice, DeviceModule
from .smartcammodule import SmartCamModule from .smartcammodule import SmartCamModule
@ -78,7 +79,7 @@ class SmartCamDevice(SmartDevice):
self._children[child_id]._update_internal_state(info) self._children[child_id]._update_internal_state(info)
async def _initialize_smart_child( async def _initialize_smart_child(
self, info: dict, child_components: dict self, info: dict, child_components_raw: ComponentsRaw
) -> SmartDevice: ) -> SmartDevice:
"""Initialize a smart child device attached to a smartcam device.""" """Initialize a smart child device attached to a smartcam device."""
child_id = info["device_id"] child_id = info["device_id"]
@ -93,7 +94,7 @@ class SmartCamDevice(SmartDevice):
return await SmartChildDevice.create( return await SmartChildDevice.create(
parent=self, parent=self,
child_info=info, child_info=info,
child_components=child_components, child_components_raw=child_components_raw,
protocol=child_protocol, protocol=child_protocol,
last_update=initial_response, last_update=initial_response,
) )
@ -108,17 +109,8 @@ class SmartCamDevice(SmartDevice):
self.internal_state.update(resp) self.internal_state.update(resp)
smart_children_components = { smart_children_components = {
child["device_id"]: { child["device_id"]: child
comp["id"]: int(comp["ver_code"]) for comp in component_list
}
for child in resp["getChildDeviceComponentList"]["child_component_list"] for child in resp["getChildDeviceComponentList"]["child_component_list"]
if (component_list := child.get("component_list"))
# Child camera devices will have a different component schema so only
# extract smart values.
and (first_comp := next(iter(component_list), None))
and isinstance(first_comp, dict)
and "id" in first_comp
and "ver_code" in first_comp
} }
children = {} children = {}
for info in resp["getChildDeviceList"]["child_device_list"]: for info in resp["getChildDeviceList"]["child_device_list"]:
@ -172,6 +164,13 @@ class SmartCamDevice(SmartDevice):
return res return res
@staticmethod
def _parse_components(components_raw: ComponentsRaw) -> dict[str, int]:
return {
str(comp["name"]): int(comp["version"])
for comp in components_raw["app_component_list"]
}
async def _negotiate(self) -> None: async def _negotiate(self) -> None:
"""Perform initialization. """Perform initialization.
@ -186,12 +185,10 @@ class SmartCamDevice(SmartDevice):
self._last_update.update(resp) self._last_update.update(resp)
self._update_internal_info(resp) self._update_internal_info(resp)
self._components = { self._components_raw = cast(
comp["name"]: int(comp["version"]) ComponentsRaw, resp["getAppComponentList"]["app_component"]
for comp in resp["getAppComponentList"]["app_component"][ )
"app_component_list" self._components = self._parse_components(self._components_raw)
]
}
if "childControl" in self._components and not self.children: if "childControl" in self._components and not self.children:
await self._initialize_children() await self._initialize_children()

View File

@ -145,7 +145,9 @@ async def test_child_device_type_unknown(caplog):
super().__init__( super().__init__(
SmartDevice("127.0.0.1"), SmartDevice("127.0.0.1"),
{"device_id": "1", "category": "foobar"}, {"device_id": "1", "category": "foobar"},
{"device", 1}, {
"component_list": [{"id": "device", "ver_code": 1}],
},
) )
assert DummyDevice().device_type is DeviceType.Unknown assert DummyDevice().device_type is DeviceType.Unknown

View File

@ -86,7 +86,11 @@ async def test_device_class_ctors(device_class_name_obj):
if issubclass(klass, SmartChildDevice): if issubclass(klass, SmartChildDevice):
parent = SmartDevice(host, config=config) parent = SmartDevice(host, config=config)
dev = klass( dev = klass(
parent, {"dummy": "info", "device_id": "dummy"}, {"dummy": "components"} parent,
{"dummy": "info", "device_id": "dummy"},
{
"component_list": [{"id": "device", "ver_code": 1}],
},
) )
else: else:
dev = klass(host, config=config) dev = klass(host, config=config)
@ -106,7 +110,11 @@ async def test_device_class_repr(device_class_name_obj):
if issubclass(klass, SmartChildDevice): if issubclass(klass, SmartChildDevice):
parent = SmartDevice(host, config=config) parent = SmartDevice(host, config=config)
dev = klass( dev = klass(
parent, {"dummy": "info", "device_id": "dummy"}, {"dummy": "components"} parent,
{"dummy": "info", "device_id": "dummy"},
{
"component_list": [{"id": "device", "ver_code": 1}],
},
) )
else: else:
dev = klass(host, config=config) dev = klass(host, config=config)