diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index e2e8e6b24f9..226b81b1c74 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -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.""" diff --git a/homeassistant/components/reolink/button.py b/homeassistant/components/reolink/button.py index 6e9c9c2e386..88204d9a806 100644 --- a/homeassistant/components/reolink/button.py +++ b/homeassistant/components/reolink/button.py @@ -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.""" diff --git a/homeassistant/components/reolink/camera.py b/homeassistant/components/reolink/camera.py index ea9b84cd53f..2ad8105c66c 100644 --- a/homeassistant/components/reolink/camera.py +++ b/homeassistant/components/reolink/camera.py @@ -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( diff --git a/homeassistant/components/reolink/entity.py b/homeassistant/components/reolink/entity.py index 5c874fb7ff9..584b380f391 100644 --- a/homeassistant/components/reolink/entity.py +++ b/homeassistant/components/reolink/entity.py @@ -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: diff --git a/homeassistant/components/reolink/light.py b/homeassistant/components/reolink/light.py index f1aa0cb9ee2..b2d0402b1b9 100644 --- a/homeassistant/components/reolink/light.py +++ b/homeassistant/components/reolink/light.py @@ -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} diff --git a/homeassistant/components/reolink/number.py b/homeassistant/components/reolink/number.py index 1780465850a..6a89eabba2b 100644 --- a/homeassistant/components/reolink/number.py +++ b/homeassistant/components/reolink/number.py @@ -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: diff --git a/homeassistant/components/reolink/select.py b/homeassistant/components/reolink/select.py index 566dbc92fbe..3d75b08b5d1 100644 --- a/homeassistant/components/reolink/select.py +++ b/homeassistant/components/reolink/select.py @@ -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: diff --git a/homeassistant/components/reolink/sensor.py b/homeassistant/components/reolink/sensor.py index 9a03f497944..3a5da97dc61 100644 --- a/homeassistant/components/reolink/sensor.py +++ b/homeassistant/components/reolink/sensor.py @@ -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: diff --git a/homeassistant/components/reolink/siren.py b/homeassistant/components/reolink/siren.py index f063b65e2b4..ec709f6ae3d 100644 --- a/homeassistant/components/reolink/siren.py +++ b/homeassistant/components/reolink/siren.py @@ -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.""" diff --git a/homeassistant/components/reolink/switch.py b/homeassistant/components/reolink/switch.py index eb77b16478f..fbb8922188d 100644 --- a/homeassistant/components/reolink/switch.py +++ b/homeassistant/components/reolink/switch.py @@ -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}"