From c9fd7ed056e5dd66a97502dbd585cb9d9883b4cc Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Thu, 16 Jan 2025 14:47:11 +0100 Subject: [PATCH 1/3] Add childlock module for vacuums --- devtools/helpers/smartrequests.py | 2 +- kasa/module.py | 1 + kasa/smart/modules/__init__.py | 2 + kasa/smart/modules/childlock.py | 37 ++++++++++++++++ .../smart/RV20 Max Plus(EU)_1.0_1.0.7.json | 3 ++ tests/smart/modules/test_childlock.py | 44 +++++++++++++++++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 kasa/smart/modules/childlock.py create mode 100644 tests/smart/modules/test_childlock.py diff --git a/devtools/helpers/smartrequests.py b/devtools/helpers/smartrequests.py index 3db1a2c99..3756cb956 100644 --- a/devtools/helpers/smartrequests.py +++ b/devtools/helpers/smartrequests.py @@ -448,7 +448,7 @@ def get_component_requests(component_id, ver_code): "battery": [SmartRequest.get_raw_request("getBatteryInfo")], "consumables": [SmartRequest.get_raw_request("getConsumablesInfo")], "direction_control": [], - "button_and_led": [], + "button_and_led": [SmartRequest.get_raw_request("getChildLockInfo")], "speaker": [ SmartRequest.get_raw_request("getSupportVoiceLanguage"), SmartRequest.get_raw_request("getCurrentVoiceLanguage"), diff --git a/kasa/module.py b/kasa/module.py index c477dbedc..506509654 100644 --- a/kasa/module.py +++ b/kasa/module.py @@ -152,6 +152,7 @@ class Module(ABC): ChildProtection: Final[ModuleName[smart.ChildProtection]] = ModuleName( "ChildProtection" ) + ChildLock: Final[ModuleName[smart.ChildLock]] = ModuleName("ChildLock") TriggerLogs: Final[ModuleName[smart.TriggerLogs]] = ModuleName("TriggerLogs") HomeKit: Final[ModuleName[smart.HomeKit]] = ModuleName("HomeKit") diff --git a/kasa/smart/modules/__init__.py b/kasa/smart/modules/__init__.py index 48378a575..a17859e4a 100644 --- a/kasa/smart/modules/__init__.py +++ b/kasa/smart/modules/__init__.py @@ -6,6 +6,7 @@ from .batterysensor import BatterySensor from .brightness import Brightness from .childdevice import ChildDevice +from .childlock import ChildLock from .childprotection import ChildProtection from .clean import Clean from .cloud import Cloud @@ -45,6 +46,7 @@ "Energy", "DeviceModule", "ChildDevice", + "ChildLock", "BatterySensor", "HumiditySensor", "TemperatureSensor", diff --git a/kasa/smart/modules/childlock.py b/kasa/smart/modules/childlock.py new file mode 100644 index 000000000..be3c6b5df --- /dev/null +++ b/kasa/smart/modules/childlock.py @@ -0,0 +1,37 @@ +"""Child lock module.""" + +from __future__ import annotations + +from ...feature import Feature +from ..smartmodule import SmartModule + + +class ChildLock(SmartModule): + """Implementation for child_protection.""" + + REQUIRED_COMPONENT = "button_and_led" + QUERY_GETTER_NAME = "getChildLockInfo" + + def _initialize_features(self) -> None: + """Initialize features after the initial update.""" + self._add_feature( + Feature( + device=self._device, + id="child_lock", + name="Child lock", + container=self, + attribute_getter="enabled", + attribute_setter="set_enabled", + type=Feature.Type.Switch, + category=Feature.Category.Config, + ) + ) + + @property + def enabled(self) -> bool: + """Return True if child protection is enabled.""" + return self.data["child_lock_status"] + + async def set_enabled(self, enabled: bool) -> dict: + """Set child protection.""" + return await self.call("setChildLockInfo", {"child_lock_status": enabled}) diff --git a/tests/fixtures/smart/RV20 Max Plus(EU)_1.0_1.0.7.json b/tests/fixtures/smart/RV20 Max Plus(EU)_1.0_1.0.7.json index 2f945c948..c978f89c9 100644 --- a/tests/fixtures/smart/RV20 Max Plus(EU)_1.0_1.0.7.json +++ b/tests/fixtures/smart/RV20 Max Plus(EU)_1.0_1.0.7.json @@ -165,6 +165,9 @@ "getCarpetClean": { "carpet_clean_prefer": "boost" }, + "getChildLockInfo": { + "child_lock_status": false + }, "getCleanAttr": { "cistern": 2, "clean_number": 1, diff --git a/tests/smart/modules/test_childlock.py b/tests/smart/modules/test_childlock.py new file mode 100644 index 000000000..5daca122f --- /dev/null +++ b/tests/smart/modules/test_childlock.py @@ -0,0 +1,44 @@ +import pytest + +from kasa import Module +from kasa.smart.modules import ChildLock + +from ...device_fixtures import parametrize + +childlock = parametrize( + "has child lock", + component_filter="button_and_led", + protocol_filter={"SMART.CHILD"}, +) + + +@childlock +@pytest.mark.parametrize( + ("feature", "prop_name", "type"), + [ + ("child_lock", "enabled", bool), + ], +) +async def test_features(dev, feature, prop_name, type): + """Test that features are registered and work as expected.""" + protect: ChildLock = dev.modules[Module.ChildLock] + assert protect is not None + + prop = getattr(protect, prop_name) + assert isinstance(prop, type) + + feat = protect._device.features[feature] + assert feat.value == prop + assert isinstance(feat.value, type) + + +@childlock +async def test_enabled(dev): + """Test the API.""" + protect: ChildLock = dev.modules[Module.ChildLock] + assert protect is not None + + assert isinstance(protect.enabled, bool) + await protect.set_enabled(False) + await dev.update() + assert protect.enabled is False From 1f47bdd3821dfff37f1b3ce76e08f4817cdc98f2 Mon Sep 17 00:00:00 2001 From: "Teemu R." Date: Fri, 17 Jan 2025 13:09:32 +0100 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Steven B. <51370195+sdb9696@users.noreply.github.com> --- kasa/smart/modules/childlock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kasa/smart/modules/childlock.py b/kasa/smart/modules/childlock.py index be3c6b5df..1c5e72d9e 100644 --- a/kasa/smart/modules/childlock.py +++ b/kasa/smart/modules/childlock.py @@ -7,7 +7,7 @@ class ChildLock(SmartModule): - """Implementation for child_protection.""" + """Implementation for child lock.""" REQUIRED_COMPONENT = "button_and_led" QUERY_GETTER_NAME = "getChildLockInfo" @@ -29,9 +29,9 @@ def _initialize_features(self) -> None: @property def enabled(self) -> bool: - """Return True if child protection is enabled.""" + """Return True if child lock is enabled.""" return self.data["child_lock_status"] async def set_enabled(self, enabled: bool) -> dict: - """Set child protection.""" + """Set child lock.""" return await self.call("setChildLockInfo", {"child_lock_status": enabled}) From cf361868ded02e0af8c3f7146c40921e6c432d5c Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Fri, 17 Jan 2025 13:10:31 +0100 Subject: [PATCH 3/3] protocol_filter SMART.CHILD -> SMART --- tests/smart/modules/test_childlock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/smart/modules/test_childlock.py b/tests/smart/modules/test_childlock.py index 5daca122f..2ffa91045 100644 --- a/tests/smart/modules/test_childlock.py +++ b/tests/smart/modules/test_childlock.py @@ -8,7 +8,7 @@ childlock = parametrize( "has child lock", component_filter="button_and_led", - protocol_filter={"SMART.CHILD"}, + protocol_filter={"SMART"}, )