mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-26 13:47:03 +02:00
Make notifications toggleable via MQTT (#13657)
* Add ability to toggle mqtt state from MQTT / ws * Listen to notification config updates * Add docs for notifications
This commit is contained in:
parent
8db9824842
commit
07d1692f2b
@ -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.
|
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 Topics
|
||||||
|
|
||||||
### `frigate/<camera_name>/<object_name>`
|
### `frigate/<camera_name>/<object_name>`
|
||||||
|
@ -406,7 +406,7 @@ class FrigateApp:
|
|||||||
if self.config.mqtt.enabled:
|
if self.config.mqtt.enabled:
|
||||||
comms.append(MqttClient(self.config))
|
comms.append(MqttClient(self.config))
|
||||||
|
|
||||||
if self.config.notifications.enabled:
|
if self.config.notifications.enabled_in_config:
|
||||||
comms.append(WebPushClient(self.config))
|
comms.append(WebPushClient(self.config))
|
||||||
|
|
||||||
comms.append(WebSocketClient(self.config))
|
comms.append(WebSocketClient(self.config))
|
||||||
|
@ -75,6 +75,9 @@ class Dispatcher:
|
|||||||
"birdseye": self._on_birdseye_command,
|
"birdseye": self._on_birdseye_command,
|
||||||
"birdseye_mode": self._on_birdseye_mode_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:
|
for comm in self.comms:
|
||||||
comm.subscribe(self._receive)
|
comm.subscribe(self._receive)
|
||||||
@ -86,9 +89,13 @@ class Dispatcher:
|
|||||||
if topic.endswith("set"):
|
if topic.endswith("set"):
|
||||||
try:
|
try:
|
||||||
# example /cam_name/detect/set payload=ON|OFF
|
# example /cam_name/detect/set payload=ON|OFF
|
||||||
camera_name = topic.split("/")[-3]
|
if topic.count("/") == 2:
|
||||||
command = topic.split("/")[-2]
|
camera_name = topic.split("/")[-3]
|
||||||
self._camera_settings_handlers[command](camera_name, payload)
|
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:
|
except IndexError:
|
||||||
logger.error(f"Received invalid set command: {topic}")
|
logger.error(f"Received invalid set command: {topic}")
|
||||||
return
|
return
|
||||||
@ -282,6 +289,18 @@ class Dispatcher:
|
|||||||
self.config_updater.publish(f"config/motion/{camera_name}", motion_settings)
|
self.config_updater.publish(f"config/motion/{camera_name}", motion_settings)
|
||||||
self.publish(f"{camera_name}/motion_threshold/state", payload, retain=True)
|
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:
|
def _on_audio_command(self, camera_name: str, payload: str) -> None:
|
||||||
"""Callback for audio topic."""
|
"""Callback for audio topic."""
|
||||||
audio_settings = self.config.cameras[camera_name].audio
|
audio_settings = self.config.cameras[camera_name].audio
|
||||||
|
@ -105,6 +105,13 @@ class MqttClient(Communicator): # type: ignore[misc]
|
|||||||
retain=True,
|
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)
|
self.publish("available", "online", retain=True)
|
||||||
|
|
||||||
def on_mqtt_command(
|
def on_mqtt_command(
|
||||||
@ -209,6 +216,12 @@ class MqttClient(Communicator): # type: ignore[misc]
|
|||||||
self.on_mqtt_command,
|
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(
|
self.client.message_callback_add(
|
||||||
f"{self.mqtt_config.topic_prefix}/restart", self.on_mqtt_command
|
f"{self.mqtt_config.topic_prefix}/restart", self.on_mqtt_command
|
||||||
)
|
)
|
||||||
|
@ -9,6 +9,7 @@ from typing import Any, Callable
|
|||||||
from py_vapid import Vapid01
|
from py_vapid import Vapid01
|
||||||
from pywebpush import WebPusher
|
from pywebpush import WebPusher
|
||||||
|
|
||||||
|
from frigate.comms.config_updater import ConfigSubscriber
|
||||||
from frigate.comms.dispatcher import Communicator
|
from frigate.comms.dispatcher import Communicator
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.const import CONFIG_DIR
|
from frigate.const import CONFIG_DIR
|
||||||
@ -41,6 +42,9 @@ class WebPushClient(Communicator): # type: ignore[misc]
|
|||||||
for sub in user["notification_tokens"]:
|
for sub in user["notification_tokens"]:
|
||||||
self.web_pushers[user["username"]].append(WebPusher(sub))
|
self.web_pushers[user["username"]].append(WebPusher(sub))
|
||||||
|
|
||||||
|
# notification config updater
|
||||||
|
self.config_subscriber = ConfigSubscriber("config/notifications")
|
||||||
|
|
||||||
def subscribe(self, receiver: Callable) -> None:
|
def subscribe(self, receiver: Callable) -> None:
|
||||||
"""Wrapper for allowing dispatcher to subscribe."""
|
"""Wrapper for allowing dispatcher to subscribe."""
|
||||||
pass
|
pass
|
||||||
@ -101,6 +105,15 @@ class WebPushClient(Communicator): # type: ignore[misc]
|
|||||||
|
|
||||||
def publish(self, topic: str, payload: Any, retain: bool = False) -> None:
|
def publish(self, topic: str, payload: Any, retain: bool = False) -> None:
|
||||||
"""Wrapper for publishing when client is in valid state."""
|
"""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":
|
if topic == "reviews":
|
||||||
self.send_alert(json.loads(payload))
|
self.send_alert(json.loads(payload))
|
||||||
|
|
||||||
|
@ -172,6 +172,9 @@ class AuthConfig(FrigateBaseModel):
|
|||||||
class NotificationConfig(FrigateBaseModel):
|
class NotificationConfig(FrigateBaseModel):
|
||||||
enabled: bool = Field(default=False, title="Enable notifications")
|
enabled: bool = Field(default=False, title="Enable notifications")
|
||||||
email: Optional[str] = Field(default=None, title="Email required for push.")
|
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):
|
class StatsConfig(FrigateBaseModel):
|
||||||
@ -1459,6 +1462,9 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
config.mqtt.user = config.mqtt.user.format(**FRIGATE_ENV_VARS)
|
config.mqtt.user = config.mqtt.user.format(**FRIGATE_ENV_VARS)
|
||||||
config.mqtt.password = config.mqtt.password.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
|
# GenAI substitution
|
||||||
if config.genai.api_key:
|
if config.genai.api_key:
|
||||||
config.genai.api_key = config.genai.api_key.format(**FRIGATE_ENV_VARS)
|
config.genai.api_key = config.genai.api_key.format(**FRIGATE_ENV_VARS)
|
||||||
|
Loading…
Reference in New Issue
Block a user