Ensure onvif retries setup if camera fails to respond correctly (#91866)

This commit is contained in:
J. Nick Koston 2023-04-22 18:27:54 -05:00 committed by GitHub
parent 6013584b7b
commit 82340907c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 27 additions and 10 deletions

View File

@ -31,6 +31,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try: try:
await device.async_setup() await device.async_setup()
if not entry.data.get(CONF_SNAPSHOT_AUTH):
await async_populate_snapshot_auth(hass, device, entry)
except RequestError as err: except RequestError as err:
await device.device.close() await device.device.close()
raise ConfigEntryNotReady( raise ConfigEntryNotReady(
@ -52,9 +54,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not device.available: if not device.available:
raise ConfigEntryNotReady() 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 hass.data[DOMAIN][entry.unique_id] = device
device.platforms = [Platform.BUTTON, Platform.CAMERA] device.platforms = [Platform.BUTTON, Platform.CAMERA]

View File

@ -1,6 +1,8 @@
"""Support for ONVIF Cameras with FFmpeg as decoder.""" """Support for ONVIF Cameras with FFmpeg as decoder."""
from __future__ import annotations from __future__ import annotations
import asyncio
from haffmpeg.camera import CameraMjpeg from haffmpeg.camera import CameraMjpeg
from onvif.exceptions import ONVIFError from onvif.exceptions import ONVIFError
import voluptuous as vol import voluptuous as vol
@ -110,6 +112,7 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
== HTTP_BASIC_AUTHENTICATION == HTTP_BASIC_AUTHENTICATION
) )
self._stream_uri: str | None = None self._stream_uri: str | None = None
self._stream_uri_future: asyncio.Future[str] | None = None
@property @property
def name(self) -> str: def name(self) -> str:
@ -130,7 +133,7 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
async def stream_source(self): async def stream_source(self):
"""Return the stream source.""" """Return the stream source."""
return self._stream_uri return await self._async_get_stream_uri()
async def async_camera_image( async def async_camera_image(
self, width: int | None = None, height: int | None = None self, width: int | None = None, height: int | None = None
@ -158,10 +161,10 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
self.device.name, self.device.name,
) )
assert self._stream_uri stream_uri = await self._async_get_stream_uri()
return await ffmpeg.async_get_image( return await ffmpeg.async_get_image(
self.hass, self.hass,
self._stream_uri, stream_uri,
extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS), extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS),
width=width, width=width,
height=height, height=height,
@ -173,9 +176,10 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
ffmpeg_manager = get_ffmpeg_manager(self.hass) ffmpeg_manager = get_ffmpeg_manager(self.hass)
stream = CameraMjpeg(ffmpeg_manager.binary) stream = CameraMjpeg(ffmpeg_manager.binary)
stream_uri = await self._async_get_stream_uri()
await stream.open_camera( await stream.open_camera(
self._stream_uri, stream_uri,
extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS), extra_cmd=self.device.config_entry.options.get(CONF_EXTRA_ARGUMENTS),
) )
@ -190,13 +194,27 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera):
finally: finally:
await stream.close() await stream.close()
async def async_added_to_hass(self) -> None: async def _async_get_stream_uri(self) -> str:
"""Run when entity about to be added to hass.""" """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) 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(uri_no_auth)
url = url.with_user(self.device.username) url = url.with_user(self.device.username)
url = url.with_password(self.device.password) url = url.with_password(self.device.password)
self._stream_uri = str(url) self._stream_uri = str(url)
self._stream_uri_future.set_result(self._stream_uri)
return self._stream_uri
async def async_perform_ptz( async def async_perform_ptz(
self, self,