home-automation-home-assistant/custom_components/reolink_dev/camera.py

268 lines
8.4 KiB
Python

"""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