"""This component provides support for Reolink IP cameras.""" import asyncio from datetime import datetime import logging import voluptuous as vol from homeassistant.components.camera import SUPPORT_STREAM, Camera # from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_web, async_get_clientsession, ) from .const import ( DOMAIN_DATA, LAST_EVENT, SERVICE_PTZ_CONTROL, SERVICE_QUERY_VOD, SERVICE_SET_BACKLIGHT, SERVICE_SET_DAYNIGHT, SERVICE_SET_SENSITIVITY, SUPPORT_PLAYBACK, SUPPORT_PTZ, ) from .entity import ReolinkEntity from .typings import VoDEvent _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_devices): """Set up a Reolink IP Camera.""" platform = entity_platform.current_platform.get() camera = ReolinkCamera(hass, config_entry) platform.async_register_entity_service( SERVICE_SET_SENSITIVITY, { vol.Required("sensitivity"): cv.positive_int, vol.Optional("preset"): cv.positive_int, }, SERVICE_SET_SENSITIVITY, ) platform.async_register_entity_service( SERVICE_SET_DAYNIGHT, { vol.Required("mode"): cv.string, }, SERVICE_SET_DAYNIGHT, ) platform.async_register_entity_service( SERVICE_SET_BACKLIGHT, { vol.Required("mode"): cv.string, }, SERVICE_SET_BACKLIGHT, ) platform.async_register_entity_service( SERVICE_PTZ_CONTROL, { vol.Required("command"): cv.string, vol.Optional("preset"): cv.positive_int, vol.Optional("speed"): cv.positive_int, }, SERVICE_PTZ_CONTROL, [SUPPORT_PTZ], ) platform.async_register_entity_service( SERVICE_QUERY_VOD, { vol.Required("event_id"): cv.string, vol.Optional("start"): cv.datetime, vol.Optional("end"): cv.datetime, }, SERVICE_QUERY_VOD, [SUPPORT_PLAYBACK], ) async_add_devices([camera]) class ReolinkCamera(ReolinkEntity, Camera): """An implementation of a Reolink IP camera.""" def __init__(self, hass, config): """Initialize a Reolink camera.""" ReolinkEntity.__init__(self, hass, config) Camera.__init__(self) self._entry_id = config.entry_id # self._ffmpeg = self._hass.data[DATA_FFMPEG] # self._last_image = None self._ptz_commands = { "AUTO": "Auto", "DOWN": "Down", "FOCUSDEC": "FocusDec", "FOCUSINC": "FocusInc", "LEFT": "Left", "LEFTDOWN": "LeftDown", "LEFTUP": "LeftUp", "RIGHT": "Right", "RIGHTDOWN": "RightDown", "RIGHTUP": "RightUp", "STOP": "Stop", "TOPOS": "ToPos", "UP": "Up", "ZOOMDEC": "ZoomDec", "ZOOMINC": "ZoomInc", } self._daynight_modes = { "AUTO": "Auto", "COLOR": "Color", "BLACKANDWHITE": "Black&White", } self._backlight_modes = { "BACKLIGHTCONTROL": "BackLightControl", "DYNAMICRANGECONTROL": "DynamicRangeControl", "OFF": "Off", } @property def unique_id(self): """Return Unique ID string.""" return f"reolink_camera_{self._base.unique_id}" @property def name(self): """Return the name of this camera.""" return self._base.name @property def ptz_support(self): """Return whether the camera has PTZ support.""" return self._base.api.ptz_support @property def playback_support(self): """ Return whethere the camera has VoDs. """ # TODO : this should probably be like ptz above, and be a property of the api return bool(self._base.api.hdd_info) @property def device_state_attributes(self): """Return the camera state attributes.""" attrs = {} if self._base.api.ptz_support: attrs["ptz_presets"] = self._base.api.ptz_presets for key, value in self._backlight_modes.items(): if value == self._base.api.backlight_state: attrs["backlight_state"] = key for key, value in self._daynight_modes.items(): if value == self._base.api.daynight_state: attrs["daynight_state"] = key if self._base.api.sensitivity_presets: attrs["sensitivity"] = self.get_sensitivity_presets() if self.playback_support: data: dict = self.hass.data.get(DOMAIN_DATA) data = data.get(self._base.unique_id) if data else None last: VoDEvent = data.get(LAST_EVENT) if data else None if last and last.url: attrs["video_url"] = last.url if last.thumbnail and last.thumbnail.exists: attrs["video_thumbnail"] = last.thumbnail.url return attrs @property def supported_features(self): """Return supported features.""" features = SUPPORT_STREAM if self.ptz_support: features += SUPPORT_PTZ if self.playback_support: features += SUPPORT_PLAYBACK return features async def stream_source(self): """Return the source of the stream.""" return await self._base.api.get_stream_source() async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" stream_source = await self.stream_source() websession = async_get_clientsession(self._hass) stream_coro = websession.get(stream_source, timeout=10) return await async_aiohttp_proxy_web(self._hass, request, stream_coro) async def async_camera_image(self): """Return a still image response from the camera.""" return await self._base.api.get_snapshot() async def ptz_control(self, command, **kwargs): """Pass PTZ command to the camera.""" if not self.ptz_support: _LOGGER.error("PTZ is not supported on this device") return await self._base.api.set_ptz_command( command=self._ptz_commands[command], **kwargs ) async def query_vods(self, event_id, **kwargs): """ Query camera for VoDs and emit results """ if not self.playback_support: _LOGGER.error("Video Playback is not supported on this device") return await self._base.emit_search_results( event_id, self._entry_id, context=self._context, **kwargs ) def get_sensitivity_presets(self): """Get formatted sensitivity presets.""" presets = list() preset = dict() for api_preset in self._base.api.sensitivity_presets: preset["id"] = api_preset["id"] preset["sensitivity"] = api_preset["sensitivity"] time_string = f'{api_preset["beginHour"]}:{api_preset["beginMin"]}' begin = datetime.strptime(time_string, "%H:%M") preset["begin"] = begin.strftime("%H:%M") time_string = f'{api_preset["endHour"]}:{api_preset["endMin"]}' end = datetime.strptime(time_string, "%H:%M") preset["end"] = end.strftime("%H:%M") presets.append(preset.copy()) return presets async def set_sensitivity(self, sensitivity, **kwargs): """Set the sensitivity to the camera.""" if "preset" in kwargs: kwargs["preset"] += 1 # The camera preset ID's on the GUI are always +1 await self._base.api.set_sensitivity(value=sensitivity, **kwargs) async def set_daynight(self, mode): """Set the day and night mode to the camera.""" await self._base.api.set_daynight(value=self._daynight_modes[mode]) async def set_backlight(self, mode): """Set the backlight mode to the camera.""" await self._base.api.set_backlight(value=self._backlight_modes[mode]) async def async_enable_motion_detection(self): """Predefined camera service implementation.""" self._base.motion_detection_state = True async def async_disable_motion_detection(self): """Predefined camera service implementation.""" self._base.motion_detection_state = False