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-09 23:25:39 +00:00
|
|
|
from uiprotect.data import NVR, Event, ProtectAdoptableDeviceModel
|
2022-01-10 04:37:24 +00:00
|
|
|
|
|
|
|
from homeassistant.helpers.entity import EntityDescription
|
|
|
|
|
|
|
|
from .utils import get_nested_attr
|
|
|
|
|
|
|
|
_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
|
|
|
|
get_ufp_enabled: Callable[[T], bool] = bool
|
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-16 02:02:03 +00:00
|
|
|
if (_ufp_value := self.ufp_value) is not None:
|
|
|
|
ufp_value = tuple(_ufp_value.split("."))
|
|
|
|
_setter("get_ufp_value", partial(get_nested_attr, attrs=ufp_value))
|
|
|
|
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-16 02:02:03 +00:00
|
|
|
if (_ufp_enabled := self.ufp_enabled) is not None:
|
|
|
|
ufp_enabled = tuple(_ufp_enabled.split("."))
|
|
|
|
_setter("get_ufp_enabled", partial(get_nested_attr, attrs=ufp_enabled))
|
2024-06-18 17:08:22 +00:00
|
|
|
|
2024-06-16 02:02:03 +00:00
|
|
|
if (_ufp_required_field := self.ufp_required_field) is not None:
|
|
|
|
ufp_required_field = tuple(_ufp_required_field.split("."))
|
|
|
|
_setter(
|
|
|
|
"has_required",
|
|
|
|
lambda obj: bool(get_nested_attr(obj, 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
|
|
|
|
|
|
|
|
def get_event_obj(self, obj: T) -> Event | None:
|
|
|
|
"""Return value from UniFi Protect device."""
|
|
|
|
return None
|
|
|
|
|
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__()
|
|
|
|
|
2024-01-11 05:02:16 +00:00
|
|
|
def get_is_on(self, obj: T, event: Event | None) -> bool:
|
2022-11-28 19:07:53 +00:00
|
|
|
"""Return value if event is active."""
|
2024-01-11 05:02:16 +00:00
|
|
|
return event is not None and self.get_ufp_value(obj)
|
2022-11-28 19:07:53 +00:00
|
|
|
|
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)
|