diff --git a/SUPPORTED.md b/SUPPORTED.md index 200689b6..935b25e1 100644 --- a/SUPPORTED.md +++ b/SUPPORTED.md @@ -293,6 +293,7 @@ All Tapo devices require authentication.
Hub-Connected Devices may work acros - Hardware: 2.0 (EU) / Firmware: 1.4.3 - **C220** - Hardware: 1.0 (EU) / Firmware: 1.2.2 + - Hardware: 1.0 (EU) / Firmware: 1.2.5 - **C225** - Hardware: 2.0 (US) / Firmware: 1.0.11 - **C325WB** diff --git a/devtools/helpers/smartcamrequests.py b/devtools/helpers/smartcamrequests.py index 5759a44b..6c60b12a 100644 --- a/devtools/helpers/smartcamrequests.py +++ b/devtools/helpers/smartcamrequests.py @@ -44,6 +44,11 @@ SMARTCAM_REQUESTS: list[dict] = [ {"getWhitelampConfig": {"image": {"name": "switch"}}}, {"getMsgPushConfig": {"msg_push": {"name": ["chn1_msg_push_info"]}}}, {"getSdCardStatus": {"harddisk_manage": {"table": ["hd_info"]}}}, + { + "getLinecrossingDetectionConfig": { + "linecrossing_detection": {"name": ["detection", "arming_schedule"]} + } + }, {"getCircularRecordingConfig": {"harddisk_manage": {"name": "harddisk"}}}, {"getRecordPlan": {"record_plan": {"name": ["chn1_channel"]}}}, {"getAudioConfig": {"audio_config": {"name": ["speaker", "microphone"]}}}, diff --git a/kasa/smartcam/__init__.py b/kasa/smartcam/__init__.py index 21cbeb50..b16e5410 100644 --- a/kasa/smartcam/__init__.py +++ b/kasa/smartcam/__init__.py @@ -1,6 +1,7 @@ """Package for supporting tapo-branded cameras.""" +from .detectionmodule import DetectionModule from .smartcamchild import SmartCamChild from .smartcamdevice import SmartCamDevice -__all__ = ["SmartCamDevice", "SmartCamChild"] +__all__ = ["SmartCamDevice", "SmartCamChild", "DetectionModule"] diff --git a/kasa/smartcam/detectionmodule.py b/kasa/smartcam/detectionmodule.py new file mode 100644 index 00000000..9a147f99 --- /dev/null +++ b/kasa/smartcam/detectionmodule.py @@ -0,0 +1,58 @@ +"""SmartCamModule base class for all detections.""" + +from __future__ import annotations + +import logging + +from kasa.feature import Feature +from kasa.smart.smartmodule import allow_update_after +from kasa.smartcam.smartcammodule import SmartCamModule + +_LOGGER = logging.getLogger(__name__) + + +class DetectionModule(SmartCamModule): + """SmartCamModule base class for all detections.""" + + #: Feature ID, filled by inheriting class + DETECTION_FEATURE_ID: str = "" + + #: User-friendly short description, filled by inheriting class + DETECTION_FEATURE_NAME: str = "" + + #: Feature setter method name, filled by inheriting class + QUERY_SETTER_NAME: str = "" + + #: Feature section name, filled by inheriting class + QUERY_SET_SECTION_NAME: str = "" + + def _initialize_features(self) -> None: + """Initialize features after the initial update.""" + self._add_feature( + Feature( + self._device, + id=self.DETECTION_FEATURE_ID, + name=self.DETECTION_FEATURE_NAME, + container=self, + attribute_getter="enabled", + attribute_setter="set_enabled", + type=Feature.Type.Switch, + category=Feature.Category.Config, + ) + ) + + @property + def enabled(self) -> bool: + """Return the detection enabled state.""" + return self.data[self.QUERY_SECTION_NAMES]["enabled"] == "on" + + @allow_update_after + async def set_enabled(self, enable: bool) -> dict: + """Set the detection enabled state.""" + params = {"enabled": "on" if enable else "off"} + return await self._device._query_setter_helper( + self.QUERY_SETTER_NAME, + self.QUERY_MODULE_NAME, + self.QUERY_SET_SECTION_NAME, + params, + ) diff --git a/kasa/smartcam/modules/__init__.py b/kasa/smartcam/modules/__init__.py index 4f6ed866..3434bddb 100644 --- a/kasa/smartcam/modules/__init__.py +++ b/kasa/smartcam/modules/__init__.py @@ -2,31 +2,40 @@ from .alarm import Alarm from .babycrydetection import BabyCryDetection +from .barkdetection import BarkDetection from .battery import Battery from .camera import Camera from .childdevice import ChildDevice from .childsetup import ChildSetup from .device import DeviceModule +from .glassdetection import GlassDetection from .homekit import HomeKit from .led import Led from .lensmask import LensMask +from .linecrossingdetection import LineCrossingDetection from .matter import Matter +from .meowdetection import MeowDetection from .motiondetection import MotionDetection from .pantilt import PanTilt from .persondetection import PersonDetection from .petdetection import PetDetection from .tamperdetection import TamperDetection from .time import Time +from .vehicledetection import VehicleDetection __all__ = [ "Alarm", "BabyCryDetection", + "BarkDetection", "Battery", "Camera", "ChildDevice", "ChildSetup", "DeviceModule", + "GlassDetection", "Led", + "LineCrossingDetection", + "MeowDetection", "PanTilt", "PersonDetection", "PetDetection", @@ -36,4 +45,5 @@ __all__ = [ "MotionDetection", "LensMask", "TamperDetection", + "VehicleDetection", ] diff --git a/kasa/smartcam/modules/babycrydetection.py b/kasa/smartcam/modules/babycrydetection.py index 75399885..6289b517 100644 --- a/kasa/smartcam/modules/babycrydetection.py +++ b/kasa/smartcam/modules/babycrydetection.py @@ -4,14 +4,12 @@ from __future__ import annotations import logging -from ...feature import Feature -from ...smart.smartmodule import allow_update_after -from ..smartcammodule import SmartCamModule +from kasa.smartcam.detectionmodule import DetectionModule _LOGGER = logging.getLogger(__name__) -class BabyCryDetection(SmartCamModule): +class BabyCryDetection(DetectionModule): """Implementation of baby cry detection module.""" REQUIRED_COMPONENT = "babyCryDetection" @@ -20,30 +18,7 @@ class BabyCryDetection(SmartCamModule): 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", - name="Baby cry detection", - container=self, - attribute_getter="enabled", - attribute_setter="set_enabled", - type=Feature.Type.Switch, - category=Feature.Category.Config, - ) - ) - - @property - def enabled(self) -> bool: - """Return the baby cry detection enabled state.""" - return self.data["bcd"]["enabled"] == "on" - - @allow_update_after - 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 - ) + DETECTION_FEATURE_ID = "baby_cry_detection" + DETECTION_FEATURE_NAME = "Baby cry detection" + QUERY_SETTER_NAME = "setBCDConfig" + QUERY_SET_SECTION_NAME = "bcd" diff --git a/kasa/smartcam/modules/barkdetection.py b/kasa/smartcam/modules/barkdetection.py new file mode 100644 index 00000000..8c3b3b82 --- /dev/null +++ b/kasa/smartcam/modules/barkdetection.py @@ -0,0 +1,24 @@ +"""Implementation of bark detection module.""" + +from __future__ import annotations + +import logging + +from kasa.smartcam.detectionmodule import DetectionModule + +_LOGGER = logging.getLogger(__name__) + + +class BarkDetection(DetectionModule): + """Implementation of bark detection module.""" + + REQUIRED_COMPONENT = "barkDetection" + + QUERY_GETTER_NAME = "getBarkDetectionConfig" + QUERY_MODULE_NAME = "bark_detection" + QUERY_SECTION_NAMES = "detection" + + DETECTION_FEATURE_ID = "bark_detection" + DETECTION_FEATURE_NAME = "Bark detection" + QUERY_SETTER_NAME = "setBarkDetectionConfig" + QUERY_SET_SECTION_NAME = "detection" diff --git a/kasa/smartcam/modules/glassdetection.py b/kasa/smartcam/modules/glassdetection.py new file mode 100644 index 00000000..bd0c7ea9 --- /dev/null +++ b/kasa/smartcam/modules/glassdetection.py @@ -0,0 +1,24 @@ +"""Implementation of glass detection module.""" + +from __future__ import annotations + +import logging + +from kasa.smartcam.detectionmodule import DetectionModule + +_LOGGER = logging.getLogger(__name__) + + +class GlassDetection(DetectionModule): + """Implementation of glass detection module.""" + + REQUIRED_COMPONENT = "glassDetection" + + QUERY_GETTER_NAME = "getGlassDetectionConfig" + QUERY_MODULE_NAME = "glass_detection" + QUERY_SECTION_NAMES = "detection" + + DETECTION_FEATURE_ID = "glass_detection" + DETECTION_FEATURE_NAME = "Glass detection" + QUERY_SETTER_NAME = "setGlassDetectionConfig" + QUERY_SET_SECTION_NAME = "detection" diff --git a/kasa/smartcam/modules/linecrossingdetection.py b/kasa/smartcam/modules/linecrossingdetection.py new file mode 100644 index 00000000..2fc0de14 --- /dev/null +++ b/kasa/smartcam/modules/linecrossingdetection.py @@ -0,0 +1,24 @@ +"""Implementation of line crossing detection module.""" + +from __future__ import annotations + +import logging + +from kasa.smartcam.detectionmodule import DetectionModule + +_LOGGER = logging.getLogger(__name__) + + +class LineCrossingDetection(DetectionModule): + """Implementation of line crossing detection module.""" + + REQUIRED_COMPONENT = "linecrossingDetection" + + QUERY_GETTER_NAME = "getLinecrossingDetectionConfig" + QUERY_MODULE_NAME = "linecrossing_detection" + QUERY_SECTION_NAMES = "detection" + + DETECTION_FEATURE_ID = "line_crossing_detection" + DETECTION_FEATURE_NAME = "Line crossing detection" + QUERY_SETTER_NAME = "setLinecrossingDetectionConfig" + QUERY_SET_SECTION_NAME = "detection" diff --git a/kasa/smartcam/modules/meowdetection.py b/kasa/smartcam/modules/meowdetection.py new file mode 100644 index 00000000..6c02bfba --- /dev/null +++ b/kasa/smartcam/modules/meowdetection.py @@ -0,0 +1,24 @@ +"""Implementation of meow detection module.""" + +from __future__ import annotations + +import logging + +from kasa.smartcam.detectionmodule import DetectionModule + +_LOGGER = logging.getLogger(__name__) + + +class MeowDetection(DetectionModule): + """Implementation of meow detection module.""" + + REQUIRED_COMPONENT = "meowDetection" + + QUERY_GETTER_NAME = "getMeowDetectionConfig" + QUERY_MODULE_NAME = "meow_detection" + QUERY_SECTION_NAMES = "detection" + + DETECTION_FEATURE_ID = "meow_detection" + DETECTION_FEATURE_NAME = "Meow detection" + QUERY_SETTER_NAME = "setMeowDetectionConfig" + QUERY_SET_SECTION_NAME = "detection" diff --git a/kasa/smartcam/modules/motiondetection.py b/kasa/smartcam/modules/motiondetection.py index dd3c168e..df9b1863 100644 --- a/kasa/smartcam/modules/motiondetection.py +++ b/kasa/smartcam/modules/motiondetection.py @@ -4,14 +4,12 @@ from __future__ import annotations import logging -from ...feature import Feature -from ...smart.smartmodule import allow_update_after -from ..smartcammodule import SmartCamModule +from kasa.smartcam.detectionmodule import DetectionModule _LOGGER = logging.getLogger(__name__) -class MotionDetection(SmartCamModule): +class MotionDetection(DetectionModule): """Implementation of motion detection module.""" REQUIRED_COMPONENT = "detection" @@ -20,30 +18,7 @@ class MotionDetection(SmartCamModule): 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", - name="Motion detection", - container=self, - attribute_getter="enabled", - attribute_setter="set_enabled", - type=Feature.Type.Switch, - category=Feature.Category.Config, - ) - ) - - @property - def enabled(self) -> bool: - """Return the motion detection enabled state.""" - return self.data["motion_det"]["enabled"] == "on" - - @allow_update_after - 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( - "setDetectionConfig", self.QUERY_MODULE_NAME, "motion_det", params - ) + DETECTION_FEATURE_ID = "motion_detection" + DETECTION_FEATURE_NAME = "Motion detection" + QUERY_SETTER_NAME = "setDetectionConfig" + QUERY_SET_SECTION_NAME = "motion_det" diff --git a/kasa/smartcam/modules/persondetection.py b/kasa/smartcam/modules/persondetection.py index 96b31dc4..3b1213e8 100644 --- a/kasa/smartcam/modules/persondetection.py +++ b/kasa/smartcam/modules/persondetection.py @@ -4,14 +4,12 @@ from __future__ import annotations import logging -from ...feature import Feature -from ...smart.smartmodule import allow_update_after -from ..smartcammodule import SmartCamModule +from kasa.smartcam.detectionmodule import DetectionModule _LOGGER = logging.getLogger(__name__) -class PersonDetection(SmartCamModule): +class PersonDetection(DetectionModule): """Implementation of person detection module.""" REQUIRED_COMPONENT = "personDetection" @@ -20,30 +18,7 @@ class PersonDetection(SmartCamModule): 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", - name="Person detection", - container=self, - attribute_getter="enabled", - attribute_setter="set_enabled", - type=Feature.Type.Switch, - category=Feature.Category.Config, - ) - ) - - @property - def enabled(self) -> bool: - """Return the person detection enabled state.""" - return self.data["detection"]["enabled"] == "on" - - @allow_update_after - 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 - ) + DETECTION_FEATURE_ID = "person_detection" + DETECTION_FEATURE_NAME = "Person detection" + QUERY_SETTER_NAME = "setPersonDetectionConfig" + QUERY_SET_SECTION_NAME = "detection" diff --git a/kasa/smartcam/modules/petdetection.py b/kasa/smartcam/modules/petdetection.py index 2c716230..58ff5cc4 100644 --- a/kasa/smartcam/modules/petdetection.py +++ b/kasa/smartcam/modules/petdetection.py @@ -4,14 +4,12 @@ from __future__ import annotations import logging -from ...feature import Feature -from ...smart.smartmodule import allow_update_after -from ..smartcammodule import SmartCamModule +from kasa.smartcam.detectionmodule import DetectionModule _LOGGER = logging.getLogger(__name__) -class PetDetection(SmartCamModule): +class PetDetection(DetectionModule): """Implementation of pet detection module.""" REQUIRED_COMPONENT = "petDetection" @@ -20,30 +18,7 @@ class PetDetection(SmartCamModule): QUERY_MODULE_NAME = "pet_detection" QUERY_SECTION_NAMES = "detection" - def _initialize_features(self) -> None: - """Initialize features after the initial update.""" - self._add_feature( - Feature( - self._device, - id="pet_detection", - name="Pet detection", - container=self, - attribute_getter="enabled", - attribute_setter="set_enabled", - type=Feature.Type.Switch, - category=Feature.Category.Config, - ) - ) - - @property - def enabled(self) -> bool: - """Return the pet detection enabled state.""" - return self.data["detection"]["enabled"] == "on" - - @allow_update_after - async def set_enabled(self, enable: bool) -> dict: - """Set the pet detection enabled state.""" - params = {"enabled": "on" if enable else "off"} - return await self._device._query_setter_helper( - "setPetDetectionConfig", self.QUERY_MODULE_NAME, "detection", params - ) + DETECTION_FEATURE_ID = "pet_detection" + DETECTION_FEATURE_NAME = "Pet detection" + QUERY_SETTER_NAME = "setPetDetectionConfig" + QUERY_SET_SECTION_NAME = "detection" diff --git a/kasa/smartcam/modules/tamperdetection.py b/kasa/smartcam/modules/tamperdetection.py index f572ded6..aa1cc474 100644 --- a/kasa/smartcam/modules/tamperdetection.py +++ b/kasa/smartcam/modules/tamperdetection.py @@ -4,14 +4,12 @@ from __future__ import annotations import logging -from ...feature import Feature -from ...smart.smartmodule import allow_update_after -from ..smartcammodule import SmartCamModule +from kasa.smartcam.detectionmodule import DetectionModule _LOGGER = logging.getLogger(__name__) -class TamperDetection(SmartCamModule): +class TamperDetection(DetectionModule): """Implementation of tamper detection module.""" REQUIRED_COMPONENT = "tamperDetection" @@ -20,30 +18,7 @@ class TamperDetection(SmartCamModule): 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", - name="Tamper detection", - container=self, - attribute_getter="enabled", - attribute_setter="set_enabled", - type=Feature.Type.Switch, - category=Feature.Category.Config, - ) - ) - - @property - def enabled(self) -> bool: - """Return the tamper detection enabled state.""" - return self.data["tamper_det"]["enabled"] == "on" - - @allow_update_after - 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 - ) + DETECTION_FEATURE_ID = "tamper_detection" + DETECTION_FEATURE_NAME = "Tamper detection" + QUERY_SETTER_NAME = "setTamperDetectionConfig" + QUERY_SET_SECTION_NAME = "tamper_det" diff --git a/kasa/smartcam/modules/vehicledetection.py b/kasa/smartcam/modules/vehicledetection.py new file mode 100644 index 00000000..f5da9b0c --- /dev/null +++ b/kasa/smartcam/modules/vehicledetection.py @@ -0,0 +1,24 @@ +"""Implementation of vehicle detection module.""" + +from __future__ import annotations + +import logging + +from kasa.smartcam.detectionmodule import DetectionModule + +_LOGGER = logging.getLogger(__name__) + + +class VehicleDetection(DetectionModule): + """Implementation of vehicle detection module.""" + + REQUIRED_COMPONENT = "vehicleDetection" + + QUERY_GETTER_NAME = "getVehicleDetectionConfig" + QUERY_MODULE_NAME = "vehicle_detection" + QUERY_SECTION_NAMES = "detection" + + DETECTION_FEATURE_ID = "vehicle_detection" + DETECTION_FEATURE_NAME = "Vehicle detection" + QUERY_SETTER_NAME = "setVehicleDetectionConfig" + QUERY_SET_SECTION_NAME = "detection" diff --git a/kasa/smartcam/smartcammodule.py b/kasa/smartcam/smartcammodule.py index 400b1674..7402b8cb 100644 --- a/kasa/smartcam/smartcammodule.py +++ b/kasa/smartcam/smartcammodule.py @@ -36,6 +36,22 @@ class SmartCamModule(SmartModule): "BabyCryDetection" ) + SmartCamLineCrossingDetection: Final[ModuleName[modules.LineCrossingDetection]] = ( + ModuleName("LineCrossingDetection") + ) + SmartCamBarkDetection: Final[ModuleName[modules.BarkDetection]] = ModuleName( + "BarkDetection" + ) + SmartCamGlassDetection: Final[ModuleName[modules.GlassDetection]] = ModuleName( + "GlassDetection" + ) + SmartCamMeowDetection: Final[ModuleName[modules.MeowDetection]] = ModuleName( + "MeowDetection" + ) + SmartCamVehicleDetection: Final[ModuleName[modules.VehicleDetection]] = ModuleName( + "VehicleDetection" + ) + SmartCamBattery: Final[ModuleName[modules.Battery]] = ModuleName("Battery") SmartCamDeviceModule: Final[ModuleName[modules.DeviceModule]] = ModuleName( diff --git a/tests/fixtures/smartcam/C220(EU)_1.0_1.2.5.json b/tests/fixtures/smartcam/C220(EU)_1.0_1.2.5.json new file mode 100644 index 00000000..5fc86df9 --- /dev/null +++ b/tests/fixtures/smartcam/C220(EU)_1.0_1.2.5.json @@ -0,0 +1,1139 @@ +{ + "discovery_result": { + "error_code": 0, + "result": { + "decrypted_data": { + "connect_ssid": "#MASKED_SSID#", + "connect_type": "wireless", + "device_id": "0000000000000000000000000000000000000000", + "http_port": 443, + "last_alarm_time": "1750109746", + "last_alarm_type": "motion", + "owner": "00000000000000000000000000000000", + "sd_status": "offline" + }, + "device_id": "00000000000000000000000000000000", + "device_model": "C220", + "device_name": "#MASKED_NAME#", + "device_type": "SMART.IPCAMERA", + "encrypt_info": { + "data": "", + "key": "", + "sym_schm": "AES" + }, + "encrypt_type": [ + "3" + ], + "factory_default": false, + "firmware_version": "1.2.5 Build 241224 Rel.40956n", + "hardware_version": "1.0", + "ip": "127.0.0.123", + "isResetWiFi": false, + "is_support_iot_cloud": true, + "mac": "7C-F1-7E-00-00-00", + "mgt_encrypt_schm": { + "is_support_https": true + }, + "protocol_version": 1 + } + }, + "getAlertConfig": { + "msg_alarm": { + "capability": { + "alarm_duration_support": "1", + "alarm_func": [ + "sound", + "light" + ], + "alarm_volume_support": "1", + "alert_event_type_support": "1", + "usr_def_audio_alarm_max_num": "2", + "usr_def_audio_alarm_support": "1", + "usr_def_audio_max_duration": "15", + "usr_def_audio_type": "0", + "usr_def_start_file_id": "8195" + }, + "chn1_msg_alarm_info": { + "alarm_duration": "0", + "alarm_mode": [ + "light", + "sound" + ], + "alarm_type": "0", + "alarm_volume": "high", + "enabled": "off", + "light_alarm_enabled": "on", + "light_type": "1", + "sound_alarm_enabled": "on" + }, + "usr_def_audio": [] + } + }, + "getAlertPlan": { + "msg_alarm_plan": { + "chn1_msg_alarm_plan": { + "alarm_plan_1": "0000-0000,127", + "enabled": "off" + } + } + }, + "getAlertTypeList": { + "msg_alarm": { + "alert_type": { + "alert_type_list": [ + "Siren", + "Emergency", + "Red Alert" + ] + } + } + }, + "getAppComponentList": { + "app_component": { + "app_component_list": [ + { + "name": "sdCard", + "version": 1 + }, + { + "name": "timezone", + "version": 1 + }, + { + "name": "system", + "version": 3 + }, + { + "name": "led", + "version": 1 + }, + { + "name": "playback", + "version": 6 + }, + { + "name": "detection", + "version": 3 + }, + { + "name": "alert", + "version": 2 + }, + { + "name": "firmware", + "version": 2 + }, + { + "name": "account", + "version": 2 + }, + { + "name": "quickSetup", + "version": 1 + }, + { + "name": "ptz", + "version": 1 + }, + { + "name": "video", + "version": 3 + }, + { + "name": "lensMask", + "version": 2 + }, + { + "name": "lightFrequency", + "version": 1 + }, + { + "name": "dayNightMode", + "version": 1 + }, + { + "name": "osd", + "version": 2 + }, + { + "name": "record", + "version": 1 + }, + { + "name": "videoRotation", + "version": 1 + }, + { + "name": "audio", + "version": 3 + }, + { + "name": "diagnose", + "version": 1 + }, + { + "name": "msgPush", + "version": 3 + }, + { + "name": "linecrossingDetection", + "version": 2 + }, + { + "name": "deviceShare", + "version": 1 + }, + { + "name": "tamperDetection", + "version": 1 + }, + { + "name": "tapoCare", + "version": 1 + }, + { + "name": "targetTrack", + "version": 1 + }, + { + "name": "blockZone", + "version": 1 + }, + { + "name": "babyCryDetection", + "version": 1 + }, + { + "name": "personDetection", + "version": 2 + }, + { + "name": "needSubscriptionServiceList", + "version": 1 + }, + { + "name": "patrol", + "version": 1 + }, + { + "name": "vehicleDetection", + "version": 1 + }, + { + "name": "petDetection", + "version": 1 + }, + { + "name": "meowDetection", + "version": 1 + }, + { + "name": "barkDetection", + "version": 1 + }, + { + "name": "glassDetection", + "version": 1 + }, + { + "name": "markerBox", + "version": 1 + }, + { + "name": "nvmp", + "version": 1 + }, + { + "name": "iotCloud", + "version": 1 + }, + { + "name": "panoramicView", + "version": 1 + }, + { + "name": "recordDownload", + "version": 1 + }, + { + "name": "smartTrack", + "version": 1 + }, + { + "name": "detectionRegion", + "version": 2 + }, + { + "name": "staticIp", + "version": 2 + }, + { + "name": "snapshot", + "version": 2 + }, + { + "name": "timeFormat", + "version": 1 + }, + { + "name": "upnpc", + "version": 2 + }, + { + "name": "relayPreConnection", + "version": 1 + } + ] + } + }, + "getAudioConfig": { + "audio_config": { + "microphone": { + "bitrate": "64", + "channels": "1", + "echo_cancelling": "off", + "encode_type": "G711alaw", + "factory_noise_cancelling": "off", + "input_device_type": "MicIn", + "mute": "off", + "noise_cancelling": "on", + "sampling_rate": "8", + "volume": "100" + }, + "speaker": { + "mute": "off", + "output_device_type": "SpeakerOut", + "volume": "100" + } + } + }, + "getBCDConfig": { + "sound_detection": { + "bcd": { + "digital_sensitivity": "50", + "enabled": "off", + "sensitivity": "medium" + } + } + }, + "getBarkDetectionConfig": { + "bark_detection": { + "detection": { + "enabled": "off", + "sensitivity": "50" + } + } + }, + "getCircularRecordingConfig": { + "harddisk_manage": { + "harddisk": { + "loop": "on" + } + } + }, + "getClockStatus": { + "system": { + "clock_status": { + "local_time": "2025-06-16 23:47:00", + "seconds_from_1970": 1750110420 + } + } + }, + "getConnectStatus": { + "onboarding": { + "get_connect_status": { + "status": 0 + } + } + }, + "getConnectionType": { + "link_type": "wifi", + "rssi": "3", + "rssiValue": -54, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + "getDetectionConfig": { + "motion_detection": { + "motion_det": { + "digital_sensitivity": "60", + "enabled": "off", + "non_vehicle_enabled": "off", + "people_enabled": "off", + "sensitivity": "medium", + "vehicle_enabled": "off" + } + } + }, + "getDeviceInfo": { + "device_info": { + "basic_info": { + "avatar": "camera c212", + "barcode": "", + "dev_id": "0000000000000000000000000000000000000000", + "device_alias": "#MASKED_NAME#", + "device_info": "C220 1.0 IPC", + "device_model": "C220", + "device_name": "#MASKED_NAME#", + "device_type": "SMART.IPCAMERA", + "features": 3, + "ffs": false, + "has_set_location_info": 1, + "hw_desc": "00000000000000000000000000000000", + "hw_id": "00000000000000000000000000000000", + "hw_version": "1.0", + "is_cal": true, + "latitude": 0, + "longitude": 0, + "mac": "7C-F1-7E-00-00-00", + "manufacturer_name": "TP-LINK", + "mobile_access": "0", + "no_rtsp_constrain": 1, + "oem_id": "00000000000000000000000000000000", + "region": "EU", + "sw_version": "1.2.5 Build 241224 Rel.40956n", + "tss": false + } + } + }, + "getFirmwareAutoUpgradeConfig": { + "auto_upgrade": { + "common": { + "enabled": "on", + "random_range": "120", + "time": "03:00" + } + } + }, + "getFirmwareUpdateStatus": { + "cloud_config": { + "upgrade_status": { + "lastUpgradingSuccess": true, + "state": "normal" + } + } + }, + "getGlassDetectionConfig": { + "glass_detection": { + "detection": { + "enabled": "on", + "sensitivity": "50" + } + } + }, + "getLastAlarmInfo": { + "system": { + "last_alarm_info": { + "last_alarm_time": "1750109746", + "last_alarm_type": "motion" + } + } + }, + "getLdc": { + "image": { + "common": { + "area_compensation": "default", + "auto_exp_antiflicker": "off", + "auto_exp_gain_max": "0", + "backlight": "off", + "chroma": "50", + "contrast": "50", + "dehaze": "off", + "eis": "off", + "exp_gain": "100", + "exp_level": "0", + "exp_type": "auto", + "focus_limited": "10", + "focus_type": "manual", + "high_light_compensation": "off", + "inf_delay": "5", + "inf_end_time": "21600", + "inf_sensitivity": "4", + "inf_sensitivity_day2night": "1400", + "inf_sensitivity_night2day": "9100", + "inf_start_time": "64800", + "inf_type": "auto", + "iris_level": "160", + "light_freq_mode": "50", + "lock_blue_colton": "0", + "lock_blue_gain": "0", + "lock_gb_gain": "0", + "lock_gr_gain": "0", + "lock_green_colton": "0", + "lock_red_colton": "0", + "lock_red_gain": "0", + "lock_source": "local", + "luma": "50", + "saturation": "50", + "sharpness": "50", + "shutter": "1/25", + "smartir": "auto_ir", + "smartir_level": "0", + "smartwtl": "auto_wtl", + "smartwtl_digital_level": "50", + "smartwtl_level": "3", + "style": "standard", + "wb_B_gain": "50", + "wb_G_gain": "50", + "wb_R_gain": "50", + "wb_type": "auto", + "wd_gain": "50", + "wide_dynamic": "off", + "wtl_delay": "5", + "wtl_end_time": "21600", + "wtl_sensitivity": "4", + "wtl_sensitivity_day2night": "1400", + "wtl_sensitivity_night2day": "9100", + "wtl_start_time": "64800", + "wtl_type": "auto" + }, + "switch": { + "best_view_distance": "0", + "clear_licence_plate_mode": "off", + "flip_type": "off", + "full_color_min_keep_time": "30", + "full_color_people_enhance": "off", + "image_scene_mode": "normal", + "image_scene_mode_autoday": "normal", + "image_scene_mode_autonight": "normal", + "image_scene_mode_common": "normal", + "image_scene_mode_shedday": "normal", + "image_scene_mode_shednight": "normal", + "ldc": "off", + "night_vision_mode": "inf_night_vision", + "overexposure_people_suppression": "off", + "rotate_type": "off", + "schedule_end_time": "64800", + "schedule_start_time": "21600", + "switch_mode": "common", + "wtl_force_time": "300", + "wtl_intensity_level": "1" + } + } + }, + "getLedStatus": { + "led": { + "config": { + "enabled": "off" + } + } + }, + "getLensMaskConfig": { + "lens_mask": { + "lens_mask_info": { + "enabled": "off" + } + } + }, + "getLightFrequencyInfo": { + "image": { + "common": { + "area_compensation": "default", + "auto_exp_antiflicker": "off", + "auto_exp_gain_max": "0", + "backlight": "off", + "chroma": "50", + "contrast": "50", + "dehaze": "off", + "eis": "off", + "exp_gain": "100", + "exp_level": "0", + "exp_type": "auto", + "focus_limited": "10", + "focus_type": "manual", + "high_light_compensation": "off", + "inf_delay": "5", + "inf_end_time": "21600", + "inf_sensitivity": "4", + "inf_sensitivity_day2night": "1400", + "inf_sensitivity_night2day": "9100", + "inf_start_time": "64800", + "inf_type": "auto", + "iris_level": "160", + "light_freq_mode": "50", + "lock_blue_colton": "0", + "lock_blue_gain": "0", + "lock_gb_gain": "0", + "lock_gr_gain": "0", + "lock_green_colton": "0", + "lock_red_colton": "0", + "lock_red_gain": "0", + "lock_source": "local", + "luma": "50", + "saturation": "50", + "sharpness": "50", + "shutter": "1/25", + "smartir": "auto_ir", + "smartir_level": "0", + "smartwtl": "auto_wtl", + "smartwtl_digital_level": "50", + "smartwtl_level": "3", + "style": "standard", + "wb_B_gain": "50", + "wb_G_gain": "50", + "wb_R_gain": "50", + "wb_type": "auto", + "wd_gain": "50", + "wide_dynamic": "off", + "wtl_delay": "5", + "wtl_end_time": "21600", + "wtl_sensitivity": "4", + "wtl_sensitivity_day2night": "1400", + "wtl_sensitivity_night2day": "9100", + "wtl_start_time": "64800", + "wtl_type": "auto" + } + } + }, + "getLinecrossingDetectionConfig": { + "linecrossing_detection": { + "arming_schedule": { + "friday": "[\"0000-2400\"]", + "monday": "[\"0000-2400\"]", + "saturday": "[\"0000-2400\"]", + "sunday": "[\"0000-2400\"]", + "thursday": "[\"0000-2400\"]", + "tuesday": "[\"0000-2400\"]", + "wednesday": "[\"0000-2400\"]" + }, + "detection": { + "enabled": "on" + } + } + }, + "getMediaEncrypt": { + "cet": { + "media_encrypt": { + "enabled": "on" + } + } + }, + "getMeowDetectionConfig": { + "meow_detection": { + "detection": { + "enabled": "off", + "sensitivity": "50" + } + } + }, + "getMsgPushConfig": { + "msg_push": { + "chn1_msg_push_info": { + "notification_enabled": "on", + "rich_notification_enabled": "off" + } + } + }, + "getNightVisionCapability": { + "image_capability": { + "supplement_lamp": { + "night_vision_mode_range": [ + "inf_night_vision" + ], + "supplement_lamp_type": [ + "infrared_lamp" + ] + } + } + }, + "getNightVisionModeConfig": { + "image": { + "switch": { + "best_view_distance": "0", + "clear_licence_plate_mode": "off", + "flip_type": "off", + "full_color_min_keep_time": "30", + "full_color_people_enhance": "off", + "image_scene_mode": "normal", + "image_scene_mode_autoday": "normal", + "image_scene_mode_autonight": "normal", + "image_scene_mode_common": "normal", + "image_scene_mode_shedday": "normal", + "image_scene_mode_shednight": "normal", + "ldc": "off", + "night_vision_mode": "inf_night_vision", + "overexposure_people_suppression": "off", + "rotate_type": "off", + "schedule_end_time": "64800", + "schedule_start_time": "21600", + "switch_mode": "common", + "wtl_force_time": "300", + "wtl_intensity_level": "1" + } + } + }, + "getPersonDetectionConfig": { + "people_detection": { + "detection": { + "enabled": "off", + "sensitivity": "60" + } + } + }, + "getPetDetectionConfig": { + "pet_detection": { + "detection": { + "enabled": "off", + "sensitivity": "60" + } + } + }, + "getPresetConfig": { + "preset": { + "preset": { + "id": [], + "name": [], + "position_pan": [], + "position_tilt": [], + "position_zoom": [], + "read_only": [] + } + } + }, + "getRecordPlan": { + "record_plan": { + "chn1_channel": { + "enabled": "on", + "friday": "[\"0000-2400:2\"]", + "monday": "[\"0000-2400:2\"]", + "saturday": "[\"0000-2400:2\"]", + "sunday": "[\"0000-2400:2\"]", + "thursday": "[\"0000-2400:2\"]", + "tuesday": "[\"0000-2400:2\"]", + "wednesday": "[\"0000-2400:2\"]" + } + } + }, + "getRotationStatus": { + "image": { + "switch": { + "best_view_distance": "0", + "clear_licence_plate_mode": "off", + "flip_type": "off", + "full_color_min_keep_time": "30", + "full_color_people_enhance": "off", + "image_scene_mode": "normal", + "image_scene_mode_autoday": "normal", + "image_scene_mode_autonight": "normal", + "image_scene_mode_common": "normal", + "image_scene_mode_shedday": "normal", + "image_scene_mode_shednight": "normal", + "ldc": "off", + "night_vision_mode": "inf_night_vision", + "overexposure_people_suppression": "off", + "rotate_type": "off", + "schedule_end_time": "64800", + "schedule_start_time": "21600", + "switch_mode": "common", + "wtl_force_time": "300", + "wtl_intensity_level": "1" + } + } + }, + "getSdCardStatus": { + "harddisk_manage": { + "hd_info": [ + { + "hd_info_1": { + "crossline_free_space": "0B", + "crossline_free_space_accurate": "0B", + "crossline_total_space": "0B", + "crossline_total_space_accurate": "0B", + "detect_status": "offline", + "disk_name": "1", + "free_space": "0B", + "free_space_accurate": "0B", + "loop_record_status": "0", + "msg_push_free_space": "0B", + "msg_push_free_space_accurate": "0B", + "msg_push_total_space": "0B", + "msg_push_total_space_accurate": "0B", + "percent": "0", + "picture_free_space": "0B", + "picture_free_space_accurate": "0B", + "picture_total_space": "0B", + "picture_total_space_accurate": "0B", + "record_duration": "0", + "record_free_duration": "0", + "record_start_time": "0", + "rw_attr": "r", + "status": "offline", + "total_space": "0B", + "total_space_accurate": "0B", + "type": "local", + "video_free_space": "0B", + "video_free_space_accurate": "0B", + "video_total_space": "0B", + "video_total_space_accurate": "0B", + "write_protect": "0" + } + } + ] + } + }, + "getTamperDetectionConfig": { + "tamper_detection": { + "tamper_det": { + "digital_sensitivity": "50", + "enabled": "on", + "sensitivity": "medium" + } + } + }, + "getTargetTrackConfig": { + "target_track": { + "target_track_info": { + "back_time": "30", + "enabled": "off", + "track_mode": "pantilt", + "track_time": "0" + } + } + }, + "getTimezone": { + "system": { + "basic": { + "timezone": "UTC+01:00", + "timing_mode": "ntp", + "zone_id": "Europe/Amsterdam" + } + } + }, + "getVehicleDetectionConfig": { + "vehicle_detection": { + "detection": { + "enabled": "off", + "sensitivity": "60" + } + } + }, + "getVideoCapability": { + "video_capability": { + "main": { + "bitrate_types": [ + "cbr", + "vbr" + ], + "bitrates": [ + "256", + "512", + "1024", + "1536", + "2048", + "2560" + ], + "change_fps_support": "1", + "encode_types": [ + "H264", + "H265" + ], + "frame_rates": [ + "65551", + "65556", + "65561" + ], + "minor_stream_support": "1", + "qualitys": [ + "1", + "3", + "5" + ], + "resolutions": [ + "2560*1440", + "1920*1080" + ] + } + } + }, + "getVideoQualities": { + "video": { + "main": { + "bitrate": "2560", + "bitrate_type": "vbr", + "default_bitrate": "2560", + "encode_type": "H264", + "frame_rate": "65561", + "name": "VideoEncoder_1", + "quality": "3", + "resolution": "2560*1440", + "smart_codec": "off" + } + } + }, + "getWhitelampConfig": { + "image": { + "switch": { + "best_view_distance": "0", + "clear_licence_plate_mode": "off", + "flip_type": "off", + "full_color_min_keep_time": "30", + "full_color_people_enhance": "off", + "image_scene_mode": "normal", + "image_scene_mode_autoday": "normal", + "image_scene_mode_autonight": "normal", + "image_scene_mode_common": "normal", + "image_scene_mode_shedday": "normal", + "image_scene_mode_shednight": "normal", + "ldc": "off", + "night_vision_mode": "inf_night_vision", + "overexposure_people_suppression": "off", + "rotate_type": "off", + "schedule_end_time": "64800", + "schedule_start_time": "21600", + "switch_mode": "common", + "wtl_force_time": "300", + "wtl_intensity_level": "1" + } + } + }, + "getWhitelampStatus": { + "rest_time": 0, + "status": 0 + }, + "get_audio_capability": { + "get": { + "audio_capability": { + "device_microphone": { + "aec": "1", + "channels": "1", + "echo_cancelling": "0", + "encode_type": [ + "G711alaw" + ], + "half_duplex": "1", + "mute": "1", + "noise_cancelling": "1", + "sampling_rate": [ + "8" + ], + "volume": "1" + }, + "device_speaker": { + "channels": "1", + "decode_type": [ + "G711alaw" + ], + "mute": "0", + "output_device_type": "0", + "sampling_rate": [ + "8" + ], + "system_volume": "80", + "volume": "1" + } + } + } + }, + "get_audio_config": { + "get": { + "audio_config": { + "microphone": { + "bitrate": "64", + "channels": "1", + "echo_cancelling": "off", + "encode_type": "G711alaw", + "factory_noise_cancelling": "off", + "input_device_type": "MicIn", + "mute": "off", + "noise_cancelling": "on", + "sampling_rate": "8", + "volume": "100" + }, + "speaker": { + "mute": "off", + "output_device_type": "SpeakerOut", + "volume": "100" + } + } + } + }, + "get_cet": { + "get": { + "cet": { + "vhttpd": { + "port": "8800" + } + } + } + }, + "get_function": { + "get": { + "function": { + "module_spec": { + "ae_weighting_table_resolution": "5*5", + "ai_enhance_capability": "1", + "ai_enhance_range": [ + "traditional_enhance" + ], + "ai_firmware_upgrade": "0", + "alarm_out_num": "0", + "app_version": "1.0.0", + "audio": [ + "speaker", + "microphone" + ], + "auth_encrypt": "1", + "auto_ip_configurable": "1", + "backlight_coexistence": "1", + "change_password": "1", + "client_info": "1", + "cloud_storage_version": "1.0", + "config_recovery": [ + "audio_config", + "OSD", + "image", + "video" + ], + "custom_area_compensation": "1", + "custom_auto_mode_exposure_level": "1", + "daynight_subdivision": "1", + "device_share": [ + "preview", + "playback", + "voice", + "cloud_storage", + "motor" + ], + "download": [ + "video" + ], + "events": [ + "motion", + "tamper" + ], + "force_iframe_support": "1", + "http_system_state_audio_support": "1", + "image_capability": "1", + "image_list": [ + "supplement_lamp", + "expose" + ], + "ir_led_pwm_control": "1", + "led": "1", + "lens_mask": "1", + "linkage_capability": "1", + "local_storage": "1", + "media_encrypt": "1", + "motor": "0", + "msg_alarm_list": [ + "sound", + "light" + ], + "msg_push": "1", + "multi_user": "0", + "multicast": "0", + "network": [ + "wifi", + "ethernet" + ], + "osd_capability": "1", + "ota_upgrade": "1", + "p2p_support_versions": [ + "2.0" + ], + "personalized_audio_alarm": "0", + "playback": [ + "local", + "p2p", + "relay" + ], + "playback_scale": "1", + "preview": [ + "local", + "p2p", + "relay" + ], + "ptz": "1", + "record_max_slot_cnt": "6", + "record_type": [ + "timing", + "motion" + ], + "relay_support_versions": [ + "2.0" + ], + "remote_upgrade": "1", + "reonboarding": "0", + "smart_codec": "0", + "smart_detection": "1", + "ssl_cer_version": "1.0", + "storage_api_version": "2.2", + "storage_capability": "1", + "stream_max_sessions": "10", + "streaming_support_versions": [ + "2.0" + ], + "tapo_care_version": "1.0.0", + "target_track": "1", + "timing_reboot": "1", + "verification_change_password": "1", + "video_codec": [ + "h264", + "h265" + ], + "video_detection_digital_sensitivity": "1", + "wide_range_inf_sensitivity": "1", + "wifi_connection_info": "1", + "wireless_hotspot": "0" + } + } + } + }, + "scanApList": { + "onboarding": { + "scan": { + "ap_list": [ + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 2, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 1, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 4, + "bssid": "000000000000", + "encryption": 3, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + }, + { + "auth": 3, + "bssid": "000000000000", + "encryption": 2, + "rssi": 0, + "ssid": "I01BU0tFRF9TU0lEIw==" + } + ], + "wpa3_supported": "true" + } + } + } +} diff --git a/tests/smartcam/modules/test_babycrydetection.py b/tests/smartcam/modules/test_babycrydetection.py deleted file mode 100644 index 89ff5ac4..00000000 --- a/tests/smartcam/modules/test_babycrydetection.py +++ /dev/null @@ -1,45 +0,0 @@ -"""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") - 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_detections.py b/tests/smartcam/modules/test_detections.py new file mode 100644 index 00000000..c4659f7b --- /dev/null +++ b/tests/smartcam/modules/test_detections.py @@ -0,0 +1,168 @@ +"""Tests for smartcam detections.""" + +from __future__ import annotations + +from typing import NamedTuple + +import pytest + +from kasa import Device +from kasa.modulemapping import ModuleName +from kasa.smartcam import DetectionModule +from kasa.smartcam.smartcammodule import SmartCamModule + +from ...fixtureinfo import filter_fixtures, idgenerator + + +class Detection(NamedTuple): + desc: str + module: ModuleName[DetectionModule] + feature_name: str + component_filter: str + model_filter: str | None = None + + +def parametrize_detection( + *, + model_filter=None, + protocol_filter=None, + fixture_name="dev", + extra_params_names: list[str], + extra_params_values: list[Detection], +): + _pytest_parameters = [] + + _arg_names = fixture_name + if extra_params_names: + _arg_names = f"{fixture_name},{','.join(extra_params_names)}" + + _model_filter = model_filter + + for _detection in extra_params_values: + if _detection.model_filter: + _model_filter = _detection.model_filter + + extra_values = list(map(lambda x: _detection._asdict()[x], extra_params_names)) + _pytest_parameters.extend( + [ + (i, *extra_values) + for i in filter_fixtures( + _detection.desc, + model_filter=_model_filter, + protocol_filter=protocol_filter, + component_filter=_detection.component_filter, + data_root_filter=None, + device_type_filter=None, + ) + ] + ) + + return pytest.mark.parametrize( + _arg_names, + _pytest_parameters, + indirect=[fixture_name], + ids=idgenerator, + ) + + +detections = [ + Detection( + desc="has baby cry detection", + module=SmartCamModule.SmartCamBabyCryDetection, + feature_name="baby_cry_detection", + component_filter="babyCryDetection", + ), + Detection( + desc="has bark detection", + module=SmartCamModule.SmartCamBarkDetection, + feature_name="bark_detection", + component_filter="barkDetection", + ), + Detection( + desc="has glass detection", + module=SmartCamModule.SmartCamGlassDetection, + feature_name="glass_detection", + component_filter="glassDetection", + ), + Detection( + desc="has line crossing detection", + module=SmartCamModule.SmartCamLineCrossingDetection, + feature_name="line_crossing_detection", + component_filter="linecrossingDetection", + model_filter="C220(EU)_1.0_1.2.5", + ), + Detection( + desc="has meow detection", + module=SmartCamModule.SmartCamMeowDetection, + feature_name="meow_detection", + component_filter="meowDetection", + ), + Detection( + desc="has motion detection", + module=SmartCamModule.SmartCamMotionDetection, + feature_name="motion_detection", + component_filter="detection", + ), + Detection( + desc="has person detection", + module=SmartCamModule.SmartCamPersonDetection, + feature_name="person_detection", + component_filter="personDetection", + ), + Detection( + desc="has pet detection", + module=SmartCamModule.SmartCamPetDetection, + feature_name="pet_detection", + component_filter="petDetection", + ), + Detection( + desc="has tamper detection", + module=SmartCamModule.SmartCamTamperDetection, + feature_name="tamper_detection", + component_filter="tamperDetection", + ), + Detection( + desc="has vehicle detection", + module=SmartCamModule.SmartCamVehicleDetection, + feature_name="vehicle_detection", + component_filter="vehicleDetection", + ), +] + +params_detections = parametrize_detection( + protocol_filter={"SMARTCAM"}, + extra_params_names=["module", "feature_name"], + extra_params_values=detections, +) + + +@params_detections +async def test_detections( + dev: Device, module: ModuleName[DetectionModule], feature_name: str +): + detection = dev.modules.get(module) + assert detection + + detection_feat = dev.features.get(feature_name) + assert detection_feat + + original_enabled = detection.enabled + + try: + await detection.set_enabled(not original_enabled) + await dev.update() + assert detection.enabled is not original_enabled + assert detection_feat.value is not original_enabled + + await detection.set_enabled(original_enabled) + await dev.update() + assert detection.enabled is original_enabled + assert detection_feat.value is original_enabled + + await detection_feat.set_value(not original_enabled) + await dev.update() + assert detection.enabled is not original_enabled + assert detection_feat.value is not original_enabled + + finally: + await detection.set_enabled(original_enabled) diff --git a/tests/smartcam/modules/test_motiondetection.py b/tests/smartcam/modules/test_motiondetection.py deleted file mode 100644 index c4ff9807..00000000 --- a/tests/smartcam/modules/test_motiondetection.py +++ /dev/null @@ -1,43 +0,0 @@ -"""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") - 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 deleted file mode 100644 index 34137587..00000000 --- a/tests/smartcam/modules/test_persondetection.py +++ /dev/null @@ -1,45 +0,0 @@ -"""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") - 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_petdetection.py b/tests/smartcam/modules/test_petdetection.py deleted file mode 100644 index 6eff0c8a..00000000 --- a/tests/smartcam/modules/test_petdetection.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Tests for smartcam pet detection module.""" - -from __future__ import annotations - -from kasa import Device -from kasa.smartcam.smartcammodule import SmartCamModule - -from ...device_fixtures import parametrize - -petdetection = parametrize( - "has pet detection", - component_filter="petDetection", - protocol_filter={"SMARTCAM"}, -) - - -@petdetection -async def test_petdetection(dev: Device): - """Test device pet detection.""" - pet = dev.modules.get(SmartCamModule.SmartCamPetDetection) - assert pet - - pde_feat = dev.features.get("pet_detection") - assert pde_feat - - original_enabled = pet.enabled - - try: - await pet.set_enabled(not original_enabled) - await dev.update() - assert pet.enabled is not original_enabled - assert pde_feat.value is not original_enabled - - await pet.set_enabled(original_enabled) - await dev.update() - assert pet.enabled is original_enabled - assert pde_feat.value is original_enabled - - await pde_feat.set_value(not original_enabled) - await dev.update() - assert pet.enabled is not original_enabled - assert pde_feat.value is not original_enabled - - finally: - await pet.set_enabled(original_enabled) diff --git a/tests/smartcam/modules/test_tamperdetection.py b/tests/smartcam/modules/test_tamperdetection.py deleted file mode 100644 index ab2f851d..00000000 --- a/tests/smartcam/modules/test_tamperdetection.py +++ /dev/null @@ -1,45 +0,0 @@ -"""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") - 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)