Skip to content

Commit

Permalink
Make notifications toggleable via MQTT (#13657)
Browse files Browse the repository at this point in the history
* Add ability to toggle mqtt state from MQTT / ws

* Listen to notification config updates

* Add docs for notifications
  • Loading branch information
NickM-27 authored Sep 10, 2024
1 parent 8db9824 commit 07d1692
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 4 deletions.
8 changes: 8 additions & 0 deletions docs/docs/integrations/mqtt.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<camera_name>/<object_name>`
Expand Down
2 changes: 1 addition & 1 deletion frigate/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
25 changes: 22 additions & 3 deletions frigate/comms/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions frigate/comms/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
)
Expand Down
13 changes: 13 additions & 0 deletions frigate/comms/webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -101,6 +105,15 @@ 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."""
# 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

if topic == "reviews":
self.send_alert(json.loads(payload))

Expand Down
6 changes: 6 additions & 0 deletions frigate/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 07d1692

Please sign in to comment.