Add LensMask module to smartcam (#1385)
Some checks are pending
CI / Perform linting checks (3.13) (push) Waiting to run
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.13) (push) Blocked by required conditions
CodeQL checks / Analyze (python) (push) Waiting to run

Ensures no error with devices that do not have the `lens_mask` component.
This commit is contained in:
Steven B. 2024-12-17 20:15:42 +00:00 committed by GitHub
parent 37ef7b0463
commit ba273f308e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 78 additions and 28 deletions

View File

@ -159,6 +159,7 @@ class Module(ABC):
# SMARTCAM only modules # SMARTCAM only modules
Camera: Final[ModuleName[smartcam.Camera]] = ModuleName("Camera") Camera: Final[ModuleName[smartcam.Camera]] = ModuleName("Camera")
LensMask: Final[ModuleName[smartcam.LensMask]] = ModuleName("LensMask")
def __init__(self, device: Device, module: str) -> None: def __init__(self, device: Device, module: str) -> None:
self._device = device self._device = device

View File

@ -6,6 +6,7 @@ from .childdevice import ChildDevice
from .device import DeviceModule from .device import DeviceModule
from .homekit import HomeKit from .homekit import HomeKit
from .led import Led from .led import Led
from .lensmask import LensMask
from .matter import Matter from .matter import Matter
from .pantilt import PanTilt from .pantilt import PanTilt
from .time import Time from .time import Time
@ -20,4 +21,5 @@ __all__ = [
"Time", "Time",
"HomeKit", "HomeKit",
"Matter", "Matter",
"LensMask",
] ]

View File

@ -1,16 +1,18 @@
"""Implementation of device module.""" """Implementation of camera module."""
from __future__ import annotations from __future__ import annotations
import base64 import base64
import logging import logging
from enum import StrEnum from enum import StrEnum
from typing import Annotated
from urllib.parse import quote_plus from urllib.parse import quote_plus
from ...credentials import Credentials from ...credentials import Credentials
from ...device_type import DeviceType from ...device_type import DeviceType
from ...feature import Feature from ...feature import Feature
from ...json import loads as json_loads from ...json import loads as json_loads
from ...module import FeatureAttribute, Module
from ..smartcammodule import SmartCamModule from ..smartcammodule import SmartCamModule
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -29,12 +31,9 @@ class StreamResolution(StrEnum):
class Camera(SmartCamModule): class Camera(SmartCamModule):
"""Implementation of device module.""" """Implementation of device module."""
QUERY_GETTER_NAME = "getLensMaskConfig"
QUERY_MODULE_NAME = "lens_mask"
QUERY_SECTION_NAMES = "lens_mask_info"
def _initialize_features(self) -> None: def _initialize_features(self) -> None:
"""Initialize features after the initial update.""" """Initialize features after the initial update."""
if Module.LensMask in self._device.modules:
self._add_feature( self._add_feature(
Feature( Feature(
self._device, self._device,
@ -49,8 +48,20 @@ class Camera(SmartCamModule):
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return the device id.""" """Return the device on state."""
return self.data["lens_mask_info"]["enabled"] == "off" if lens_mask := self._device.modules.get(Module.LensMask):
return lens_mask.state
return True
async def set_state(self, on: bool) -> Annotated[dict, FeatureAttribute()]:
"""Set the device on state.
If the device does not support setting state will do nothing.
"""
if lens_mask := self._device.modules.get(Module.LensMask):
# Turning off enables the privacy mask which is why value is reversed.
return await lens_mask.set_state(not on)
return {}
def _get_credentials(self) -> Credentials | None: def _get_credentials(self) -> Credentials | None:
"""Get credentials from .""" """Get credentials from ."""
@ -109,14 +120,6 @@ class Camera(SmartCamModule):
"""Return the onvif url.""" """Return the onvif url."""
return f"http://{self._device.host}:{ONVIF_PORT}/onvif/device_service" return f"http://{self._device.host}:{ONVIF_PORT}/onvif/device_service"
async def set_state(self, on: bool) -> dict:
"""Set the device state."""
# Turning off enables the privacy mask which is why value is reversed.
params = {"enabled": "off" if on else "on"}
return await self._device._query_setter_helper(
"setLensMaskConfig", self.QUERY_MODULE_NAME, "lens_mask_info", params
)
async def _check_supported(self) -> bool: async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device.""" """Additional check to see if the module is supported by the device."""
return self._device.device_type is DeviceType.Camera return self._device.device_type is DeviceType.Camera

View File

@ -0,0 +1,29 @@
"""Implementation of lens mask privacy module."""
from __future__ import annotations
import logging
from ..smartcammodule import SmartCamModule
_LOGGER = logging.getLogger(__name__)
class LensMask(SmartCamModule):
"""Implementation of lens mask module."""
QUERY_GETTER_NAME = "getLensMaskConfig"
QUERY_MODULE_NAME = "lens_mask"
QUERY_SECTION_NAMES = "lens_mask_info"
@property
def state(self) -> bool:
"""Return the lens mask state."""
return self.data["lens_mask_info"]["enabled"] == "off"
async def set_state(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, "lens_mask_info", params
)

View File

@ -134,6 +134,11 @@ class SmartCamDevice(SmartDevice):
if ( if (
mod.REQUIRED_COMPONENT mod.REQUIRED_COMPONENT
and mod.REQUIRED_COMPONENT not in self._components and mod.REQUIRED_COMPONENT not in self._components
# Always add Camera module to cameras
and (
mod._module_name() != Module.Camera
or self._device_type is not DeviceType.Camera
)
): ):
continue continue
module = mod(self, mod._module_name()) module = mod(self, mod._module_name())

View File

@ -17,11 +17,21 @@ async def test_state(dev: Device):
if dev.device_type is DeviceType.Hub: if dev.device_type is DeviceType.Hub:
pytest.skip("Hubs cannot be switched on and off") pytest.skip("Hubs cannot be switched on and off")
if Module.LensMask in dev.modules:
state = dev.is_on state = dev.is_on
await dev.set_state(not state) await dev.set_state(not state)
await dev.update() await dev.update()
assert dev.is_on is not state assert dev.is_on is not state
dev.modules.pop(Module.LensMask) # type: ignore[attr-defined]
# Test with no lens mask module. Device is always on.
assert dev.is_on is True
res = await dev.set_state(False)
assert res == {}
await dev.update()
assert dev.is_on is True
@device_smartcam @device_smartcam
async def test_alias(dev): async def test_alias(dev):