mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-10-18 05:18:02 +00:00
Initial support for tapos with child devices (#720)
* Add ChildDevice and ChildProtocolWrapper * Initialize & update children * Fix circular imports * Add dummy_protocol fixture and tests for unwrapping responseData * Use dummy_protocol for existing smartprotocol tests * Move _ChildProtocolWrapper to smartprotocol.py * Use dummy_protocol for test multiple requests * Use device_id instead of position for selecting the child * Fix wrapping for regular requests * Remove unused imports * tweak * rename child_device to childdevice * Fix import
This commit is contained in:
44
kasa/tapo/childdevice.py
Normal file
44
kasa/tapo/childdevice.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Child device implementation."""
|
||||
from typing import Dict, Optional
|
||||
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..exceptions import SmartDeviceException
|
||||
from ..smartprotocol import SmartProtocol, _ChildProtocolWrapper
|
||||
from .tapodevice import TapoDevice
|
||||
|
||||
|
||||
class ChildDevice(TapoDevice):
|
||||
"""Presentation of a child device.
|
||||
|
||||
This wraps the protocol communications and sets internal data for the child.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: TapoDevice,
|
||||
child_id: str,
|
||||
config: Optional[DeviceConfig] = None,
|
||||
protocol: Optional[SmartProtocol] = None,
|
||||
) -> None:
|
||||
super().__init__(parent.host, config=parent.config, protocol=parent.protocol)
|
||||
self._parent = parent
|
||||
self._id = child_id
|
||||
self.protocol = _ChildProtocolWrapper(child_id, parent.protocol)
|
||||
|
||||
async def update(self, update_children: bool = True):
|
||||
"""We just set the info here accordingly."""
|
||||
|
||||
def _get_child_info() -> Dict:
|
||||
"""Return the subdevice information for this device."""
|
||||
for child in self._parent._last_update["child_info"]["child_device_list"]:
|
||||
if child["device_id"] == self._id:
|
||||
return child
|
||||
|
||||
raise SmartDeviceException(
|
||||
f"Unable to find child device with position {self._id}"
|
||||
)
|
||||
|
||||
self._last_update = self._sys_info = self._info = _get_child_info()
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ChildDevice {self.alias} of {self._parent}>"
|
@@ -5,11 +5,11 @@ from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Dict, List, Optional, Set, cast
|
||||
|
||||
from ..aestransport import AesTransport
|
||||
from ..device_type import DeviceType
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..emeterstatus import EmeterStatus
|
||||
from ..exceptions import AuthenticationException, SmartDeviceException
|
||||
from ..modules import Emeter
|
||||
from ..protocol import BaseProtocol
|
||||
from ..smartdevice import SmartDevice, WifiNetwork
|
||||
from ..smartprotocol import SmartProtocol
|
||||
|
||||
@@ -24,17 +24,27 @@ class TapoDevice(SmartDevice):
|
||||
host: str,
|
||||
*,
|
||||
config: Optional[DeviceConfig] = None,
|
||||
protocol: Optional[BaseProtocol] = None,
|
||||
protocol: Optional[SmartProtocol] = None,
|
||||
) -> None:
|
||||
_protocol = protocol or SmartProtocol(
|
||||
transport=AesTransport(config=config or DeviceConfig(host=host)),
|
||||
)
|
||||
super().__init__(host=host, config=config, protocol=_protocol)
|
||||
self.protocol: SmartProtocol
|
||||
self._components_raw: Optional[Dict[str, Any]] = None
|
||||
self._components: Dict[str, int]
|
||||
self._state_information: Dict[str, Any] = {}
|
||||
self._discovery_info: Optional[Dict[str, Any]] = None
|
||||
self.modules: Dict[str, Any] = {}
|
||||
|
||||
async def _initialize_children(self):
|
||||
children = self._last_update["child_info"]["child_device_list"]
|
||||
# TODO: Use the type information to construct children,
|
||||
# as hubs can also have them.
|
||||
from .childdevice import ChildDevice
|
||||
|
||||
self.children = [
|
||||
ChildDevice(parent=self, child_id=child["device_id"]) for child in children
|
||||
]
|
||||
self._device_type = DeviceType.Strip
|
||||
|
||||
async def update(self, update_children: bool = True):
|
||||
"""Update the device."""
|
||||
@@ -51,6 +61,10 @@ class TapoDevice(SmartDevice):
|
||||
await self._initialize_modules()
|
||||
|
||||
extra_reqs: Dict[str, Any] = {}
|
||||
|
||||
if "child_device" in self._components:
|
||||
extra_reqs = {**extra_reqs, "get_child_device_list": None}
|
||||
|
||||
if "energy_monitoring" in self._components:
|
||||
extra_reqs = {
|
||||
**extra_reqs,
|
||||
@@ -81,8 +95,15 @@ class TapoDevice(SmartDevice):
|
||||
"time": self._time,
|
||||
"energy": self._energy,
|
||||
"emeter": self._emeter,
|
||||
"child_info": resp.get("get_child_device_list", {}),
|
||||
}
|
||||
|
||||
if self._last_update["child_info"]:
|
||||
if not self.children:
|
||||
await self._initialize_children()
|
||||
for child in self.children:
|
||||
await child.update()
|
||||
|
||||
_LOGGER.debug("Got an update: %s", self._data)
|
||||
|
||||
async def _initialize_modules(self):
|
||||
|
@@ -4,8 +4,8 @@ from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, Optional, cast
|
||||
|
||||
from ..deviceconfig import DeviceConfig
|
||||
from ..protocol import BaseProtocol
|
||||
from ..smartdevice import DeviceType
|
||||
from ..smartprotocol import SmartProtocol
|
||||
from .tapodevice import TapoDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -19,7 +19,7 @@ class TapoPlug(TapoDevice):
|
||||
host: str,
|
||||
*,
|
||||
config: Optional[DeviceConfig] = None,
|
||||
protocol: Optional[BaseProtocol] = None,
|
||||
protocol: Optional[SmartProtocol] = None,
|
||||
) -> None:
|
||||
super().__init__(host=host, config=config, protocol=protocol)
|
||||
self._device_type = DeviceType.Plug
|
||||
|
Reference in New Issue
Block a user