From cd0a74ca962443d740cca8542f78a69f47e1f6a6 Mon Sep 17 00:00:00 2001 From: "Steven B." <51370195+sdb9696@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:17:27 +0100 Subject: [PATCH] Improve supported module checks for hub children (#1188) No devices in `fixtures/smart/child` support the `get_device_time` or `get_device_usage` methods so this PR tests for whether the device is a hub child and marks those modules/methods as not supported. This prevents features being erroneously created on child devices. It also moves the logic for getting the time from the parent module behind getting it from the child module which was masking the creation of these unsupported modules. --- kasa/smart/modules/devicemodule.py | 3 ++- kasa/smart/modules/time.py | 9 +++++++++ kasa/smart/smartdevice.py | 9 +++++++-- kasa/tests/test_childdevice.py | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/kasa/smart/modules/devicemodule.py b/kasa/smart/modules/devicemodule.py index 1d2b64f2..89c87c20 100644 --- a/kasa/smart/modules/devicemodule.py +++ b/kasa/smart/modules/devicemodule.py @@ -23,7 +23,8 @@ class DeviceModule(SmartModule): "get_device_info": None, } # Device usage is not available on older firmware versions - if self.supported_version >= 2: + # or child devices of hubs + if self.supported_version >= 2 and not self._device._is_hub_child: query["get_device_usage"] = None return query diff --git a/kasa/smart/modules/time.py b/kasa/smart/modules/time.py index c182b8af..cac01d73 100644 --- a/kasa/smart/modules/time.py +++ b/kasa/smart/modules/time.py @@ -83,3 +83,12 @@ class Time(SmartModule, TimeInterface): if region: params["region"] = region return await self.call("set_device_time", params) + + async def _check_supported(self): + """Additional check to see if the module is supported by the device. + + Hub attached sensors report the time module but do return device time. + """ + if self._device._is_hub_child: + return False + return await super()._check_supported() diff --git a/kasa/smart/smartdevice.py b/kasa/smart/smartdevice.py index 095156e3..0a8c136c 100644 --- a/kasa/smart/smartdevice.py +++ b/kasa/smart/smartdevice.py @@ -457,6 +457,11 @@ class SmartDevice(Device): for child in self._children.values(): await child._initialize_features() + @property + def _is_hub_child(self) -> bool: + """Returns true if the device is a child of a hub.""" + return self.parent is not None and self.parent.device_type is DeviceType.Hub + @property def is_cloud_connected(self) -> bool: """Returns if the device is connected to the cloud.""" @@ -485,8 +490,8 @@ class SmartDevice(Device): @property def time(self) -> datetime: """Return the time.""" - if (self._parent and (time_mod := self._parent.modules.get(Module.Time))) or ( - time_mod := self.modules.get(Module.Time) + if (time_mod := self.modules.get(Module.Time)) or ( + self._parent and (time_mod := self._parent.modules.get(Module.Time)) ): return time_mod.time diff --git a/kasa/tests/test_childdevice.py b/kasa/tests/test_childdevice.py index 251af878..797e8dff 100644 --- a/kasa/tests/test_childdevice.py +++ b/kasa/tests/test_childdevice.py @@ -1,7 +1,9 @@ import inspect import sys +from datetime import datetime, timezone import pytest +from freezegun.api import FrozenDateTimeFactory from kasa import Device from kasa.device_type import DeviceType @@ -120,3 +122,15 @@ async def test_parent_property(dev: Device): assert dev.parent is None for child in dev.children: assert child.parent == dev + + +@has_children_smart +async def test_child_time(dev: Device, freezer: FrozenDateTimeFactory): + """Test a child device gets the time from it's parent module.""" + if not dev.children: + pytest.skip(f"Device {dev} fixture does not have any children") + + fallback_time = datetime.now(timezone.utc).astimezone().replace(microsecond=0) + assert dev.parent is None + for child in dev.children: + assert child.time != fallback_time