Cache Canary camera image (#73923)

* Cache camera image

Cache camera image so a new image isn't generated each call.
Adds debug logging

* Apply suggestions from code review

code compression with walrus operator

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* fix after walrus operator suggested tweak

fully use the live_stream_session variable in async_camera_image

* Invalidate cached image after 15 minutes

requested code change; invalidate cached image

* Removed unnecessary if statement

based on code review

* Image capture flow updates

now sets the image expiration upon getting an updated image
updates the cache image only when a new image is captured

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
0bmay 2022-07-11 12:53:33 -07:00 committed by GitHub
parent 5774f2e7b9
commit 2d2fd3e48f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

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