2022-01-01 21:23:10 +00:00
|
|
|
"""The unifiprotect integration models."""
|
2024-03-08 15:35:23 +00:00
|
|
|
|
2022-01-01 21:23:10 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-01-10 04:37:24 +00:00
|
|
|
from collections.abc import Callable, Coroutine
|
2022-01-01 21:23:10 +00:00
|
|
|
from dataclasses import dataclass
|
2022-06-21 16:17:29 +00:00
|
|
|
from enum import Enum
|
2024-06-16 02:02:03 +00:00
|
|
|
from functools import partial
|
2022-01-10 04:37:24 +00:00
|
|
|
import logging
|
2024-06-16 02:02:03 +00:00
|
|
|
from operator import attrgetter
|
|
|
|
from typing import Any, Generic, TypeVar
|
2022-01-10 04:37:24 +00:00
|
|
|
|
2024-06-21 16:26:14 +00:00
|
|
|
from uiprotect import make_enabled_getter, make_required_getter, make_value_getter
|
2024-06-21 03:03:07 +00:00
|
|
|
from uiprotect.data import (
|
|
|
|
NVR,
|
|
|
|
Event,
|
|
|
|
ProtectAdoptableDeviceModel,
|
|
|
|
SmartDetectObjectType,
|
|
|
|
)
|
2022-01-10 04:37:24 +00:00
|
|
|
|
|
|
|
from homeassistant.helpers.entity import EntityDescription
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2022-01-01 21:23:10 +00:00
|
|
|
|
2023-01-23 09:05:56 +00:00
|
|
|
T = TypeVar("T", bound=ProtectAdoptableDeviceModel | NVR)
|
2022-01-17 20:51:55 +00:00
|
|
|
|
2022-01-01 21:23:10 +00:00
|
|
|
|
2022-06-21 16:17:29 +00:00
|
|
|
class PermRequired(int, Enum):
|
|
|
|
"""Type of permission level required for entity."""
|
|
|
|
|
|
|
|
NO_WRITE = 1
|
|
|
|
WRITE = 2
|
2022-08-25 23:54:52 +00:00
|
|
|
DELETE = 3
|
2022-06-21 16:17:29 +00:00
|
|
|
|
|
|
|
|
2024-01-12 08:33:33 +00:00
|
|
|
@dataclass(frozen=True, kw_only=True)
|
2024-06-16 02:02:03 +00:00
|
|
|
class ProtectEntityDescription(EntityDescription, Generic[T]):
|
|
|
|
"""Base class for protect entity descriptions."""
|
|
|
|
|
|
|
|
ufp_required_field: str | None = None
|
|
|
|
ufp_value: str | None = None
|
2022-01-17 20:51:55 +00:00
|
|
|
ufp_value_fn: Callable[[T], Any] | None = None
|
2024-06-16 02:02:03 +00:00
|
|
|
ufp_enabled: str | None = None
|
2022-06-21 16:17:29 +00:00
|
|
|
ufp_perm: PermRequired | None = None
|
2022-01-10 04:37:24 +00:00
|
|
|
|
2024-06-18 17:08:22 +00:00
|
|
|
# The below are set in __post_init__
|
|
|
|
has_required: Callable[[T], bool] = bool
|
2024-06-21 09:58:45 +00:00
|
|
|
get_ufp_enabled: Callable[[T], bool] | None = None
|
2024-06-16 02:02:03 +00:00
|
|
|
|
2024-06-18 17:08:22 +00:00
|
|
|
def get_ufp_value(self, obj: T) -> Any:
|
|
|
|
"""Return value from UniFi Protect device; overridden in __post_init__."""
|
|
|
|
# ufp_value or ufp_value_fn are required, the
|
2024-06-16 02:02:03 +00:00
|
|
|
# RuntimeError is to catch any issues in the code
|
|
|
|
# with new descriptions.
|
2022-01-10 04:37:24 +00:00
|
|
|
raise RuntimeError( # pragma: no cover
|
2024-06-18 17:08:22 +00:00
|
|
|
f"`ufp_value` or `ufp_value_fn` is required for {self}"
|
2022-01-10 04:37:24 +00:00
|
|
|
)
|
|
|
|
|
2024-06-16 02:02:03 +00:00
|
|
|
def __post_init__(self) -> None:
|
|
|
|
"""Override get_ufp_value, has_required, and get_ufp_enabled if required."""
|
|
|
|
_setter = partial(object.__setattr__, self)
|
2024-06-18 17:08:22 +00:00
|
|
|
|
2024-06-21 16:26:14 +00:00
|
|
|
if (ufp_value := self.ufp_value) is not None:
|
|
|
|
_setter("get_ufp_value", make_value_getter(ufp_value))
|
2024-06-16 02:02:03 +00:00
|
|
|
elif (ufp_value_fn := self.ufp_value_fn) is not None:
|
|
|
|
_setter("get_ufp_value", ufp_value_fn)
|
2024-06-18 17:08:22 +00:00
|
|
|
|
2024-06-21 16:26:14 +00:00
|
|
|
if (ufp_enabled := self.ufp_enabled) is not None:
|
|
|
|
_setter("get_ufp_enabled", make_enabled_getter(ufp_enabled))
|
2024-06-18 17:08:22 +00:00
|
|
|
|
2024-06-21 16:26:14 +00:00
|
|
|
if (ufp_required_field := self.ufp_required_field) is not None:
|
|
|
|
_setter("has_required", make_required_getter(ufp_required_field))
|
2022-11-28 19:07:53 +00:00
|
|
|
|
|
|
|
|
2024-01-12 08:33:33 +00:00
|
|
|
@dataclass(frozen=True, kw_only=True)
|
2024-06-16 02:02:03 +00:00
|
|
|
class ProtectEventMixin(ProtectEntityDescription[T]):
|
2022-11-28 19:07:53 +00:00
|
|
|
"""Mixin for events."""
|
|
|
|
|
|
|
|
ufp_event_obj: str | None = None
|
2024-06-21 03:03:07 +00:00
|
|
|
ufp_obj_type: SmartDetectObjectType | None = None
|
2022-11-28 19:07:53 +00:00
|
|
|
|
|
|
|
def get_event_obj(self, obj: T) -> Event | None:
|
|
|
|
"""Return value from UniFi Protect device."""
|
|
|
|
return None
|
|
|
|
|
2024-06-21 03:03:07 +00:00
|
|
|
def has_matching_smart(self, event: Event) -> bool:
|
|
|
|
"""Determine if the detection type is a match."""
|
|
|
|
return (
|
|
|
|
not (obj_type := self.ufp_obj_type) or obj_type in event.smart_detect_types
|
|
|
|
)
|
|
|
|
|
2024-06-16 02:02:03 +00:00
|
|
|
def __post_init__(self) -> None:
|
|
|
|
"""Override get_event_obj if ufp_event_obj is set."""
|
|
|
|
if (_ufp_event_obj := self.ufp_event_obj) is not None:
|
|
|
|
object.__setattr__(self, "get_event_obj", attrgetter(_ufp_event_obj))
|
|
|
|
super().__post_init__()
|
|
|
|
|
2022-01-10 04:37:24 +00:00
|
|
|
|
2024-01-12 08:33:33 +00:00
|
|
|
@dataclass(frozen=True, kw_only=True)
|
2024-06-16 02:02:03 +00:00
|
|
|
class ProtectSetableKeysMixin(ProtectEntityDescription[T]):
|
2022-01-17 20:51:55 +00:00
|
|
|
"""Mixin for settable values."""
|
2022-01-10 04:37:24 +00:00
|
|
|
|
|
|
|
ufp_set_method: str | None = None
|
2022-01-17 20:51:55 +00:00
|
|
|
ufp_set_method_fn: Callable[[T, Any], Coroutine[Any, Any, None]] | None = None
|
2022-01-10 04:37:24 +00:00
|
|
|
|
2022-01-17 20:51:55 +00:00
|
|
|
async def ufp_set(self, obj: T, value: Any) -> None:
|
2022-01-10 04:37:24 +00:00
|
|
|
"""Set value for UniFi Protect device."""
|
2022-06-22 20:57:21 +00:00
|
|
|
_LOGGER.debug("Setting %s to %s for %s", self.name, value, obj.display_name)
|
2022-01-10 04:37:24 +00:00
|
|
|
if self.ufp_set_method is not None:
|
|
|
|
await getattr(obj, self.ufp_set_method)(value)
|
|
|
|
elif self.ufp_set_method_fn is not None:
|
|
|
|
await self.ufp_set_method_fn(obj, value)
|