mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 11:13:34 +00:00
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:
parent
8cb5c2e180
commit
f8a46f74cd
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user