10000 Add childprotection module (#1141) · python-kasa/python-kasa@0360107 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0360107

Browse files
authored
Add childprotection module (#1141)
When turned on, rotating the thermostat will not change the target temperature.
1 parent 77b654a commit 0360107

File tree

5 files changed

+102
-1
lines changed

5 files changed

+102
-1
lines changed

kasa/module.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ class Module(ABC):
127127
WaterleakSensor: Final[ModuleName[smart.WaterleakSensor]] = ModuleName(
128128
"WaterleakSensor"
129129
)
130+
ChildProtection: Final[ModuleName[smart.ChildProtection]] = ModuleName(
131+
"ChildProtection"
132+
)
130133
TriggerLogs: Final[ModuleName[smart.TriggerLogs]] = ModuleName("TriggerLogs")
131134

132135
# SMARTCAMERA only modules

kasa/smart/modules/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .batterysensor import BatterySensor
77
from .brightness import Brightness
88
from .childdevice import ChildDevice
9+
from .childprotection import ChildProtection
910
from .cloud import Cloud
1011
from .color import Color
1112
from .colortemperature import ColorTemperature
@@ -40,6 +41,7 @@
4041
"HumiditySensor",
4142
"TemperatureSensor",
4243
"TemperatureControl",
44+
"ChildProtection",
4345
"ReportMode",
4446
"AutoOff",
4547
"Led",

kasa/smart/modules/childprotection.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Child lock module."""
2+
3+
from __future__ import annotations
4+
5+
from ...feature import Feature
6+
from ..smartmodule import SmartModule
7+
8+
9+
class ChildProtection(SmartModule):
10+
"""Implementation for child_protection."""
11+
12+
REQUIRED_COMPONENT = "child_protection"
13+
QUERY_GETTER_NAME = "get_child_protection"
14+
15+
def _initialize_features(self):
16+
"""Initialize features after the initial update."""
17+
self._add_feature(
18+
Feature(
19+
device=self._device,
20+
id="child_lock",
21+
name="Child lock",
22+
container=self,
23+
attribute_getter="enabled",
24+
attribute_setter="set_enabled",
25+
type=Feature.Type.Switch,
26+
category=Feature.Category.Config,
27+
)
28+
)
29+
30+
def query(self) -> dict:
31+
"""Query to execute during the update cycle."""
32+
return {}
33+
34+
@property
35+
def enabled(self) -> bool:
36+
"""Return True if child protection is enabled."""
37+
return self.data["child_protection"]
38+
39+
async def set_enabled(self, enabled: bool) -> dict:
40+
"""Set child protection."""
41+
return await self.call("set_child_protection", {"enable": enabled})

kasa/tests/fakeprotocol_smart.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,14 +430,24 @@ def _edit_preset_rules(self, info, params):
430430
info["get_preset_rules"]["states"][params["index"]] = params["state"]
431431
return {"error_code": 0}
432432

433+
def _update_sysinfo_key(self, info: dict, key: str, value: str) -> dict:
434+
"""Update a single key in the main system info.
435+
436+
This is used to implement child device setters that change the main sysinfo state.
437+
"""
438+
sys_info = info.get("get_device_info", info)
439+
sys_info[key] = value
440+
441+
return {"error_code": 0}
442+
433443
async def _send_request(self, request_dict: dict):
434444
method = request_dict["method"]
435445

436446
info = self.info
437447
if method == "control_child":
438448
return await self._handle_control_child(request_dict["params"])
439449

440-
params = request_dict.get("params")
450+
params = request_dict.get("params", {})
441451
if method == "component_nego" or method[:4] == "get_":
442452
if method in info:
443453
result = copy.deepcopy(info[method])
@@ -518,6 +528,8 @@ async def _send_request(self, request_dict: dict):
518528
return self._edit_preset_ 9E88 rules(info, params)
519529
elif method == "set_on_off_gradually_info":
520530
return self._set_on_off_gradually_info(info, params)
531+
elif method == "set_child_protection":
532+
return self._update_sysinfo_key(info, "child_protection", params["enable"])
521533
elif method[:4] == "set_":
522534
target_method = f"get_{method[4:]}"
523535
info[target_method].update(params)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
3+
from kasa import Module
4+
from kasa.smart.modules import ChildProtection
5+
from kasa.tests.device_fixtures import parametrize
6+
7+
child_protection = parametrize(
8+
"has child protection",
9+
component_filter="child_protection",
10+
protocol_filter={"SMART.CHILD"},
11+
)
12+
13+
14+
@child_protection
15+
@pytest.mark.parametrize(
16+
("feature", "prop_name", "type"),
17+
[
18+
("child_lock", "enabled", bool),
19+
],
20+
)
21+
async def test_features(dev, feature, prop_name, type):
22+
"""Test that features are registered and work as expected."""
23+
protect: ChildProtection = dev.modules[Module.ChildProtection]
24+
assert protect is not None
25+
26+
prop = getattr(protect, prop_name)
27+
assert isinstance(prop, type)
28+
29+
feat = protect._device.features[feature]
30+
assert feat.value == prop
31+
assert isinstance(feat.value, type)
32+
33+
34+
@child_protection
35+
async def test_enabled(dev):
36+
"""Test the API."""
37+
protect: ChildProtection = dev.modules[Module.ChildProtection]
38+
assert protect is not None
39+
40+
assert isinstance(protect.enabled, bool)
41+
await protect.set_enabled(False)
42+
await dev.update()
43+
assert protect.enabled is False

0 commit comments

Comments
 (0)
0