From e300908a8e732e4a9ec3f4a7245bfe4570e8c737 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Wed, 18 May 2022 19:59:35 +0200 Subject: [PATCH] Refresh camera stream source of Synology DSM connected cameras (#70938) Co-authored-by: Paulus Schoutsen --- .../components/synology_dsm/__init__.py | 24 +++++++++++++--- .../components/synology_dsm/camera.py | 28 ++++++++++++++++++- .../components/synology_dsm/const.py | 3 ++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 8dbdecb9305..ece38bf7326 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -23,6 +23,7 @@ from homeassistant.const import CONF_MAC, CONF_SCAN_INTERVAL, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .common import SynoApi @@ -36,6 +37,7 @@ from .const import ( EXCEPTION_DETAILS, EXCEPTION_UNKNOWN, PLATFORMS, + SIGNAL_CAMERA_SOURCE_CHANGED, SYNO_API, SYSTEM_LOADED, UNDO_UPDATE_LISTENER, @@ -123,6 +125,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return None surveillance_station = api.surveillance_station + current_data: dict[str, SynoCamera] = { + camera.id: camera for camera in surveillance_station.get_all_cameras() + } try: async with async_timeout.timeout(30): @@ -130,12 +135,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except SynologyDSMAPIErrorException as err: raise UpdateFailed(f"Error communicating with API: {err}") from err - return { - "cameras": { - camera.id: camera for camera in surveillance_station.get_all_cameras() - } + new_data: dict[str, SynoCamera] = { + camera.id: camera for camera in surveillance_station.get_all_cameras() } + for cam_id, cam_data_new in new_data.items(): + if ( + (cam_data_current := current_data.get(cam_id)) is not None + and cam_data_current.live_view.rtsp != cam_data_new.live_view.rtsp + ): + async_dispatcher_send( + hass, + f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{entry.entry_id}_{cam_id}", + cam_data_new.live_view.rtsp, + ) + + return {"cameras": new_data} + async def async_coordinator_update_data_central() -> None: """Fetch all device and sensor data from api.""" try: diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index c6d44d8883d..cab2536187c 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -16,7 +16,8 @@ from homeassistant.components.camera import ( CameraEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -27,6 +28,7 @@ from .const import ( COORDINATOR_CAMERAS, DEFAULT_SNAPSHOT_QUALITY, DOMAIN, + SIGNAL_CAMERA_SOURCE_CHANGED, SYNO_API, ) from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription @@ -130,6 +132,29 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera): """Return the camera motion detection status.""" return self.camera_data.is_motion_detection_enabled # type: ignore[no-any-return] + def _listen_source_updates(self) -> None: + """Listen for camera source changed events.""" + + @callback + def _handle_signal(url: str) -> None: + if self.stream: + _LOGGER.debug("Update stream URL for camera %s", self.camera_data.name) + self.stream.update_source(url) + + assert self.platform + assert self.platform.config_entry + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{SIGNAL_CAMERA_SOURCE_CHANGED}_{self.platform.config_entry.entry_id}_{self.camera_data.id}", + _handle_signal, + ) + ) + + async def async_added_to_hass(self) -> None: + """Subscribe to signal.""" + self._listen_source_updates() + def camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: @@ -162,6 +187,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity, Camera): ) if not self.available: return None + return self.camera_data.live_view.rtsp # type: ignore[no-any-return] def enable_motion_detection(self) -> None: diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 1b4e5f0bb36..f716130a5e4 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -43,6 +43,9 @@ DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED ENTITY_UNIT_LOAD = "load" +# Signals +SIGNAL_CAMERA_SOURCE_CHANGED = "synology_dsm.camera_stream_source_changed" + # Services SERVICE_REBOOT = "reboot" SERVICE_SHUTDOWN = "shutdown"