Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make notifications toggleable via MQTT #13657

Merged
merged 3 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading