mirror of
https://github.com/home-assistant/core.git
synced 2026-05-29 19:57:40 +01:00
ef887e529f
* Fix onvif cameras that cannot parse relative time The spec requires that the camera can parse relative or absolute timestamps However there are many cameras that cannot parse time correctly. Much of the event code has been offloaded to the library and support to determine if the camera has broken time and switch to absolute timestamps is now built into the library * adjust verison * fixes * bump * bump * bump * more fixes * preen * fix resume * one more fix * fix race in webhook setup * bump to 3.1.3 which has more fixes for broken camera firmwares * bump 3.1.4 for more fixes * fix * fix comment * bump * fix url limit * bump * more fixes * old hik uses -s
149 lines
5.1 KiB
Python
149 lines
5.1 KiB
Python
"""The ONVIF integration."""
|
|
import asyncio
|
|
from http import HTTPStatus
|
|
import logging
|
|
|
|
from httpx import RequestError
|
|
from onvif.exceptions import ONVIFAuthError, ONVIFError, ONVIFTimeoutError
|
|
from onvif.util import is_auth_error, stringify_onvif_error
|
|
from zeep.exceptions import Fault, TransportError
|
|
|
|
from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS
|
|
from homeassistant.components.stream import CONF_RTSP_TRANSPORT, RTSP_TRANSPORTS
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import (
|
|
EVENT_HOMEASSISTANT_STOP,
|
|
HTTP_BASIC_AUTHENTICATION,
|
|
HTTP_DIGEST_AUTHENTICATION,
|
|
Platform,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
|
|
|
from .const import CONF_SNAPSHOT_AUTH, DEFAULT_ARGUMENTS, DOMAIN
|
|
from .device import ONVIFDevice
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Set up ONVIF from a config entry."""
|
|
if DOMAIN not in hass.data:
|
|
hass.data[DOMAIN] = {}
|
|
|
|
if not entry.options:
|
|
await async_populate_options(hass, entry)
|
|
|
|
device = ONVIFDevice(hass, entry)
|
|
|
|
try:
|
|
await device.async_setup()
|
|
if not entry.data.get(CONF_SNAPSHOT_AUTH):
|
|
await async_populate_snapshot_auth(hass, device, entry)
|
|
except RequestError as err:
|
|
await device.device.close()
|
|
raise ConfigEntryNotReady(
|
|
f"Could not connect to camera {device.device.host}:{device.device.port}: {err}"
|
|
) from err
|
|
except Fault as err:
|
|
await device.device.close()
|
|
if is_auth_error(err):
|
|
raise ConfigEntryAuthFailed(
|
|
f"Auth Failed: {stringify_onvif_error(err)}"
|
|
) from err
|
|
raise ConfigEntryNotReady(
|
|
f"Could not connect to camera: {stringify_onvif_error(err)}"
|
|
) from err
|
|
except ONVIFError as err:
|
|
await device.device.close()
|
|
raise ConfigEntryNotReady(
|
|
f"Could not setup camera {device.device.host}:{device.device.port}: {stringify_onvif_error(err)}"
|
|
) from err
|
|
except TransportError as err:
|
|
await device.device.close()
|
|
stringified_onvif_error = stringify_onvif_error(err)
|
|
if err.status_code in (
|
|
HTTPStatus.UNAUTHORIZED.value,
|
|
HTTPStatus.FORBIDDEN.value,
|
|
):
|
|
raise ConfigEntryAuthFailed(
|
|
f"Auth Failed: {stringified_onvif_error}"
|
|
) from err
|
|
raise ConfigEntryNotReady(
|
|
f"Could not setup camera {device.device.host}:{device.device.port}: {stringified_onvif_error}"
|
|
) from err
|
|
except asyncio.CancelledError as err:
|
|
# After https://github.com/agronholm/anyio/issues/374 is resolved
|
|
# this may be able to be removed
|
|
await device.device.close()
|
|
raise ConfigEntryNotReady(f"Setup was unexpectedly canceled: {err}") from err
|
|
|
|
if not device.available:
|
|
raise ConfigEntryNotReady()
|
|
|
|
hass.data[DOMAIN][entry.unique_id] = device
|
|
|
|
device.platforms = [Platform.BUTTON, Platform.CAMERA]
|
|
|
|
if device.capabilities.events:
|
|
device.platforms += [Platform.BINARY_SENSOR, Platform.SENSOR]
|
|
|
|
if device.capabilities.imaging:
|
|
device.platforms += [Platform.SWITCH]
|
|
|
|
await hass.config_entries.async_forward_entry_setups(entry, device.platforms)
|
|
|
|
entry.async_on_unload(
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.async_stop)
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Unload a config entry."""
|
|
|
|
device: ONVIFDevice = hass.data[DOMAIN][entry.unique_id]
|
|
|
|
if device.capabilities.events and device.events.started:
|
|
try:
|
|
await device.events.async_stop()
|
|
except (ONVIFError, Fault, RequestError, TransportError):
|
|
LOGGER.warning("Error while stopping events: %s", device.name)
|
|
|
|
return await hass.config_entries.async_unload_platforms(entry, device.platforms)
|
|
|
|
|
|
async def _get_snapshot_auth(device):
|
|
"""Determine auth type for snapshots."""
|
|
if not device.capabilities.snapshot or not (device.username and device.password):
|
|
return HTTP_DIGEST_AUTHENTICATION
|
|
|
|
try:
|
|
snapshot = await device.device.get_snapshot(device.profiles[0].token)
|
|
|
|
if snapshot:
|
|
return HTTP_DIGEST_AUTHENTICATION
|
|
return HTTP_BASIC_AUTHENTICATION
|
|
except (ONVIFAuthError, ONVIFTimeoutError):
|
|
return HTTP_BASIC_AUTHENTICATION
|
|
except ONVIFError:
|
|
return HTTP_DIGEST_AUTHENTICATION
|
|
|
|
|
|
async def async_populate_snapshot_auth(hass, device, entry):
|
|
"""Check if digest auth for snapshots is possible."""
|
|
auth = await _get_snapshot_auth(device)
|
|
new_data = {**entry.data, CONF_SNAPSHOT_AUTH: auth}
|
|
hass.config_entries.async_update_entry(entry, data=new_data)
|
|
|
|
|
|
async def async_populate_options(hass, entry):
|
|
"""Populate default options for device."""
|
|
options = {
|
|
CONF_EXTRA_ARGUMENTS: DEFAULT_ARGUMENTS,
|
|
CONF_RTSP_TRANSPORT: next(iter(RTSP_TRANSPORTS)),
|
|
}
|
|
|
|
hass.config_entries.async_update_entry(entry, options=options)
|