From 3372baacefe36e33fba736b39ef1f562c173aff3 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:39:23 +0000 Subject: [PATCH 1/3] Add motion module to smartcam --- kasa/smartcam/modules/__init__.py | 2 ++ kasa/smartcam/modules/motion.py | 47 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 kasa/smartcam/modules/motion.py diff --git a/kasa/smartcam/modules/__init__.py b/kasa/smartcam/modules/__init__.py index fae5923fa..e543cc0d1 100644 --- a/kasa/smartcam/modules/__init__.py +++ b/kasa/smartcam/modules/__init__.py @@ -8,6 +8,7 @@ from .led import Led from .lensmask import LensMask from .matter import Matter +from .motion import Motion from .pantilt import PanTilt from .time import Time @@ -21,5 +22,6 @@ "Time", "HomeKit", "Matter", + "Motion", "LensMask", ] diff --git a/kasa/smartcam/modules/motion.py b/kasa/smartcam/modules/motion.py new file mode 100644 index 000000000..1c44613a5 --- /dev/null +++ b/kasa/smartcam/modules/motion.py @@ -0,0 +1,47 @@ +"""Implementation of motion detection module.""" + +from __future__ import annotations + +import logging + +from ...feature import Feature +from ..smartcammodule import SmartCamModule + +_LOGGER = logging.getLogger(__name__) + + +class Motion(SmartCamModule): + """Implementation of lens mask module.""" + + REQUIRED_COMPONENT = "detection" + + QUERY_GETTER_NAME = "getDetectionConfig" + QUERY_MODULE_NAME = "motion_detection" + QUERY_SECTION_NAMES = "motion_det" + + def _initialize_features(self) -> None: + """Initialize features after the initial update.""" + self._add_feature( + Feature( + self._device, + id="motion_detection_enabled", + name="Motion detection enabled", + container=self, + attribute_getter="enabled", + attribute_setter="set_enabled", + type=Feature.Type.Switch, + category=Feature.Category.Primary, + ) + ) + + @property + def enabled(self) -> bool: + """Return the lens mask state.""" + return self.data["motion_det"]["enabled"] == "on" + + async def set_enabled(self, state: bool) -> dict: + """Set the lens mask state.""" + params = {"enabled": "on" if state else "off"} + return await self._device._query_setter_helper( + "setLensMaskConfig", self.QUERY_MODULE_NAME, "motion_det", params + ) From 241586da9a795306595a83cd81067d146c459e3b Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:21:19 +0000 Subject: [PATCH 2/3] Add smartcam detection modules --- kasa/smartcam/modules/__init__.py | 10 +++- kasa/smartcam/modules/babycrydetection.py | 47 +++++++++++++++++++ .../modules/{motion.py => motiondetection.py} | 14 +++--- kasa/smartcam/modules/persondetection.py | 47 +++++++++++++++++++ kasa/smartcam/modules/tamperdetection.py | 47 +++++++++++++++++++ kasa/smartcam/smartcammodule.py | 12 +++++ .../smartcam/modules/test_babycrydetection.py | 45 ++++++++++++++++++ .../smartcam/modules/test_motiondetection.py | 43 +++++++++++++++++ .../smartcam/modules/test_persondetection.py | 45 ++++++++++++++++++ .../smartcam/modules/test_tamperdetection.py | 45 ++++++++++++++++++ 10 files changed, 346 insertions(+), 9 deletions(-) create mode 100644 kasa/smartcam/modules/babycrydetection.py rename kasa/smartcam/modules/{motion.py => motiondetection.py} (73%) create mode 100644 kasa/smartcam/modules/persondetection.py create mode 100644 kasa/smartcam/modules/tamperdetection.py create mode 100644 tests/smartcam/modules/test_babycrydetection.py create mode 100644 tests/smartcam/modules/test_motiondetection.py create mode 100644 tests/smartcam/modules/test_persondetection.py create mode 100644 tests/smartcam/modules/test_tamperdetection.py diff --git a/kasa/smartcam/modules/__init__.py b/kasa/smartcam/modules/__init__.py index e543cc0d1..3ea4bb6a0 100644 --- a/kasa/smartcam/modules/__init__.py +++ b/kasa/smartcam/modules/__init__.py @@ -1,6 +1,7 @@ """Modules for SMARTCAM devices.""" from .alarm import Alarm +from .babycrydetection import BabyCryDetection from .camera import Camera from .childdevice import ChildDevice from .device import DeviceModule @@ -8,20 +9,25 @@ from .led import Led from .lensmask import LensMask from .matter import Matter -from .motion import Motion +from .motiondetection import MotionDetection from .pantilt import PanTilt +from .persondetection import PersonDetection +from .tamperdetection import TamperDetection from .time import Time __all__ = [ "Alarm", + "BabyCryDetection", "Camera", "ChildDevice", "DeviceModule", "Led", "PanTilt", + "PersonDetection", "Time", "HomeKit", "Matter", - "Motion", + "MotionDetection", "LensMask", + "TamperDetection", ] diff --git a/kasa/smartcam/modules/babycrydetection.py b/kasa/smartcam/modules/babycrydetection.py new file mode 100644 index 000000000..87fcad540 --- /dev/null +++ b/kasa/smartcam/modules/babycrydetection.py @@ -0,0 +1,47 @@ +"""Implementation of baby cry detection module.""" + +from __future__ import annotations + +import logging + +from ...feature import Feature +from ..smartcammodule import SmartCamModule + +_LOGGER = logging.getLogger(__name__) + + +class BabyCryDetection(SmartCamModule): + """Implementation of baby cry detection module.""" + + REQUIRED_COMPONENT = "babyCryDetection" + + QUERY_GETTER_NAME = "getBCDConfig" + QUERY_MODULE_NAME = "sound_detection" + QUERY_SECTION_NAMES = "bcd" + + def _initialize_features(self) -> None: + """Initialize features after the initial update.""" + self._add_feature( + Feature( + self._device, + id="baby_cry_detection_enabled", + name="Baby cry detection enabled", + container=self, + attribute_getter="enabled", + attribute_setter="set_enabled", + type=Feature.Type.Switch, + category=Feature.Category.Primary, + ) + ) + + @property + def enabled(self) -> bool: + """Return the baby cry detection enabled state.""" + return self.data["bcd"]["enabled"] == "on" + + async def set_enabled(self, enable: bool) -> dict: + """Set the baby cry detection enabled state.""" + params = {"enabled": "on" if enable else "off"} + return await self._device._query_setter_helper( + "setBCDConfig", self.QUERY_MODULE_NAME, "bcd", params + ) diff --git a/kasa/smartcam/modules/motion.py b/kasa/smartcam/modules/motiondetection.py similarity index 73% rename from kasa/smartcam/modules/motion.py rename to kasa/smartcam/modules/motiondetection.py index 1c44613a5..173f645a3 100644 --- a/kasa/smartcam/modules/motion.py +++ b/kasa/smartcam/modules/motiondetection.py @@ -10,8 +10,8 @@ _LOGGER = logging.getLogger(__name__) -class Motion(SmartCamModule): - """Implementation of lens mask module.""" +class MotionDetection(SmartCamModule): + """Implementation of motion detection module.""" REQUIRED_COMPONENT = "detection" @@ -36,12 +36,12 @@ def _initialize_features(self) -> None: @property def enabled(self) -> bool: - """Return the lens mask state.""" + """Return the motion detection enabled state.""" return self.data["motion_det"]["enabled"] == "on" - async def set_enabled(self, state: bool) -> dict: - """Set the lens mask state.""" - params = {"enabled": "on" if state else "off"} + async def set_enabled(self, enable: bool) -> dict: + """Set the motion detection enabled state.""" + params = {"enabled": "on" if enable else "off"} return await self._device._query_setter_helper( - "setLensMaskConfig", self.QUERY_MODULE_NAME, "motion_det", params + "setDetectionConfig", self.QUERY_MODULE_NAME, "motion_det", params ) diff --git a/kasa/smartcam/modules/persondetection.py b/kasa/smartcam/modules/persondetection.py new file mode 100644 index 000000000..f88fa2085 --- /dev/null +++ b/kasa/smartcam/modules/persondetection.py @@ -0,0 +1,47 @@ +"""Implementation of person detection module.""" + +from __future__ import annotations + +import logging + +from ...feature import Feature +from ..smartcammodule import SmartCamModule + +_LOGGER = logging.getLogger(__name__) + + +class PersonDetection(SmartCamModule): + """Implementation of person detection module.""" + + REQUIRED_COMPONENT = "personDetection" + + QUERY_GETTER_NAME = "getPersonDetectionConfig" + QUERY_MODULE_NAME = "people_detection" + QUERY_SECTION_NAMES = "detection" + + def _initialize_features(self) -> None: + """Initialize features after the initial update.""" + self._add_feature( + Feature( + self._device, + id="person_detection_enabled", + name="Person detection enabled", + container=self, + attribute_getter="enabled", + attribute_setter="set_enabled", + type=Feature.Type.Switch, + category=Feature.Category.Primary, + ) + ) + + @property + def enabled(self) -> bool: + """Return the person detection enabled state.""" + return self.data["detection"]["enabled"] == "on" + + async def set_enabled(self, enable: bool) -> dict: + """Set the person detection enabled state.""" + params = {"enabled": "on" if enable else "off"} + return await self._device._query_setter_helper( + "setPersonDetectionConfig", self.QUERY_MODULE_NAME, "detection", params + ) diff --git a/kasa/smartcam/modules/tamperdetection.py b/kasa/smartcam/modules/tamperdetection.py new file mode 100644 index 000000000..66d830b29 --- /dev/null +++ b/kasa/smartcam/modules/tamperdetection.py @@ -0,0 +1,47 @@ +"""Implementation of tamper detection module.""" + +from __future__ import annotations + +import logging + +from ...feature import Feature +from ..smartcammodule import SmartCamModule + +_LOGGER = logging.getLogger(__name__) + + +class TamperDetection(SmartCamModule): + """Implementation of tamper detection module.""" + + REQUIRED_COMPONENT = "tamperDetection" + + QUERY_GETTER_NAME = "getTamperDetectionConfig" + QUERY_MODULE_NAME = "tamper_detection" + QUERY_SECTION_NAMES = "tamper_det" + + def _initialize_features(self) -> None: + """Initialize features after the initial update.""" + self._add_feature( + Feature( + self._device, + id="tamper_detection_enabled", + name="Tamper detection enabled", + container=self, + attribute_getter="enabled", + attribute_setter="set_enabled", + type=Feature.Type.Switch, + category=Feature.Category.Primary, + ) + ) + + @property + def enabled(self) -> bool: + """Return the tamper detection enabled state.""" + return self.data["tamper_det"]["enabled"] == "on" + + async def set_enabled(self, enable: bool) -> dict: + """Set the tamper detection enabled state.""" + params = {"enabled": "on" if enable else "off"} + return await self._device._query_setter_helper( + "setTamperDetectionConfig", self.QUERY_MODULE_NAME, "tamper_det", params + ) diff --git a/kasa/smartcam/smartcammodule.py b/kasa/smartcam/smartcammodule.py index 390335974..467d18c02 100644 --- a/kasa/smartcam/smartcammodule.py +++ b/kasa/smartcam/smartcammodule.py @@ -20,6 +20,18 @@ class SmartCamModule(SmartModule): """Base class for SMARTCAM modules.""" SmartCamAlarm: Final[ModuleName[modules.Alarm]] = ModuleName("SmartCamAlarm") + SmartCamMotionDetection: Final[ModuleName[modules.MotionDetection]] = ModuleName( + "MotionDetection" + ) + SmartCamPersonDetection: Final[ModuleName[modules.PersonDetection]] = ModuleName( + "PersonDetection" + ) + SmartCamTamperDetection: Final[ModuleName[modules.TamperDetection]] = ModuleName( + "TamperDetection" + ) + SmartCamBabyCryDetection: Final[ModuleName[modules.BabyCryDetection]] = ModuleName( + "BabyCryDetection" + ) #: Module name to be queried QUERY_MODULE_NAME: str diff --git a/tests/smartcam/modules/test_babycrydetection.py b/tests/smartcam/modules/test_babycrydetection.py new file mode 100644 index 000000000..6f0a7dc09 --- /dev/null +++ b/tests/smartcam/modules/test_babycrydetection.py @@ -0,0 +1,45 @@ +"""Tests for smartcam baby cry detection module.""" + +from __future__ import annotations + +from kasa import Device +from kasa.smartcam.smartcammodule import SmartCamModule + +from ...device_fixtures import parametrize + +babycrydetection = parametrize( + "has babycry detection", + component_filter="babyCryDetection", + protocol_filter={"SMARTCAM"}, +) + + +@babycrydetection +async def test_babycrydetection(dev: Device): + """Test device babycry detection.""" + babycry = dev.modules.get(SmartCamModule.SmartCamBabyCryDetection) + assert babycry + + bcde_feat = dev.features.get("baby_cry_detection_enabled") + assert bcde_feat + + original_enabled = babycry.enabled + + try: + await babycry.set_enabled(not original_enabled) + await dev.update() + assert babycry.enabled is not original_enabled + assert bcde_feat.value is not original_enabled + + await babycry.set_enabled(original_enabled) + await dev.update() + assert babycry.enabled is original_enabled + assert bcde_feat.value is original_enabled + + await bcde_feat.set_value(not original_enabled) + await dev.update() + assert babycry.enabled is not original_enabled + assert bcde_feat.value is not original_enabled + + finally: + await babycry.set_enabled(original_enabled) diff --git a/tests/smartcam/modules/test_motiondetection.py b/tests/smartcam/modules/test_motiondetection.py new file mode 100644 index 000000000..f41776ae7 --- /dev/null +++ b/tests/smartcam/modules/test_motiondetection.py @@ -0,0 +1,43 @@ +"""Tests for smartcam motion detection module.""" + +from __future__ import annotations + +from kasa import Device +from kasa.smartcam.smartcammodule import SmartCamModule + +from ...device_fixtures import parametrize + +motiondetection = parametrize( + "has motion detection", component_filter="detection", protocol_filter={"SMARTCAM"} +) + + +@motiondetection +async def test_motiondetection(dev: Device): + """Test device motion detection.""" + motion = dev.modules.get(SmartCamModule.SmartCamMotionDetection) + assert motion + + mde_feat = dev.features.get("motion_detection_enabled") + assert mde_feat + + original_enabled = motion.enabled + + try: + await motion.set_enabled(not original_enabled) + await dev.update() + assert motion.enabled is not original_enabled + assert mde_feat.value is not original_enabled + + await motion.set_enabled(original_enabled) + await dev.update() + assert motion.enabled is original_enabled + assert mde_feat.value is original_enabled + + await mde_feat.set_value(not original_enabled) + await dev.update() + assert motion.enabled is not original_enabled + assert mde_feat.value is not original_enabled + + finally: + await motion.set_enabled(original_enabled) diff --git a/tests/smartcam/modules/test_persondetection.py b/tests/smartcam/modules/test_persondetection.py new file mode 100644 index 000000000..678bdc0ba --- /dev/null +++ b/tests/smartcam/modules/test_persondetection.py @@ -0,0 +1,45 @@ +"""Tests for smartcam person detection module.""" + +from __future__ import annotations + +from kasa import Device +from kasa.smartcam.smartcammodule import SmartCamModule + +from ...device_fixtures import parametrize + +persondetection = parametrize( + "has person detection", + component_filter="personDetection", + protocol_filter={"SMARTCAM"}, +) + + +@persondetection +async def test_persondetection(dev: Device): + """Test device person detection.""" + person = dev.modules.get(SmartCamModule.SmartCamPersonDetection) + assert person + + pde_feat = dev.features.get("person_detection_enabled") + assert pde_feat + + original_enabled = person.enabled + + try: + await person.set_enabled(not original_enabled) + await dev.update() + assert person.enabled is not original_enabled + assert pde_feat.value is not original_enabled + + await person.set_enabled(original_enabled) + await dev.update() + assert person.enabled is original_enabled + assert pde_feat.value is original_enabled + + await pde_feat.set_value(not original_enabled) + await dev.update() + assert person.enabled is not original_enabled + assert pde_feat.value is not original_enabled + + finally: + await person.set_enabled(original_enabled) diff --git a/tests/smartcam/modules/test_tamperdetection.py b/tests/smartcam/modules/test_tamperdetection.py new file mode 100644 index 000000000..4233b2176 --- /dev/null +++ b/tests/smartcam/modules/test_tamperdetection.py @@ -0,0 +1,45 @@ +"""Tests for smartcam tamper detection module.""" + +from __future__ import annotations + +from kasa import Device +from kasa.smartcam.smartcammodule import SmartCamModule + +from ...device_fixtures import parametrize + +tamperdetection = parametrize( + "has tamper detection", + component_filter="tamperDetection", + protocol_filter={"SMARTCAM"}, +) + + +@tamperdetection +async def test_tamperdetection(dev: Device): + """Test device tamper detection.""" + tamper = dev.modules.get(SmartCamModule.SmartCamTamperDetection) + assert tamper + + tde_feat = dev.features.get("tamper_detection_enabled") + assert tde_feat + + original_enabled = tamper.enabled + + try: + await tamper.set_enabled(not original_enabled) + await dev.update() + assert tamper.enabled is not original_enabled + assert tde_feat.value is not original_enabled + + await tamper.set_enabled(original_enabled) + await dev.update() + assert tamper.enabled is original_enabled + assert tde_feat.value is original_enabled + + await tde_feat.set_value(not original_enabled) + await dev.update() + assert tamper.enabled is not original_enabled + assert tde_feat.value is not original_enabled + + finally: + await tamper.set_enabled(original_enabled) From 9b337931dc19f0ec869ad55c7e27aa7b8e5c49c9 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:28:13 +0000 Subject: [PATCH 3/3] Remove _enabled suffix from features --- kasa/smartcam/modules/babycrydetection.py | 4 ++-- kasa/smartcam/modules/motiondetection.py | 4 ++-- kasa/smartcam/modules/persondetection.py | 4 ++-- kasa/smartcam/modules/tamperdetection.py | 4 ++-- tests/smartcam/modules/test_babycrydetection.py | 2 +- tests/smartcam/modules/test_motiondetection.py | 2 +- tests/smartcam/modules/test_persondetection.py | 2 +- tests/smartcam/modules/test_tamperdetection.py | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/kasa/smartcam/modules/babycrydetection.py b/kasa/smartcam/modules/babycrydetection.py index 87fcad540..e9e323717 100644 --- a/kasa/smartcam/modules/babycrydetection.py +++ b/kasa/smartcam/modules/babycrydetection.py @@ -24,8 +24,8 @@ def _initialize_features(self) -> None: self._add_feature( Feature( self._device, - id="baby_cry_detection_enabled", - name="Baby cry detection enabled", + id="baby_cry_detection", + name="Baby cry detection", container=self, attribute_getter="enabled", attribute_setter="set_enabled", diff --git a/kasa/smartcam/modules/motiondetection.py b/kasa/smartcam/modules/motiondetection.py index 173f645a3..33067bdff 100644 --- a/kasa/smartcam/modules/motiondetection.py +++ b/kasa/smartcam/modules/motiondetection.py @@ -24,8 +24,8 @@ def _initialize_features(self) -> None: self._add_feature( Feature( self._device, - id="motion_detection_enabled", - name="Motion detection enabled", + id="motion_detection", + name="Motion detection", container=self, attribute_getter="enabled", attribute_setter="set_enabled", diff --git a/kasa/smartcam/modules/persondetection.py b/kasa/smartcam/modules/persondetection.py index f88fa2085..641609d54 100644 --- a/kasa/smartcam/modules/persondetection.py +++ b/kasa/smartcam/modules/persondetection.py @@ -24,8 +24,8 @@ def _initialize_features(self) -> None: self._add_feature( Feature( self._device, - id="person_detection_enabled", - name="Person detection enabled", + id="person_detection", + name="Person detection", container=self, attribute_getter="enabled", attribute_setter="set_enabled", diff --git a/kasa/smartcam/modules/tamperdetection.py b/kasa/smartcam/modules/tamperdetection.py index 66d830b29..32b352f79 100644 --- a/kasa/smartcam/modules/tamperdetection.py +++ b/kasa/smartcam/modules/tamperdetection.py @@ -24,8 +24,8 @@ def _initialize_features(self) -> None: self._add_feature( Feature( self._device, - id="tamper_detection_enabled", - name="Tamper detection enabled", + id="tamper_detection", + name="Tamper detection", container=self, attribute_getter="enabled", attribute_setter="set_enabled", diff --git a/tests/smartcam/modules/test_babycrydetection.py b/tests/smartcam/modules/test_babycrydetection.py index 6f0a7dc09..89ff5ac43 100644 --- a/tests/smartcam/modules/test_babycrydetection.py +++ b/tests/smartcam/modules/test_babycrydetection.py @@ -20,7 +20,7 @@ async def test_babycrydetection(dev: Device): babycry = dev.modules.get(SmartCamModule.SmartCamBabyCryDetection) assert babycry - bcde_feat = dev.features.get("baby_cry_detection_enabled") + bcde_feat = dev.features.get("baby_cry_detection") assert bcde_feat original_enabled = babycry.enabled diff --git a/tests/smartcam/modules/test_motiondetection.py b/tests/smartcam/modules/test_motiondetection.py index f41776ae7..c4ff98079 100644 --- a/tests/smartcam/modules/test_motiondetection.py +++ b/tests/smartcam/modules/test_motiondetection.py @@ -18,7 +18,7 @@ async def test_motiondetection(dev: Device): motion = dev.modules.get(SmartCamModule.SmartCamMotionDetection) assert motion - mde_feat = dev.features.get("motion_detection_enabled") + mde_feat = dev.features.get("motion_detection") assert mde_feat original_enabled = motion.enabled diff --git a/tests/smartcam/modules/test_persondetection.py b/tests/smartcam/modules/test_persondetection.py index 678bdc0ba..341375878 100644 --- a/tests/smartcam/modules/test_persondetection.py +++ b/tests/smartcam/modules/test_persondetection.py @@ -20,7 +20,7 @@ async def test_persondetection(dev: Device): person = dev.modules.get(SmartCamModule.SmartCamPersonDetection) assert person - pde_feat = dev.features.get("person_detection_enabled") + pde_feat = dev.features.get("person_detection") assert pde_feat original_enabled = person.enabled diff --git a/tests/smartcam/modules/test_tamperdetection.py b/tests/smartcam/modules/test_tamperdetection.py index 4233b2176..ab2f851d5 100644 --- a/tests/smartcam/modules/test_tamperdetection.py +++ b/tests/smartcam/modules/test_tamperdetection.py @@ -20,7 +20,7 @@ async def test_tamperdetection(dev: Device): tamper = dev.modules.get(SmartCamModule.SmartCamTamperDetection) assert tamper - tde_feat = dev.features.get("tamper_detection_enabled") + tde_feat = dev.features.get("tamper_detection") assert tde_feat original_enabled = tamper.enabled