From db5426570d3e1d09877ee1a93a822ee607b21e3a Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 10 Sep 2024 10:48:50 -0600 Subject: [PATCH 1/3] Add ability to toggle mqtt state from MQTT / ws --- frigate/app.py | 2 +- frigate/comms/dispatcher.py | 25 ++++++++++++++++++++++--- frigate/comms/mqtt.py | 13 +++++++++++++ frigate/comms/webpush.py | 5 +++++ frigate/config.py | 6 ++++++ 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/frigate/app.py b/frigate/app.py index 68e5c872cb..bad2d92812 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -406,7 +406,7 @@ def init_dispatcher(self) -> None: if self.config.mqtt.enabled: comms.append(MqttClient(self.config)) - if self.config.notifications.enabled: + if self.config.notifications.enabled_in_config: comms.append(WebPushClient(self.config)) comms.append(WebSocketClient(self.config)) diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index 8e176657da..3d8628dfe8 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -75,6 +75,9 @@ def __init__( "birdseye": self._on_birdseye_command, "birdseye_mode": self._on_birdseye_mode_command, } + self._global_settings_handlers: dict[str, Callable] = { + "notifications": self._on_notification_command, + } for comm in self.comms: comm.subscribe(self._receive) @@ -86,9 +89,13 @@ def _receive(self, topic: str, payload: str) -> Optional[Any]: if topic.endswith("set"): try: # example /cam_name/detect/set payload=ON|OFF - camera_name = topic.split("/")[-3] - command = topic.split("/")[-2] - self._camera_settings_handlers[command](camera_name, payload) + if topic.count("/") == 2: + camera_name = topic.split("/")[-3] + command = topic.split("/")[-2] + self._camera_settings_handlers[command](camera_name, payload) + elif topic.count("/") == 1: + command = topic.split("/")[-2] + self._global_settings_handlers[command](payload) except IndexError: logger.error(f"Received invalid set command: {topic}") return @@ -282,6 +289,18 @@ def _on_motion_threshold_command(self, camera_name: str, payload: int) -> None: self.config_updater.publish(f"config/motion/{camera_name}", motion_settings) self.publish(f"{camera_name}/motion_threshold/state", payload, retain=True) + def _on_notification_command(self, payload: str) -> None: + """Callback for notification topic.""" + if payload != "ON" and payload != "OFF": + f"Received unsupported value for notification: {payload}" + return + + notification_settings = self.config.notifications + logger.info(f"Setting notifications: {payload}") + notification_settings.enabled = payload == "ON" # type: ignore[union-attr] + self.config_updater.publish("config/notifications", notification_settings) + self.publish("notifications/state", payload, retain=True) + def _on_audio_command(self, camera_name: str, payload: str) -> None: """Callback for audio topic.""" audio_settings = self.config.cameras[camera_name].audio diff --git a/frigate/comms/mqtt.py b/frigate/comms/mqtt.py index b83484d417..ff1223d48a 100644 --- a/frigate/comms/mqtt.py +++ b/frigate/comms/mqtt.py @@ -105,6 +105,13 @@ def _set_initial_topics(self) -> None: retain=True, ) + if self.config.notifications.enabled_in_config: + self.publish( + "notifications/state", + "ON" if self.config.notifications.enabled else "OFF", + retain=True, + ) + self.publish("available", "online", retain=True) def on_mqtt_command( @@ -209,6 +216,12 @@ def _start(self) -> None: self.on_mqtt_command, ) + if self.config.notifications.enabled_in_config: + self.client.message_callback_add( + f"{self.mqtt_config.topic_prefix}/notifications/set", + self.on_mqtt_command, + ) + self.client.message_callback_add( f"{self.mqtt_config.topic_prefix}/restart", self.on_mqtt_command ) diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index f26d4e6a9f..3dd00fd6f2 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -101,6 +101,11 @@ def cleanup_registrations(self) -> None: def publish(self, topic: str, payload: Any, retain: bool = False) -> None: """Wrapper for publishing when client is in valid state.""" + ## TODO update notification config + + if not self.config.notifications.enabled: + return + if topic == "reviews": self.send_alert(json.loads(payload)) diff --git a/frigate/config.py b/frigate/config.py index ef3375ad0f..972562328e 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -172,6 +172,9 @@ class AuthConfig(FrigateBaseModel): class NotificationConfig(FrigateBaseModel): enabled: bool = Field(default=False, title="Enable notifications") email: Optional[str] = Field(default=None, title="Email required for push.") + enabled_in_config: Optional[bool] = Field( + default=None, title="Keep track of original state of notifications." + ) class StatsConfig(FrigateBaseModel): @@ -1459,6 +1462,9 @@ def runtime_config(self, plus_api: PlusApi = None) -> FrigateConfig: config.mqtt.user = config.mqtt.user.format(**FRIGATE_ENV_VARS) config.mqtt.password = config.mqtt.password.format(**FRIGATE_ENV_VARS) + # set notifications state + config.notifications.enabled_in_config = config.notifications.enabled + # GenAI substitution if config.genai.api_key: config.genai.api_key = config.genai.api_key.format(**FRIGATE_ENV_VARS) From d9f68db4047fd34a8e70be84edd8a5757d1c2990 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 10 Sep 2024 11:00:15 -0600 Subject: [PATCH 2/3] Listen to notification config updates --- frigate/comms/webpush.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 3dd00fd6f2..0cb75f3325 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -9,6 +9,7 @@ from py_vapid import Vapid01 from pywebpush import WebPusher +from frigate.comms.config_updater import ConfigSubscriber from frigate.comms.dispatcher import Communicator from frigate.config import FrigateConfig from frigate.const import CONFIG_DIR @@ -41,6 +42,9 @@ def __init__(self, config: FrigateConfig) -> None: for sub in user["notification_tokens"]: self.web_pushers[user["username"]].append(WebPusher(sub)) + # notification config updater + self.config_subscriber = ConfigSubscriber("config/notifications") + def subscribe(self, receiver: Callable) -> None: """Wrapper for allowing dispatcher to subscribe.""" pass @@ -101,7 +105,11 @@ def cleanup_registrations(self) -> None: def publish(self, topic: str, payload: Any, retain: bool = False) -> None: """Wrapper for publishing when client is in valid state.""" - ## TODO update notification config + # check for updated notification config + _, updated_notif_config = self.config_subscriber.check_for_update() + + if updated_notif_config: + self.config.notifications = updated_notif_config if not self.config.notifications.enabled: return From b897e50d0f8b5a577cbbd5d030b671b8f468798c Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 10 Sep 2024 11:23:04 -0600 Subject: [PATCH 3/3] Add docs for notifications --- docs/docs/integrations/mqtt.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/integrations/mqtt.md b/docs/docs/integrations/mqtt.md index 0f3dab2e9c..4c908a21ae 100644 --- a/docs/docs/integrations/mqtt.md +++ b/docs/docs/integrations/mqtt.md @@ -154,6 +154,14 @@ Message published for each changed review item. The first message is published w Same data available at `/api/stats` published at a configurable interval. +### `frigate/notifications/set` + +Topic to turn notifications on and off. Expected values are `ON` and `OFF`. + +### `frigate/notifications/state` + +Topic with current state of notifications. Published values are `ON` and `OFF`. + ## Frigate Camera Topics ### `frigate//`