From 82340907c23d99db427d0d7f502d0a6cfb8be159 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 22 Apr 2023 18:27:54 -0500 Subject: [PATCH] Ensure onvif retries setup if camera fails to respond correctly (#91866) --- homeassistant/components/onvif/__init__.py | 5 ++-- homeassistant/components/onvif/camera.py | 32 +++++++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 45fd04049ad..545ebbed799 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -31,6 +31,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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( @@ -52,9 +54,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not device.available: raise ConfigEntryNotReady() - if not entry.data.get(CONF_SNAPSHOT_AUTH): - await async_populate_snapshot_auth(hass, device, entry) - hass.data[DOMAIN][entry.unique_id] = device device.platforms = [Platform.BUTTON, Platform.CAMERA] diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 11699731b2f..6dd489b12f9 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -1,6 +1,8 @@ """Support for ONVIF Cameras with FFmpeg as decoder.""" from __future__ import annotations +import asyncio + from haffmpeg.camera import CameraMjpeg from onvif.exceptions import ONVIFError import voluptuous as vol @@ -110,6 +112,7 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): == HTTP_BASIC_AUTHENTICATION ) self._stream_uri: str | None = None + self._stream_uri_future: asyncio.Future[str] | None = None @property def name(self) -> str: @@ -130,7 +133,7 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): async def stream_source(self): """Return the stream source.""" - return self._stream_uri + return await self._async_get_stream_uri() async def async_camera_image( self, width: int | None = None, height: int | None = None @@ -158,10 +161,10 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): self.device.name, ) - assert self._stream_uri + stream_uri = await self._async_get_stream_uri() return await ffmpeg.async_get_image( self.hass, - self._stream_uri, + stream_uri, extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS), width=width, height=height, @@ -173,9 +176,10 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): ffmpeg_manager = get_ffmpeg_manager(self.hass) stream = CameraMjpeg(ffmpeg_manager.binary) + stream_uri = await self._async_get_stream_uri() await stream.open_camera( - self._stream_uri, + stream_uri, extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS), ) @@ -190,13 +194,27 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): finally: await stream.close() - async def async_added_to_hass(self) -> None: - """Run when entity about to be added to hass.""" - uri_no_auth = await self.device.async_get_stream_uri(self.profile) + async def _async_get_stream_uri(self) -> str: + """Return the stream URI.""" + if self._stream_uri: + return self._stream_uri + if self._stream_uri_future: + return await self._stream_uri_future + loop = asyncio.get_running_loop() + self._stream_uri_future = loop.create_future() + try: + uri_no_auth = await self.device.async_get_stream_uri(self.profile) + except (asyncio.TimeoutError, Exception) as err: # pylint: disable=broad-except + LOGGER.error("Failed to get stream uri: %s", err) + if self._stream_uri_future: + self._stream_uri_future.set_exception(err) + raise url = URL(uri_no_auth) url = url.with_user(self.device.username) url = url.with_password(self.device.password) self._stream_uri = str(url) + self._stream_uri_future.set_result(self._stream_uri) + return self._stream_uri async def async_perform_ptz( self,