diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 44bac9e3bde..04d8d159541 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -2,6 +2,7 @@ from __future__ import annotations from datetime import timedelta +import logging from typing import Final from aiohttp.web import Request, StreamResponse @@ -23,7 +24,7 @@ from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.util import Throttle +from homeassistant.util import dt as dt_util from .const import ( CONF_FFMPEG_ARGUMENTS, @@ -34,7 +35,7 @@ from .const import ( ) from .coordinator import CanaryDataUpdateCoordinator -MIN_TIME_BETWEEN_SESSION_RENEW: Final = timedelta(seconds=90) +FORCE_CAMERA_REFRESH_INTERVAL: Final = timedelta(minutes=15) PLATFORM_SCHEMA: Final = vol.All( cv.deprecated(CONF_FFMPEG_ARGUMENTS), @@ -47,6 +48,8 @@ PLATFORM_SCHEMA: Final = vol.All( ), ) +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistant, @@ -105,6 +108,11 @@ class CanaryCamera(CoordinatorEntity[CanaryDataUpdateCoordinator], Camera): model=device.device_type["name"], name=device.name, ) + self._image: bytes | None = None + self._expires_at = dt_util.utcnow() + _LOGGER.debug( + "%s %s has been initialized", self.name, device.device_type["name"] + ) @property def location(self) -> Location: @@ -125,17 +133,33 @@ class CanaryCamera(CoordinatorEntity[CanaryDataUpdateCoordinator], Camera): self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return a still image response from the camera.""" - await self.hass.async_add_executor_job(self.renew_live_stream_session) - live_stream_url = await self.hass.async_add_executor_job( - getattr, self._live_stream_session, "live_stream_url" - ) - return await ffmpeg.async_get_image( - self.hass, - live_stream_url, - extra_cmd=self._ffmpeg_arguments, - width=width, - height=height, - ) + utcnow = dt_util.utcnow() + if self._expires_at <= utcnow: + _LOGGER.debug("Grabbing a live view image from %s", self.name) + await self.hass.async_add_executor_job(self.renew_live_stream_session) + + if (live_stream_session := self._live_stream_session) is None: + return None + + if not (live_stream_url := live_stream_session.live_stream_url): + return None + + image = await ffmpeg.async_get_image( + self.hass, + live_stream_url, + extra_cmd=self._ffmpeg_arguments, + width=width, + height=height, + ) + + if image: + self._image = image + self._expires_at = FORCE_CAMERA_REFRESH_INTERVAL + utcnow + _LOGGER.debug("Grabbed a live view image from %s", self.name) + await self.hass.async_add_executor_job(live_stream_session.stop_session) + _LOGGER.debug("Stopped live session from %s", self.name) + + return self._image async def handle_async_mjpeg_stream( self, request: Request @@ -161,9 +185,14 @@ class CanaryCamera(CoordinatorEntity[CanaryDataUpdateCoordinator], Camera): finally: await stream.close() - @Throttle(MIN_TIME_BETWEEN_SESSION_RENEW) def renew_live_stream_session(self) -> None: """Renew live stream session.""" self._live_stream_session = self.coordinator.canary.get_live_stream_session( self._device ) + + _LOGGER.debug( + "Live Stream URL for %s is %s", + self.name, + self._live_stream_session.live_stream_url, + )