Cleans up various asserts/type ignores for UniFi Protect (#63824)

Co-authored-by: epenet <epenet@users.noreply.github.com>
This commit is contained in:
Christopher Bailey 2022-01-17 15:51:55 -05:00 committed by GitHub
parent b9cfaae3de
commit 259befa65f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 74 additions and 86 deletions

View File

@ -4,9 +4,9 @@ from __future__ import annotations
from collections.abc import Callable, Coroutine from collections.abc import Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import Any from typing import Any, Generic, TypeVar
from pyunifiprotect.data import NVR, ProtectAdoptableDeviceModel from pyunifiprotect.data import ProtectDeviceModel
from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.entity import EntityDescription
@ -14,17 +14,19 @@ from .utils import get_nested_attr
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
T = TypeVar("T", bound=ProtectDeviceModel)
@dataclass @dataclass
class ProtectRequiredKeysMixin: class ProtectRequiredKeysMixin(Generic[T]):
"""Mixin for required keys.""" """Mixin for required keys."""
ufp_required_field: str | None = None ufp_required_field: str | None = None
ufp_value: str | None = None ufp_value: str | None = None
ufp_value_fn: Callable[[ProtectAdoptableDeviceModel | NVR], Any] | None = None ufp_value_fn: Callable[[T], Any] | None = None
ufp_enabled: str | None = None ufp_enabled: str | None = None
def get_ufp_value(self, obj: ProtectAdoptableDeviceModel | NVR) -> Any: def get_ufp_value(self, obj: T) -> Any:
"""Return value from UniFi Protect device.""" """Return value from UniFi Protect device."""
if self.ufp_value is not None: if self.ufp_value is not None:
return get_nested_attr(obj, self.ufp_value) return get_nested_attr(obj, self.ufp_value)
@ -36,7 +38,7 @@ class ProtectRequiredKeysMixin:
"`ufp_value` or `ufp_value_fn` is required" "`ufp_value` or `ufp_value_fn` is required"
) )
def get_ufp_enabled(self, obj: ProtectAdoptableDeviceModel | NVR) -> bool: def get_ufp_enabled(self, obj: T) -> bool:
"""Return value from UniFi Protect device.""" """Return value from UniFi Protect device."""
if self.ufp_enabled is not None: if self.ufp_enabled is not None:
return bool(get_nested_attr(obj, self.ufp_enabled)) return bool(get_nested_attr(obj, self.ufp_enabled))
@ -44,15 +46,13 @@ class ProtectRequiredKeysMixin:
@dataclass @dataclass
class ProtectSetableKeysMixin(ProtectRequiredKeysMixin): class ProtectSetableKeysMixin(ProtectRequiredKeysMixin, Generic[T]):
"""Mixin to for settable values.""" """Mixin for settable values."""
ufp_set_method: str | None = None ufp_set_method: str | None = None
ufp_set_method_fn: Callable[ ufp_set_method_fn: Callable[[T, Any], Coroutine[Any, Any, None]] | None = None
[ProtectAdoptableDeviceModel, Any], Coroutine[Any, Any, None]
] | None = None
async def ufp_set(self, obj: ProtectAdoptableDeviceModel, value: Any) -> None: async def ufp_set(self, obj: T, value: Any) -> None:
"""Set value for UniFi Protect device.""" """Set value for UniFi Protect device."""
assert isinstance(self, EntityDescription) assert isinstance(self, EntityDescription)
_LOGGER.debug("Setting %s to %s for %s", self.name, value, obj.name) _LOGGER.debug("Setting %s to %s for %s", self.name, value, obj.name)

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from typing import Any from typing import Generic
from pyunifiprotect.data.devices import Camera, Light from pyunifiprotect.data.devices import Camera, Light
@ -16,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .data import ProtectData from .data import ProtectData
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import ProtectSetableKeysMixin from .models import ProtectSetableKeysMixin, T
@dataclass @dataclass
@ -30,18 +30,16 @@ class NumberKeysMixin:
@dataclass @dataclass
class ProtectNumberEntityDescription( class ProtectNumberEntityDescription(
ProtectSetableKeysMixin, NumberEntityDescription, NumberKeysMixin ProtectSetableKeysMixin, NumberEntityDescription, NumberKeysMixin, Generic[T]
): ):
"""Describes UniFi Protect Number entity.""" """Describes UniFi Protect Number entity."""
def _get_pir_duration(obj: Any) -> int: def _get_pir_duration(obj: Light) -> int:
assert isinstance(obj, Light)
return int(obj.light_device_settings.pir_duration.total_seconds()) return int(obj.light_device_settings.pir_duration.total_seconds())
async def _set_pir_duration(obj: Any, value: float) -> None: async def _set_pir_duration(obj: Light, value: float) -> None:
assert isinstance(obj, Light)
await obj.set_duration(timedelta(seconds=value)) await obj.set_duration(timedelta(seconds=value))
@ -97,7 +95,7 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ufp_value="light_device_settings.pir_sensitivity", ufp_value="light_device_settings.pir_sensitivity",
ufp_set_method="set_sensitivity", ufp_set_method="set_sensitivity",
), ),
ProtectNumberEntityDescription( ProtectNumberEntityDescription[Light](
key="duration", key="duration",
name="Auto-shutoff Duration", name="Auto-shutoff Duration",
icon="mdi:camera-timer", icon="mdi:camera-timer",

View File

@ -6,7 +6,7 @@ from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from enum import Enum from enum import Enum
import logging import logging
from typing import Any, Final from typing import Any, Final, Generic
from pyunifiprotect.api import ProtectApiClient from pyunifiprotect.api import ProtectApiClient
from pyunifiprotect.data import ( from pyunifiprotect.data import (
@ -19,9 +19,7 @@ from pyunifiprotect.data import (
RecordingMode, RecordingMode,
Viewer, Viewer,
) )
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
from pyunifiprotect.data.devices import Sensor from pyunifiprotect.data.devices import Sensor
from pyunifiprotect.data.nvr import NVR
from pyunifiprotect.data.types import ChimeType, MountType from pyunifiprotect.data.types import ChimeType, MountType
import voluptuous as vol import voluptuous as vol
@ -37,7 +35,7 @@ from homeassistant.util.dt import utcnow
from .const import ATTR_DURATION, ATTR_MESSAGE, DOMAIN, TYPE_EMPTY_VALUE from .const import ATTR_DURATION, ATTR_MESSAGE, DOMAIN, TYPE_EMPTY_VALUE
from .data import ProtectData from .data import ProtectData
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import ProtectSetableKeysMixin from .models import ProtectSetableKeysMixin, T
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_KEY_LIGHT_MOTION = "light_motion" _KEY_LIGHT_MOTION = "light_motion"
@ -104,15 +102,14 @@ SET_DOORBELL_LCD_MESSAGE_SCHEMA = vol.Schema(
@dataclass @dataclass
class ProtectSelectEntityDescription(ProtectSetableKeysMixin, SelectEntityDescription): class ProtectSelectEntityDescription(
ProtectSetableKeysMixin, SelectEntityDescription, Generic[T]
):
"""Describes UniFi Protect Select entity.""" """Describes UniFi Protect Select entity."""
ufp_options: list[dict[str, Any]] | None = None ufp_options: list[dict[str, Any]] | None = None
ufp_options_callable: Callable[ ufp_options_fn: Callable[[ProtectApiClient], list[dict[str, Any]]] | None = None
[ProtectApiClient], list[dict[str, Any]]
] | None = None
ufp_enum_type: type[Enum] | None = None ufp_enum_type: type[Enum] | None = None
ufp_set_method: str | None = None
def _get_viewer_options(api: ProtectApiClient) -> list[dict[str, Any]]: def _get_viewer_options(api: ProtectApiClient) -> list[dict[str, Any]]:
@ -140,13 +137,11 @@ def _get_paired_camera_options(api: ProtectApiClient) -> list[dict[str, Any]]:
return options return options
def _get_viewer_current(obj: Any) -> str: def _get_viewer_current(obj: Viewer) -> str:
assert isinstance(obj, Viewer)
return obj.liveview_id return obj.liveview_id
def _get_light_motion_current(obj: Any) -> str: def _get_light_motion_current(obj: Light) -> str:
assert isinstance(obj, Light)
# a bit of extra to allow On Motion Always/Dark # a bit of extra to allow On Motion Always/Dark
if ( if (
obj.light_mode_settings.mode == LightModeType.MOTION obj.light_mode_settings.mode == LightModeType.MOTION
@ -156,15 +151,13 @@ def _get_light_motion_current(obj: Any) -> str:
return obj.light_mode_settings.mode.value return obj.light_mode_settings.mode.value
def _get_doorbell_current(obj: Any) -> str | None: def _get_doorbell_current(obj: Camera) -> str | None:
assert isinstance(obj, Camera)
if obj.lcd_message is None: if obj.lcd_message is None:
return None return None
return obj.lcd_message.text return obj.lcd_message.text
async def _set_light_mode(obj: Any, mode: str) -> None: async def _set_light_mode(obj: Light, mode: str) -> None:
assert isinstance(obj, Light)
lightmode, timing = LIGHT_MODE_TO_SETTINGS[mode] lightmode, timing = LIGHT_MODE_TO_SETTINGS[mode]
await obj.set_light_settings( await obj.set_light_settings(
LightModeType(lightmode), LightModeType(lightmode),
@ -172,10 +165,7 @@ async def _set_light_mode(obj: Any, mode: str) -> None:
) )
async def _set_paired_camera( async def _set_paired_camera(obj: Light | Sensor, camera_id: str) -> None:
obj: ProtectAdoptableDeviceModel | NVR, camera_id: str
) -> None:
assert isinstance(obj, (Sensor, Light))
if camera_id == TYPE_EMPTY_VALUE: if camera_id == TYPE_EMPTY_VALUE:
camera: Camera | None = None camera: Camera | None = None
else: else:
@ -183,8 +173,7 @@ async def _set_paired_camera(
await obj.set_paired_camera(camera) await obj.set_paired_camera(camera)
async def _set_doorbell_message(obj: Any, message: str) -> None: async def _set_doorbell_message(obj: Camera, message: str) -> None:
assert isinstance(obj, Camera)
if message.startswith(DoorbellMessageType.CUSTOM_MESSAGE.value): if message.startswith(DoorbellMessageType.CUSTOM_MESSAGE.value):
await obj.set_lcd_text(DoorbellMessageType.CUSTOM_MESSAGE, text=message) await obj.set_lcd_text(DoorbellMessageType.CUSTOM_MESSAGE, text=message)
elif message == TYPE_EMPTY_VALUE: elif message == TYPE_EMPTY_VALUE:
@ -193,8 +182,7 @@ async def _set_doorbell_message(obj: Any, message: str) -> None:
await obj.set_lcd_text(DoorbellMessageType(message)) await obj.set_lcd_text(DoorbellMessageType(message))
async def _set_liveview(obj: Any, liveview_id: str) -> None: async def _set_liveview(obj: Viewer, liveview_id: str) -> None:
assert isinstance(obj, Viewer)
liveview = obj.api.bootstrap.liveviews[liveview_id] liveview = obj.api.bootstrap.liveviews[liveview_id]
await obj.set_liveview(liveview) await obj.set_liveview(liveview)
@ -221,7 +209,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_value="isp_settings.ir_led_mode", ufp_value="isp_settings.ir_led_mode",
ufp_set_method="set_ir_led_model", ufp_set_method="set_ir_led_model",
), ),
ProtectSelectEntityDescription( ProtectSelectEntityDescription[Camera](
key="doorbell_text", key="doorbell_text",
name="Doorbell Text", name="Doorbell Text",
icon="mdi:card-text", icon="mdi:card-text",
@ -229,7 +217,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
device_class=DEVICE_CLASS_LCD_MESSAGE, device_class=DEVICE_CLASS_LCD_MESSAGE,
ufp_required_field="feature_flags.has_lcd_screen", ufp_required_field="feature_flags.has_lcd_screen",
ufp_value_fn=_get_doorbell_current, ufp_value_fn=_get_doorbell_current,
ufp_options_callable=_get_doorbell_options, ufp_options_fn=_get_doorbell_options,
ufp_set_method_fn=_set_doorbell_message, ufp_set_method_fn=_set_doorbell_message,
), ),
ProtectSelectEntityDescription( ProtectSelectEntityDescription(
@ -246,7 +234,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
) )
LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = ( LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription( ProtectSelectEntityDescription[Light](
key=_KEY_LIGHT_MOTION, key=_KEY_LIGHT_MOTION,
name="Light Mode", name="Light Mode",
icon="mdi:spotlight", icon="mdi:spotlight",
@ -255,13 +243,13 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_value_fn=_get_light_motion_current, ufp_value_fn=_get_light_motion_current,
ufp_set_method_fn=_set_light_mode, ufp_set_method_fn=_set_light_mode,
), ),
ProtectSelectEntityDescription( ProtectSelectEntityDescription[Light](
key="paired_camera", key="paired_camera",
name="Paired Camera", name="Paired Camera",
icon="mdi:cctv", icon="mdi:cctv",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="camera_id", ufp_value="camera_id",
ufp_options_callable=_get_paired_camera_options, ufp_options_fn=_get_paired_camera_options,
ufp_set_method_fn=_set_paired_camera, ufp_set_method_fn=_set_paired_camera,
), ),
) )
@ -277,24 +265,24 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ufp_value="mount_type", ufp_value="mount_type",
ufp_set_method="set_mount_type", ufp_set_method="set_mount_type",
), ),
ProtectSelectEntityDescription( ProtectSelectEntityDescription[Sensor](
key="paired_camera", key="paired_camera",
name="Paired Camera", name="Paired Camera",
icon="mdi:cctv", icon="mdi:cctv",
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
ufp_value="camera_id", ufp_value="camera_id",
ufp_options_callable=_get_paired_camera_options, ufp_options_fn=_get_paired_camera_options,
ufp_set_method_fn=_set_paired_camera, ufp_set_method_fn=_set_paired_camera,
), ),
) )
VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = ( VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription( ProtectSelectEntityDescription[Viewer](
key="viewer", key="viewer",
name="Liveview", name="Liveview",
icon="mdi:view-dashboard", icon="mdi:view-dashboard",
entity_category=None, entity_category=None,
ufp_options_callable=_get_viewer_options, ufp_options_fn=_get_viewer_options,
ufp_value_fn=_get_viewer_current, ufp_value_fn=_get_viewer_current,
ufp_set_method_fn=_set_liveview, ufp_set_method_fn=_set_liveview,
), ),
@ -350,7 +338,7 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity):
# entities with categories are not exposed for voice and safe to update dynamically # entities with categories are not exposed for voice and safe to update dynamically
if ( if (
self.entity_description.entity_category is not None self.entity_description.entity_category is not None
and self.entity_description.ufp_options_callable is not None and self.entity_description.ufp_options_fn is not None
): ):
_LOGGER.debug( _LOGGER.debug(
"Updating dynamic select options for %s", self.entity_description.name "Updating dynamic select options for %s", self.entity_description.name
@ -364,8 +352,8 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity):
if self.entity_description.ufp_options is not None: if self.entity_description.ufp_options is not None:
options = self.entity_description.ufp_options options = self.entity_description.ufp_options
else: else:
assert self.entity_description.ufp_options_callable is not None assert self.entity_description.ufp_options_fn is not None
options = self.entity_description.ufp_options_callable(self.data.api) options = self.entity_description.ufp_options_fn(self.data.api)
self._attr_options = [item["name"] for item in options] self._attr_options = [item["name"] for item in options]
self._hass_to_unifi_options = {item["name"]: item["id"] for item in options} self._hass_to_unifi_options = {item["name"]: item["id"] for item in options}

View File

@ -4,11 +4,16 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
import logging import logging
from typing import Any from typing import Any, Generic
from pyunifiprotect.data import NVR, Camera, Event from pyunifiprotect.data import (
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel NVR,
from pyunifiprotect.data.devices import Sensor Camera,
Event,
ProtectAdoptableDeviceModel,
ProtectDeviceModel,
Sensor,
)
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -40,7 +45,7 @@ from .entity import (
ProtectNVREntity, ProtectNVREntity,
async_all_device_entities, async_all_device_entities,
) )
from .models import ProtectRequiredKeysMixin from .models import ProtectRequiredKeysMixin, T
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
OBJECT_TYPE_NONE = "none" OBJECT_TYPE_NONE = "none"
@ -48,12 +53,14 @@ DEVICE_CLASS_DETECTION = "unifiprotect__detection"
@dataclass @dataclass
class ProtectSensorEntityDescription(ProtectRequiredKeysMixin, SensorEntityDescription): class ProtectSensorEntityDescription(
ProtectRequiredKeysMixin, SensorEntityDescription, Generic[T]
):
"""Describes UniFi Protect Sensor entity.""" """Describes UniFi Protect Sensor entity."""
precision: int | None = None precision: int | None = None
def get_ufp_value(self, obj: ProtectAdoptableDeviceModel | NVR) -> Any: def get_ufp_value(self, obj: ProtectDeviceModel) -> Any:
"""Return value from UniFi Protect device.""" """Return value from UniFi Protect device."""
value = super().get_ufp_value(obj) value = super().get_ufp_value(obj)
@ -62,7 +69,7 @@ class ProtectSensorEntityDescription(ProtectRequiredKeysMixin, SensorEntityDescr
return value return value
def _get_uptime(obj: ProtectAdoptableDeviceModel | NVR) -> datetime | None: def _get_uptime(obj: ProtectDeviceModel) -> datetime | None:
if obj.up_since is None: if obj.up_since is None:
return None return None
@ -71,26 +78,21 @@ def _get_uptime(obj: ProtectAdoptableDeviceModel | NVR) -> datetime | None:
return obj.up_since.replace(second=0, microsecond=0) return obj.up_since.replace(second=0, microsecond=0)
def _get_nvr_recording_capacity(obj: Any) -> int: def _get_nvr_recording_capacity(obj: NVR) -> int:
assert isinstance(obj, NVR)
if obj.storage_stats.capacity is None: if obj.storage_stats.capacity is None:
return 0 return 0
return int(obj.storage_stats.capacity.total_seconds()) return int(obj.storage_stats.capacity.total_seconds())
def _get_nvr_memory(obj: Any) -> float | None: def _get_nvr_memory(obj: NVR) -> float | None:
assert isinstance(obj, NVR)
memory = obj.system_info.memory memory = obj.system_info.memory
if memory.available is None or memory.total is None: if memory.available is None or memory.total is None:
return None return None
return (1 - memory.available / memory.total) * 100 return (1 - memory.available / memory.total) * 100
def _get_alarm_sound(obj: ProtectAdoptableDeviceModel | NVR) -> str: def _get_alarm_sound(obj: Sensor) -> str:
assert isinstance(obj, Sensor)
alarm_type = OBJECT_TYPE_NONE alarm_type = OBJECT_TYPE_NONE
if ( if (
@ -103,7 +105,7 @@ def _get_alarm_sound(obj: ProtectAdoptableDeviceModel | NVR) -> str:
ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription( ProtectSensorEntityDescription[ProtectDeviceModel](
key="uptime", key="uptime",
name="Uptime", name="Uptime",
icon="mdi:clock", icon="mdi:clock",
@ -244,7 +246,7 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ufp_value="stats.temperature.value", ufp_value="stats.temperature.value",
ufp_enabled="is_temperature_sensor_enabled", ufp_enabled="is_temperature_sensor_enabled",
), ),
ProtectSensorEntityDescription( ProtectSensorEntityDescription[Sensor](
key="alarm_sound", key="alarm_sound",
name="Alarm Sound Detected", name="Alarm Sound Detected",
ufp_value_fn=_get_alarm_sound, ufp_value_fn=_get_alarm_sound,
@ -253,7 +255,7 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
) )
NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription( ProtectSensorEntityDescription[ProtectDeviceModel](
key="uptime", key="uptime",
name="Uptime", name="Uptime",
icon="mdi:clock", icon="mdi:clock",
@ -331,7 +333,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ufp_value="storage_stats.storage_distribution.free.percentage", ufp_value="storage_stats.storage_distribution.free.percentage",
precision=2, precision=2,
), ),
ProtectSensorEntityDescription( ProtectSensorEntityDescription[NVR](
key="record_capacity", key="record_capacity",
name="Recording Capacity", name="Recording Capacity",
native_unit_of_measurement=TIME_SECONDS, native_unit_of_measurement=TIME_SECONDS,
@ -363,7 +365,7 @@ NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
ufp_value="system_info.cpu.temperature", ufp_value="system_info.cpu.temperature",
), ),
ProtectSensorEntityDescription( ProtectSensorEntityDescription[NVR](
key="memory_utilization", key="memory_utilization",
name="Memory Utilization", name="Memory Utilization",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import Any from typing import Any, Generic
from pyunifiprotect.data import Camera, RecordingMode, VideoMode from pyunifiprotect.data import Camera, RecordingMode, VideoMode
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
@ -17,26 +17,26 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .data import ProtectData from .data import ProtectData
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import ProtectSetableKeysMixin from .models import ProtectSetableKeysMixin, T
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@dataclass @dataclass
class ProtectSwitchEntityDescription(ProtectSetableKeysMixin, SwitchEntityDescription): class ProtectSwitchEntityDescription(
ProtectSetableKeysMixin, SwitchEntityDescription, Generic[T]
):
"""Describes UniFi Protect Switch entity.""" """Describes UniFi Protect Switch entity."""
_KEY_PRIVACY_MODE = "privacy_mode" _KEY_PRIVACY_MODE = "privacy_mode"
def _get_is_highfps(obj: Any) -> bool: def _get_is_highfps(obj: Camera) -> bool:
assert isinstance(obj, Camera)
return bool(obj.video_mode == VideoMode.HIGH_FPS) return bool(obj.video_mode == VideoMode.HIGH_FPS)
async def _set_highfps(obj: Any, value: bool) -> None: async def _set_highfps(obj: Camera, value: bool) -> None:
assert isinstance(obj, Camera)
if value: if value:
await obj.set_video_mode(VideoMode.HIGH_FPS) await obj.set_video_mode(VideoMode.HIGH_FPS)
else: else:
@ -74,7 +74,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_value="hdr_mode", ufp_value="hdr_mode",
ufp_set_method="set_hdr", ufp_set_method="set_hdr",
), ),
ProtectSwitchEntityDescription( ProtectSwitchEntityDescription[Camera](
key="high_fps", key="high_fps",
name="High FPS", name="High FPS",
icon="mdi:video-high-definition", icon="mdi:video-high-definition",