Add common reolink entity description (#104142)

Co-authored-by: Robert Resch <robert@resch.dev>
pull/104157/head^2
starkillerOG 2023-11-30 11:26:33 +01:00 committed by GitHub
parent dd00357e9c
commit 69af4c8603
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 109 additions and 99 deletions

View File

@ -25,16 +25,18 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
@dataclass(kw_only=True)
class ReolinkBinarySensorEntityDescription(BinarySensorEntityDescription):
class ReolinkBinarySensorEntityDescription(
BinarySensorEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes binary sensor entities."""
icon_off: str = "mdi:motion-sensor-off"
icon: str = "mdi:motion-sensor"
supported: Callable[[Host, int], bool] = lambda host, ch: True
value: Callable[[Host, int], bool]
@ -128,8 +130,8 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt
entity_description: ReolinkBinarySensorEntityDescription,
) -> None:
"""Initialize Reolink binary sensor."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
super().__init__(reolink_data, channel)
if self._host.api.model in DUAL_LENS_DUAL_MOTION_MODELS:
if entity_description.translation_key is not None:
@ -138,10 +140,6 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt
key = entity_description.key
self._attr_translation_key = f"{key}_lens_{self._channel}"
self._attr_unique_id = (
f"{self._host.unique_id}_{self._channel}_{entity_description.key}"
)
@property
def icon(self) -> str | None:
"""Icon of the sensor."""

View File

@ -27,7 +27,12 @@ from homeassistant.helpers.entity_platform import (
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity, ReolinkHostCoordinatorEntity
from .entity import (
ReolinkChannelCoordinatorEntity,
ReolinkChannelEntityDescription,
ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription,
)
ATTR_SPEED = "speed"
SUPPORT_PTZ_SPEED = CameraEntityFeature.STREAM
@ -36,21 +41,23 @@ SUPPORT_PTZ_SPEED = CameraEntityFeature.STREAM
@dataclass(kw_only=True)
class ReolinkButtonEntityDescription(
ButtonEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes button entities for a camera channel."""
enabled_default: Callable[[Host, int], bool] | None = None
method: Callable[[Host, int], Any]
supported: Callable[[Host, int], bool] = lambda api, ch: True
ptz_cmd: str | None = None
@dataclass(kw_only=True)
class ReolinkHostButtonEntityDescription(ButtonEntityDescription):
class ReolinkHostButtonEntityDescription(
ButtonEntityDescription,
ReolinkHostEntityDescription,
):
"""A class that describes button entities for the host."""
method: Callable[[Host], Any]
supported: Callable[[Host], bool] = lambda api: True
BUTTON_ENTITIES = (
@ -195,12 +202,9 @@ class ReolinkButtonEntity(ReolinkChannelCoordinatorEntity, ButtonEntity):
entity_description: ReolinkButtonEntityDescription,
) -> None:
"""Initialize Reolink button entity."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
super().__init__(reolink_data, channel)
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
if entity_description.enabled_default is not None:
self._attr_entity_registry_enabled_default = (
entity_description.enabled_default(self._host.api, self._channel)
@ -241,10 +245,8 @@ class ReolinkHostButtonEntity(ReolinkHostCoordinatorEntity, ButtonEntity):
entity_description: ReolinkHostButtonEntityDescription,
) -> None:
"""Initialize Reolink button entity."""
super().__init__(reolink_data)
self.entity_description = entity_description
self._attr_unique_id = f"{self._host.unique_id}_{entity_description.key}"
super().__init__(reolink_data)
async def async_press(self) -> None:
"""Execute the button action."""

View File

@ -1,11 +1,10 @@
"""Component providing support for Reolink IP cameras."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from reolink_aio.api import DUAL_LENS_MODELS, Host
from reolink_aio.api import DUAL_LENS_MODELS
from reolink_aio.exceptions import ReolinkError
from homeassistant.components.camera import (
@ -20,7 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
_LOGGER = logging.getLogger(__name__)
@ -28,11 +27,11 @@ _LOGGER = logging.getLogger(__name__)
@dataclass(kw_only=True)
class ReolinkCameraEntityDescription(
CameraEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes camera entities for a camera channel."""
stream: str
supported: Callable[[Host, int], bool] = lambda api, ch: True
CAMERA_ENTITIES = (
@ -135,10 +134,6 @@ class ReolinkCamera(ReolinkChannelCoordinatorEntity, Camera):
f"{entity_description.translation_key}_lens_{self._channel}"
)
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
async def stream_source(self) -> str | None:
"""Return the source of the stream."""
return await self._host.api.get_stream_source(

View File

@ -1,11 +1,14 @@
"""Reolink parent entity class."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import TypeVar
from reolink_aio.api import DUAL_LENS_MODELS
from reolink_aio.api import DUAL_LENS_MODELS, Host
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
@ -17,8 +20,22 @@ from .const import DOMAIN
_T = TypeVar("_T")
@dataclass(kw_only=True)
class ReolinkChannelEntityDescription(EntityDescription):
"""A class that describes entities for a camera channel."""
supported: Callable[[Host, int], bool] = lambda api, ch: True
@dataclass(kw_only=True)
class ReolinkHostEntityDescription(EntityDescription):
"""A class that describes host entities."""
supported: Callable[[Host], bool] = lambda api: True
class ReolinkBaseCoordinatorEntity(CoordinatorEntity[DataUpdateCoordinator[_T]]):
"""Parent class fo Reolink entities."""
"""Parent class for Reolink entities."""
_attr_has_entity_name = True
@ -59,14 +76,20 @@ class ReolinkHostCoordinatorEntity(ReolinkBaseCoordinatorEntity[None]):
basically a NVR with a single channel that has the camera connected to that channel.
"""
entity_description: ReolinkHostEntityDescription | ReolinkChannelEntityDescription
def __init__(self, reolink_data: ReolinkData) -> None:
"""Initialize ReolinkHostCoordinatorEntity."""
super().__init__(reolink_data, reolink_data.device_coordinator)
self._attr_unique_id = f"{self._host.unique_id}_{self.entity_description.key}"
class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity):
"""Parent class for Reolink hardware camera entities connected to a channel of the NVR."""
entity_description: ReolinkChannelEntityDescription
def __init__(
self,
reolink_data: ReolinkData,
@ -76,6 +99,9 @@ class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity):
super().__init__(reolink_data)
self._channel = channel
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{self.entity_description.key}"
)
dev_ch = channel
if self._host.api.model in DUAL_LENS_MODELS:

View File

@ -22,17 +22,19 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
@dataclass(kw_only=True)
class ReolinkLightEntityDescription(LightEntityDescription):
class ReolinkLightEntityDescription(
LightEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes light entities."""
get_brightness_fn: Callable[[Host, int], int | None] | None = None
is_on_fn: Callable[[Host, int], bool]
set_brightness_fn: Callable[[Host, int, int], Any] | None = None
supported_fn: Callable[[Host, int], bool] = lambda api, ch: True
turn_on_off_fn: Callable[[Host, int, bool], Any]
@ -41,7 +43,7 @@ LIGHT_ENTITIES = (
key="floodlight",
translation_key="floodlight",
icon="mdi:spotlight-beam",
supported_fn=lambda api, ch: api.supported(ch, "floodLight"),
supported=lambda api, ch: api.supported(ch, "floodLight"),
is_on_fn=lambda api, ch: api.whiteled_state(ch),
turn_on_off_fn=lambda api, ch, value: api.set_whiteled(ch, state=value),
get_brightness_fn=lambda api, ch: api.whiteled_brightness(ch),
@ -52,7 +54,7 @@ LIGHT_ENTITIES = (
translation_key="ir_lights",
icon="mdi:led-off",
entity_category=EntityCategory.CONFIG,
supported_fn=lambda api, ch: api.supported(ch, "ir_lights"),
supported=lambda api, ch: api.supported(ch, "ir_lights"),
is_on_fn=lambda api, ch: api.ir_enabled(ch),
turn_on_off_fn=lambda api, ch, value: api.set_ir_lights(ch, value),
),
@ -61,7 +63,7 @@ LIGHT_ENTITIES = (
translation_key="status_led",
icon="mdi:lightning-bolt-circle",
entity_category=EntityCategory.CONFIG,
supported_fn=lambda api, ch: api.supported(ch, "power_led"),
supported=lambda api, ch: api.supported(ch, "power_led"),
is_on_fn=lambda api, ch: api.status_led_enabled(ch),
turn_on_off_fn=lambda api, ch, value: api.set_status_led(ch, value),
),
@ -80,7 +82,7 @@ async def async_setup_entry(
ReolinkLightEntity(reolink_data, channel, entity_description)
for entity_description in LIGHT_ENTITIES
for channel in reolink_data.host.api.channels
if entity_description.supported_fn(reolink_data.host.api, channel)
if entity_description.supported(reolink_data.host.api, channel)
)
@ -96,12 +98,8 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity):
entity_description: ReolinkLightEntityDescription,
) -> None:
"""Initialize Reolink light entity."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
super().__init__(reolink_data, channel)
if entity_description.set_brightness_fn is None:
self._attr_supported_color_modes = {ColorMode.ONOFF}

View File

@ -21,18 +21,20 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
@dataclass(kw_only=True)
class ReolinkNumberEntityDescription(NumberEntityDescription):
class ReolinkNumberEntityDescription(
NumberEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes number entities."""
get_max_value: Callable[[Host, int], float] | None = None
get_min_value: Callable[[Host, int], float] | None = None
method: Callable[[Host, int, float], Any]
mode: NumberMode = NumberMode.AUTO
supported: Callable[[Host, int], bool] = lambda api, ch: True
value: Callable[[Host, int], float | None]
@ -378,8 +380,8 @@ class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity):
entity_description: ReolinkNumberEntityDescription,
) -> None:
"""Initialize Reolink number entity."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
super().__init__(reolink_data, channel)
if entity_description.get_min_value is not None:
self._attr_native_min_value = entity_description.get_min_value(
@ -390,9 +392,6 @@ class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity):
self._host.api, channel
)
self._attr_mode = entity_description.mode
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
@property
def native_value(self) -> float | None:

View File

@ -24,18 +24,20 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
_LOGGER = logging.getLogger(__name__)
@dataclass(kw_only=True)
class ReolinkSelectEntityDescription(SelectEntityDescription):
class ReolinkSelectEntityDescription(
SelectEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes select entities."""
get_options: list[str] | Callable[[Host, int], list[str]]
method: Callable[[Host, int, str], Any]
supported: Callable[[Host, int], bool] = lambda api, ch: True
value: Callable[[Host, int], str] | None = None
@ -131,14 +133,10 @@ class ReolinkSelectEntity(ReolinkChannelCoordinatorEntity, SelectEntity):
entity_description: ReolinkSelectEntityDescription,
) -> None:
"""Initialize Reolink select entity."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
super().__init__(reolink_data, channel)
self._log_error = True
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
if callable(entity_description.get_options):
self._attr_options = entity_description.get_options(self._host.api, channel)
else:

View File

@ -21,31 +21,32 @@ from homeassistant.helpers.typing import StateType
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity, ReolinkHostCoordinatorEntity
from .entity import (
ReolinkChannelCoordinatorEntity,
ReolinkChannelEntityDescription,
ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription,
)
@dataclass(kw_only=True)
class ReolinkSensorEntityDescription(SensorEntityDescription):
class ReolinkSensorEntityDescription(
SensorEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes sensor entities for a camera channel."""
supported: Callable[[Host, int], bool] = lambda api, ch: True
value: Callable[[Host, int], int]
@dataclass
class ReolinkHostSensorEntityDescriptionMixin:
"""Mixin values for Reolink host sensor entities."""
value: Callable[[Host], int | None]
@dataclass
@dataclass(kw_only=True)
class ReolinkHostSensorEntityDescription(
SensorEntityDescription, ReolinkHostSensorEntityDescriptionMixin
SensorEntityDescription,
ReolinkHostEntityDescription,
):
"""A class that describes host sensor entities."""
supported: Callable[[Host], bool] = lambda api: True
value: Callable[[Host], int | None]
SENSORS = (
@ -110,12 +111,8 @@ class ReolinkSensorEntity(ReolinkChannelCoordinatorEntity, SensorEntity):
entity_description: ReolinkSensorEntityDescription,
) -> None:
"""Initialize Reolink sensor."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
super().__init__(reolink_data, channel)
@property
def native_value(self) -> StateType | date | datetime | Decimal:
@ -134,10 +131,8 @@ class ReolinkHostSensorEntity(ReolinkHostCoordinatorEntity, SensorEntity):
entity_description: ReolinkHostSensorEntityDescription,
) -> None:
"""Initialize Reolink host sensor."""
super().__init__(reolink_data)
self.entity_description = entity_description
self._attr_unique_id = f"{self._host.unique_id}_{entity_description.key}"
super().__init__(reolink_data)
@property
def native_value(self) -> StateType | date | datetime | Decimal:

View File

@ -1,11 +1,9 @@
"""Component providing support for Reolink siren entities."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from reolink_aio.api import Host
from reolink_aio.exceptions import InvalidParameterError, ReolinkError
from homeassistant.components.siren import (
@ -22,15 +20,15 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity
from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription
@dataclass
class ReolinkSirenEntityDescription(SirenEntityDescription):
class ReolinkSirenEntityDescription(
SirenEntityDescription, ReolinkChannelEntityDescription
):
"""A class that describes siren entities."""
supported: Callable[[Host, int], bool] = lambda api, ch: True
SIREN_ENTITIES = (
ReolinkSirenEntityDescription(
@ -76,12 +74,8 @@ class ReolinkSirenEntity(ReolinkChannelCoordinatorEntity, SirenEntity):
entity_description: ReolinkSirenEntityDescription,
) -> None:
"""Initialize Reolink siren entity."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
super().__init__(reolink_data, channel)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the siren."""

View File

@ -17,24 +17,33 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ReolinkData
from .const import DOMAIN
from .entity import ReolinkChannelCoordinatorEntity, ReolinkHostCoordinatorEntity
from .entity import (
ReolinkChannelCoordinatorEntity,
ReolinkChannelEntityDescription,
ReolinkHostCoordinatorEntity,
ReolinkHostEntityDescription,
)
@dataclass(kw_only=True)
class ReolinkSwitchEntityDescription(SwitchEntityDescription):
class ReolinkSwitchEntityDescription(
SwitchEntityDescription,
ReolinkChannelEntityDescription,
):
"""A class that describes switch entities."""
method: Callable[[Host, int, bool], Any]
supported: Callable[[Host, int], bool] = lambda api, ch: True
value: Callable[[Host, int], bool]
@dataclass(kw_only=True)
class ReolinkNVRSwitchEntityDescription(SwitchEntityDescription):
class ReolinkNVRSwitchEntityDescription(
SwitchEntityDescription,
ReolinkHostEntityDescription,
):
"""A class that describes NVR switch entities."""
method: Callable[[Host, bool], Any]
supported: Callable[[Host], bool] = lambda api: True
value: Callable[[Host], bool]
@ -235,12 +244,8 @@ class ReolinkSwitchEntity(ReolinkChannelCoordinatorEntity, SwitchEntity):
entity_description: ReolinkSwitchEntityDescription,
) -> None:
"""Initialize Reolink switch entity."""
super().__init__(reolink_data, channel)
self.entity_description = entity_description
self._attr_unique_id = (
f"{self._host.unique_id}_{channel}_{entity_description.key}"
)
super().__init__(reolink_data, channel)
@property
def is_on(self) -> bool:
@ -275,8 +280,8 @@ class ReolinkNVRSwitchEntity(ReolinkHostCoordinatorEntity, SwitchEntity):
entity_description: ReolinkNVRSwitchEntityDescription,
) -> None:
"""Initialize Reolink switch entity."""
super().__init__(reolink_data)
self.entity_description = entity_description
super().__init__(reolink_data)
self._attr_unique_id = f"{self._host.unique_id}_{entity_description.key}"