diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index 089da5df30..f83a117510 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -105,6 +105,8 @@ class Dispatcher: self.web_push_client = next( (comm for comm in communicators if isinstance(comm, WebPushClient)), None ) + if self.web_push_client is not None: + self.web_push_client.set_suspension_broadcaster(self.publish) def _receive(self, topic: str, payload: Any) -> Any | None: """Handle receiving of payload from communicators.""" diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index b548446c26..6f2d9ab8da 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -55,6 +55,7 @@ class WebPushClient(Communicator): c.name: 0 # type: ignore[misc] for c in self.config.cameras.values() } + self.suspension_broadcaster: Callable[[str, Any, bool], None] | None = None self.last_camera_notification_time: dict[str, float] = { c.name: 0 # type: ignore[misc] for c in self.config.cameras.values() @@ -66,6 +67,10 @@ class WebPushClient(Communicator): target=self._process_notifications, daemon=True ) self.notification_thread.start() + self.suspension_thread = threading.Thread( + target=self._process_suspensions, daemon=True + ) + self.suspension_thread.start() if not self.config.notifications.email: logger.warning("Email must be provided for push notifications to be sent.") @@ -164,6 +169,27 @@ class WebPushClient(Communicator): def is_camera_suspended(self, camera: str) -> bool: return datetime.datetime.now().timestamp() <= self.suspended_cameras[camera] + def set_suspension_broadcaster( + self, broadcaster: Callable[[str, Any, bool], None] + ) -> None: + """Register the callback used to broadcast suspension state changes.""" + self.suspension_broadcaster = broadcaster + + def _process_suspensions(self) -> None: + while not self.stop_event.wait(1): + self._clear_expired_suspensions() + + def _clear_expired_suspensions(self) -> None: + """Reset and broadcast cameras whose suspension window has elapsed.""" + now = datetime.datetime.now().timestamp() + for camera, suspended_until in list(self.suspended_cameras.items()): + if suspended_until and now > suspended_until: + self.unsuspend_notifications(camera) + if self.suspension_broadcaster is not None: + self.suspension_broadcaster( + f"{camera}/notifications/suspended", "0", True + ) + def publish(self, topic: str, payload: Any, retain: bool = False) -> None: """Wrapper for publishing when client is in valid state.""" # check for updated global config (notifications, auth)