diff --git a/homeassistant/components/reolink/camera.py b/homeassistant/components/reolink/camera.py index b012649ec4c..1bb1c14374c 100644 --- a/homeassistant/components/reolink/camera.py +++ b/homeassistant/components/reolink/camera.py @@ -1,11 +1,17 @@ """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 +from reolink_aio.api import DUAL_LENS_MODELS, Host -from homeassistant.components.camera import Camera, CameraEntityFeature +from homeassistant.components.camera import ( + Camera, + CameraEntityDescription, + CameraEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -17,6 +23,70 @@ from .entity import ReolinkChannelCoordinatorEntity _LOGGER = logging.getLogger(__name__) +@dataclass(kw_only=True) +class ReolinkCameraEntityDescription( + CameraEntityDescription, +): + """A class that describes camera entities for a camera channel.""" + + stream: str + supported: Callable[[Host, int], bool] = lambda api, ch: True + + +CAMERA_ENTITIES = ( + ReolinkCameraEntityDescription( + key="sub", + stream="sub", + translation_key="sub", + ), + ReolinkCameraEntityDescription( + key="main", + stream="main", + translation_key="main", + entity_registry_enabled_default=False, + ), + ReolinkCameraEntityDescription( + key="snapshots_sub", + stream="snapshots_sub", + translation_key="snapshots_sub", + entity_registry_enabled_default=False, + ), + ReolinkCameraEntityDescription( + key="snapshots", + stream="snapshots_main", + translation_key="snapshots_main", + entity_registry_enabled_default=False, + ), + ReolinkCameraEntityDescription( + key="ext", + stream="ext", + translation_key="ext", + supported=lambda api, ch: api.protocol in ["rtmp", "flv"], + entity_registry_enabled_default=False, + ), + ReolinkCameraEntityDescription( + key="autotrack_sub", + stream="autotrack_sub", + translation_key="autotrack_sub", + supported=lambda api, ch: api.supported(ch, "autotrack_stream"), + ), + ReolinkCameraEntityDescription( + key="autotrack_snapshots_sub", + stream="autotrack_snapshots_sub", + translation_key="autotrack_snapshots_sub", + supported=lambda api, ch: api.supported(ch, "autotrack_stream"), + entity_registry_enabled_default=False, + ), + ReolinkCameraEntityDescription( + key="autotrack_snapshots_main", + stream="autotrack_snapshots_main", + translation_key="autotrack_snapshots_main", + supported=lambda api, ch: api.supported(ch, "autotrack_stream"), + entity_registry_enabled_default=False, + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -24,62 +94,59 @@ async def async_setup_entry( ) -> None: """Set up a Reolink IP Camera.""" reolink_data: ReolinkData = hass.data[DOMAIN][config_entry.entry_id] - host = reolink_data.host - cameras = [] - for channel in host.api.stream_channels: - streams = ["sub", "main", "snapshots_sub", "snapshots_main"] - if host.api.protocol in ["rtmp", "flv"]: - streams.append("ext") - - if host.api.supported(channel, "autotrack_stream"): - streams.extend( - ["autotrack_sub", "autotrack_snapshots_sub", "autotrack_snapshots_main"] - ) - - for stream in streams: - stream_url = await host.api.get_stream_source(channel, stream) - if stream_url is None and "snapshots" not in stream: + entities: list[ReolinkCamera] = [] + for entity_description in CAMERA_ENTITIES: + for channel in reolink_data.host.api.stream_channels: + if not entity_description.supported(reolink_data.host.api, channel): + continue + stream_url = await reolink_data.host.api.get_stream_source( + channel, entity_description.stream + ) + if stream_url is None and "snapshots" not in entity_description.stream: continue - cameras.append(ReolinkCamera(reolink_data, channel, stream)) - async_add_entities(cameras) + entities.append(ReolinkCamera(reolink_data, channel, entity_description)) + + async_add_entities(entities) class ReolinkCamera(ReolinkChannelCoordinatorEntity, Camera): """An implementation of a Reolink IP camera.""" _attr_supported_features: CameraEntityFeature = CameraEntityFeature.STREAM + entity_description: ReolinkCameraEntityDescription def __init__( self, reolink_data: ReolinkData, channel: int, - stream: str, + entity_description: ReolinkCameraEntityDescription, ) -> None: """Initialize Reolink camera stream.""" + self.entity_description = entity_description ReolinkChannelCoordinatorEntity.__init__(self, reolink_data, channel) Camera.__init__(self) - self._stream = stream - - stream_name = self._stream.replace("_", " ") if self._host.api.model in DUAL_LENS_MODELS: - self._attr_name = f"{stream_name} lens {self._channel}" - else: - self._attr_name = stream_name - stream_id = self._stream - if stream_id == "snapshots_main": - stream_id = "snapshots" - self._attr_unique_id = f"{self._host.unique_id}_{self._channel}_{stream_id}" - self._attr_entity_registry_enabled_default = stream in ["sub", "autotrack_sub"] + self._attr_translation_key = ( + 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(self._channel, self._stream) + return await self._host.api.get_stream_source( + self._channel, self.entity_description.stream + ) async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return a still image response from the camera.""" - return await self._host.api.get_snapshot(self._channel, self._stream) + return await self._host.api.get_snapshot( + self._channel, self.entity_description.stream + ) diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index 4170626b547..bab2802720d 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -147,6 +147,62 @@ "name": "Guard set current position" } }, + "camera": { + "sub": { + "name": "Fluent" + }, + "main": { + "name": "Clear" + }, + "snapshots_sub": { + "name": "Snapshots fluent" + }, + "snapshots_main": { + "name": "Snapshots clear" + }, + "ext": { + "name": "Balanced" + }, + "sub_lens_0": { + "name": "Fluent lens 0" + }, + "main_lens_0": { + "name": "Clear lens 0" + }, + "snapshots_sub_lens_0": { + "name": "Snapshots fluent lens 0" + }, + "snapshots_main_lens_0": { + "name": "Snapshots clear lens 0" + }, + "ext_lens_0": { + "name": "Balanced lens 0" + }, + "sub_lens_1": { + "name": "Fluent lens 1" + }, + "main_lens_1": { + "name": "Clear lens 1" + }, + "snapshots_sub_lens_1": { + "name": "Snapshots fluent lens 1" + }, + "snapshots_main_lens_1": { + "name": "Snapshots clear lens 1" + }, + "ext_lens_1": { + "name": "Balanced lens 1" + }, + "autotrack_sub": { + "name": "Autotrack fluent" + }, + "autotrack_snapshots_sub": { + "name": "Autotrack snapshots fluent" + }, + "autotrack_snapshots_main": { + "name": "Autotrack snapshots clear" + } + }, "light": { "floodlight": { "name": "Floodlight"