Skip to content

Add smartcam detection modules #1389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions kasa/smartcam/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
"""Modules for SMARTCAM devices."""

from .alarm import Alarm
from .babycrydetection import BabyCryDetection
from .camera import Camera
from .childdevice import ChildDevice
from .device import DeviceModule
from .homekit import HomeKit
from .led import Led
from .lensmask import LensMask
from .matter import Matter
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",
"MotionDetection",
"LensMask",
"TamperDetection",
]
47 changes: 47 additions & 0 deletions kasa/smartcam/modules/babycrydetection.py
Original file line number Diff line number Diff line change
@@ -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",
name="Baby cry detection",
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
)
47 changes: 47 additions & 0 deletions kasa/smartcam/modules/motiondetection.py
Original file line number Diff line number Diff line change
@@ -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 MotionDetection(SmartCamModule):
"""Implementation of motion detection 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",
name="Motion detection",
container=self,
attribute_getter="enabled",
attribute_setter="set_enabled",
type=Feature.Type.Switch,
category=Feature.Category.Primary,
)
)

@property
def enabled(self) -> bool:
"""Return the motion detection enabled state."""
return self.data["motion_det"]["enabled"] == "on"

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
)
47 changes: 47 additions & 0 deletions kasa/smartcam/modules/persondetection.py
Original file line number Diff line number Diff line change
@@ -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",
name="Person detection",
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
)
47 changes: 47 additions & 0 deletions kasa/smartcam/modules/tamperdetection.py
Original file line number Diff line number Diff line change
@@ -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",
name="Tamper detection",
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
)
12 changes: 12 additions & 0 deletions kasa/smartcam/smartcammodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions tests/smartcam/modules/test_babycrydetection.py
Original file line number Diff line number Diff line change
@@ -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")
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)
43 changes: 43 additions & 0 deletions tests/smartcam/modules/test_motiondetection.py
Original file line number Diff line number Diff line change
@@ -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")
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)
45 changes: 45 additions & 0 deletions tests/smartcam/modules/test_persondetection.py
Original file line number Diff line number Diff line change
@@ -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")
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)
Loading
Loading