2022-12-27 20:15:53 +00:00
|
|
|
"""This component encapsulates the NVR/camera API and subscription."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import asyncio
|
2023-01-01 22:32:17 +00:00
|
|
|
from collections.abc import Mapping
|
2022-12-27 20:15:53 +00:00
|
|
|
import logging
|
2023-01-01 22:32:17 +00:00
|
|
|
from typing import Any
|
2022-12-27 20:15:53 +00:00
|
|
|
|
|
|
|
import aiohttp
|
2023-01-03 01:49:55 +00:00
|
|
|
from reolink_aio.api import Host
|
|
|
|
from reolink_aio.exceptions import (
|
2022-12-27 20:15:53 +00:00
|
|
|
ApiError,
|
|
|
|
CredentialsInvalidError,
|
|
|
|
InvalidContentTypeError,
|
|
|
|
)
|
|
|
|
|
|
|
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.helpers.device_registry import format_mac
|
|
|
|
|
2023-01-01 22:32:17 +00:00
|
|
|
from .const import CONF_PROTOCOL, CONF_USE_HTTPS, DEFAULT_TIMEOUT
|
2022-12-27 20:15:53 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class ReolinkHost:
|
|
|
|
"""The implementation of the Reolink Host class."""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
hass: HomeAssistant,
|
2023-01-01 22:32:17 +00:00
|
|
|
config: Mapping[str, Any],
|
|
|
|
options: Mapping[str, Any],
|
2022-12-27 20:15:53 +00:00
|
|
|
) -> None:
|
|
|
|
"""Initialize Reolink Host. Could be either NVR, or Camera."""
|
|
|
|
self._hass: HomeAssistant = hass
|
|
|
|
|
|
|
|
self._clientsession: aiohttp.ClientSession | None = None
|
2023-01-01 22:32:17 +00:00
|
|
|
self._unique_id: str = ""
|
2022-12-27 20:15:53 +00:00
|
|
|
|
|
|
|
self._api = Host(
|
|
|
|
config[CONF_HOST],
|
|
|
|
config[CONF_USERNAME],
|
|
|
|
config[CONF_PASSWORD],
|
|
|
|
port=config.get(CONF_PORT),
|
|
|
|
use_https=config.get(CONF_USE_HTTPS),
|
2023-01-01 22:32:17 +00:00
|
|
|
protocol=options[CONF_PROTOCOL],
|
2022-12-27 20:15:53 +00:00
|
|
|
timeout=DEFAULT_TIMEOUT,
|
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
2023-01-01 22:32:17 +00:00
|
|
|
def unique_id(self) -> str:
|
2022-12-27 20:15:53 +00:00
|
|
|
"""Create the unique ID, base for all entities."""
|
|
|
|
return self._unique_id
|
|
|
|
|
|
|
|
@property
|
|
|
|
def api(self):
|
|
|
|
"""Return the API object."""
|
|
|
|
return self._api
|
|
|
|
|
|
|
|
async def async_init(self) -> bool:
|
|
|
|
"""Connect to Reolink host."""
|
|
|
|
self._api.expire_session()
|
|
|
|
|
|
|
|
if not await self._api.get_host_data():
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self._api.mac_address is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
enable_onvif = None
|
|
|
|
enable_rtmp = None
|
|
|
|
enable_rtsp = None
|
|
|
|
|
|
|
|
if not self._api.onvif_enabled:
|
|
|
|
_LOGGER.debug(
|
|
|
|
"ONVIF is disabled on %s, trying to enable it", self._api.nvr_name
|
|
|
|
)
|
|
|
|
enable_onvif = True
|
|
|
|
|
|
|
|
if not self._api.rtmp_enabled and self._api.protocol == "rtmp":
|
|
|
|
_LOGGER.debug(
|
|
|
|
"RTMP is disabled on %s, trying to enable it", self._api.nvr_name
|
|
|
|
)
|
|
|
|
enable_rtmp = True
|
|
|
|
elif not self._api.rtsp_enabled and self._api.protocol == "rtsp":
|
|
|
|
_LOGGER.debug(
|
|
|
|
"RTSP is disabled on %s, trying to enable it", self._api.nvr_name
|
|
|
|
)
|
|
|
|
enable_rtsp = True
|
|
|
|
|
|
|
|
if enable_onvif or enable_rtmp or enable_rtsp:
|
|
|
|
if not await self._api.set_net_port(
|
|
|
|
enable_onvif=enable_onvif,
|
|
|
|
enable_rtmp=enable_rtmp,
|
|
|
|
enable_rtsp=enable_rtsp,
|
|
|
|
):
|
|
|
|
if enable_onvif:
|
|
|
|
_LOGGER.error(
|
2023-01-01 22:32:17 +00:00
|
|
|
"Failed to enable ONVIF on %s. Set it to ON to receive notifications",
|
2022-12-27 20:15:53 +00:00
|
|
|
self._api.nvr_name,
|
|
|
|
)
|
|
|
|
|
|
|
|
if enable_rtmp:
|
|
|
|
_LOGGER.error(
|
2023-01-01 22:32:17 +00:00
|
|
|
"Failed to enable RTMP on %s. Set it to ON",
|
2022-12-27 20:15:53 +00:00
|
|
|
self._api.nvr_name,
|
|
|
|
)
|
|
|
|
elif enable_rtsp:
|
|
|
|
_LOGGER.error(
|
2023-01-01 22:32:17 +00:00
|
|
|
"Failed to enable RTSP on %s. Set it to ON",
|
2022-12-27 20:15:53 +00:00
|
|
|
self._api.nvr_name,
|
|
|
|
)
|
|
|
|
|
2023-01-01 22:32:17 +00:00
|
|
|
self._unique_id = format_mac(self._api.mac_address)
|
2022-12-27 20:15:53 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def update_states(self) -> bool:
|
|
|
|
"""Call the API of the camera device to update the states."""
|
|
|
|
return await self._api.get_states()
|
|
|
|
|
|
|
|
async def disconnect(self):
|
|
|
|
"""Disconnect from the API, so the connection will be released."""
|
|
|
|
await self._api.unsubscribe_all()
|
|
|
|
|
|
|
|
try:
|
|
|
|
await self._api.logout()
|
|
|
|
except aiohttp.ClientConnectorError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Reolink connection error while logging out for host %s:%s: %s",
|
|
|
|
self._api.host,
|
|
|
|
self._api.port,
|
|
|
|
str(err),
|
|
|
|
)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Reolink connection timeout while logging out for host %s:%s",
|
|
|
|
self._api.host,
|
|
|
|
self._api.port,
|
|
|
|
)
|
|
|
|
except ApiError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Reolink API error while logging out for host %s:%s: %s",
|
|
|
|
self._api.host,
|
|
|
|
self._api.port,
|
|
|
|
str(err),
|
|
|
|
)
|
|
|
|
except CredentialsInvalidError:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Reolink credentials error while logging out for host %s:%s",
|
|
|
|
self._api.host,
|
|
|
|
self._api.port,
|
|
|
|
)
|
|
|
|
except InvalidContentTypeError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Reolink content type error while logging out for host %s:%s: %s",
|
|
|
|
self._api.host,
|
|
|
|
self._api.port,
|
|
|
|
str(err),
|
|
|
|
)
|
|
|
|
|
|
|
|
async def stop(self, event=None):
|
|
|
|
"""Disconnect the API."""
|
|
|
|
await self.disconnect()
|