diff --git a/docs/docs/configuration/advanced/reference.md b/docs/docs/configuration/advanced/reference.md index 275b855de8..949847c482 100644 --- a/docs/docs/configuration/advanced/reference.md +++ b/docs/docs/configuration/advanced/reference.md @@ -578,6 +578,10 @@ notifications: email: "admin@example.com" # Optional: Cooldown time for notifications in seconds (default: shown below) cooldown: 0 + # Optional: Delivery of notifications sent while an alert is active (default: shown below) + # Options are immediate (deliver right away or drop) and reliable (retry for up to 30 seconds) + # NOTE: reliable can deliver late notifications that still link to the live view + ttl: immediate # Optional: Record configuration # NOTE: Can be overridden at the camera level diff --git a/docs/docs/configuration/notifications.md b/docs/docs/configuration/notifications.md index 67b4f2374d..85555f42ec 100644 --- a/docs/docs/configuration/notifications.md +++ b/docs/docs/configuration/notifications.md @@ -43,7 +43,7 @@ Notifications will be prevented if either: -1. Navigate to . +1. Navigate to . - Set **Email** to your email address - Enable notifications for the desired cameras @@ -108,6 +108,29 @@ Currently, only Chrome supports images in notifications. Safari and Firefox will Different platforms handle notifications differently, some settings changes may be required to get optimal notification delivery. +### Notification TTL + +By default, Frigate sends notifications for an active alert with a `ttl` of `immediate`, so the push service either delivers them immediately or drops them. If your device is briefly unreachable (asleep, switching from Wi-Fi to cellular, or on a weak signal), those notifications are lost and you only receive the notification sent when the alert ends, which is always kept for up to an hour. + +Setting `ttl` to `reliable` tells the push service to keep retrying these notifications for up to 30 seconds. This makes delivery more reliable, but a notification delivered late will still say an object is detected, even if the object has already left. + + + + +1. Navigate to . + - Expand **Advanced Settings** and set **Notification TTL** to **Reliable** + + + + +```yaml +notifications: + ttl: reliable # retry an undelivered notification for up to 30 seconds +``` + + + + ### Android Most Android phones have battery optimization settings. To get reliable Notification delivery the browser (Chrome, Firefox) should have battery optimizations disabled. If Frigate is running as a PWA then the Frigate app should have battery optimizations disabled as well. diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index f7267896ed..cdac3587b7 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -22,6 +22,7 @@ from frigate.comms.base_communicator import Communicator from frigate.comms.config_updater import ConfigSubscriber from frigate.config import FrigateConfig from frigate.config.auth import AuthConfig +from frigate.config.camera.notification import NOTIFICATION_TTL_SECONDS from frigate.config.camera.updater import ( CameraConfigUpdateEnum, CameraConfigUpdateSubscriber, @@ -623,7 +624,7 @@ class WebPushClient(Communicator): # if event is ongoing open to live view otherwise open to recordings view direct_url = f"/review?id={reviewId}" if ended else f"/#{camera}" - ttl = 3600 if ended else 0 + ttl = 3600 if ended else NOTIFICATION_TTL_SECONDS[self.config.notifications.ttl] logger.debug(f"Sending push notification for {camera}, review ID {reviewId}") diff --git a/frigate/config/camera/notification.py b/frigate/config/camera/notification.py index 5fe6a57834..fd4a8b83db 100644 --- a/frigate/config/camera/notification.py +++ b/frigate/config/camera/notification.py @@ -1,10 +1,23 @@ +from enum import Enum + from pydantic import Field from frigate.util.env import EnvString from ..base import FrigateBaseModel -__all__ = ["NotificationConfig"] +__all__ = ["NotificationConfig", "NotificationTTLEnum", "NOTIFICATION_TTL_SECONDS"] + + +class NotificationTTLEnum(str, Enum): + immediate = "immediate" + reliable = "reliable" + + +NOTIFICATION_TTL_SECONDS = { + NotificationTTLEnum.immediate: 0, + NotificationTTLEnum.reliable: 30, +} class NotificationConfig(FrigateBaseModel): @@ -24,6 +37,11 @@ class NotificationConfig(FrigateBaseModel): title="Cooldown period", description="Cooldown (seconds) between notifications to avoid spamming recipients.", ) + ttl: NotificationTTLEnum = Field( + default=NotificationTTLEnum.immediate, + title="Notification TTL", + description="How the push service handles a notification sent while an alert is active if your device can't be reached. Immediate delivers it right away or drops it. Reliable keeps retrying for up to 30 seconds. The notification sent when an alert ends is always kept for up to an hour.", + ) enabled_in_config: bool | None = Field( default=None, title="Original notifications state", diff --git a/web/public/locales/en/config/cameras.json b/web/public/locales/en/config/cameras.json index 68eccf03c6..5f1accd456 100644 --- a/web/public/locales/en/config/cameras.json +++ b/web/public/locales/en/config/cameras.json @@ -858,6 +858,10 @@ "label": "Cooldown period", "description": "Cooldown (seconds) between notifications to avoid spamming recipients." }, + "ttl": { + "label": "Notification TTL", + "description": "How the push service handles a notification sent while an alert is active if your device can't be reached. Immediate delivers it right away or drops it. Reliable keeps retrying for up to 30 seconds. The notification sent when an alert ends is always kept for up to an hour." + }, "enabled_in_config": { "label": "Original notifications state", "description": "Indicates whether notifications were enabled in the original static configuration." diff --git a/web/public/locales/en/config/global.json b/web/public/locales/en/config/global.json index 40d4542c16..b9ba8f2044 100644 --- a/web/public/locales/en/config/global.json +++ b/web/public/locales/en/config/global.json @@ -154,6 +154,10 @@ "label": "Cooldown period", "description": "Cooldown (seconds) between notifications to avoid spamming recipients." }, + "ttl": { + "label": "Notification TTL", + "description": "How the push service handles a notification sent while an alert is active if your device can't be reached. Immediate delivers it right away or drops it. Reliable keeps retrying for up to 30 seconds. The notification sent when an alert ends is always kept for up to an hour." + }, "enabled_in_config": { "label": "Original notifications state", "description": "Indicates whether notifications were enabled in the original static configuration." diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 267c88f49f..c6e483f4ee 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -1934,6 +1934,10 @@ "saved": "Saved" } }, + "notificationTtl": { + "immediate": "Immediate", + "reliable": "Reliable" + }, "retainMode": { "all": "All", "motion": "Motion", @@ -2051,7 +2055,8 @@ "profileBaseDisabled": "Recording is disabled in this camera's base config, so enabling it in a profile has no effect. Enable recording in the base config and use a profile to disable it instead." }, "notifications": { - "profileBaseDisabled": "No cameras have notifications enabled in their base config, so enabling them in a profile has no effect. Enable notifications in the base config of at least one camera." + "profileBaseDisabled": "No cameras have notifications enabled in their base config, so enabling them in a profile has no effect. Enable notifications in the base config of at least one camera.", + "ttlDelay": "Notifications can arrive up to 30 seconds late and still link to the live view after the review item has ended." }, "birdseye": { "objectTrackingDetectDisabled": "Birdseye includes tracked objects, but object detection is disabled for this camera. The camera will not appear in Birdseye.", diff --git a/web/src/components/config-form/section-configs/notifications.ts b/web/src/components/config-form/section-configs/notifications.ts index 62adcab520..69f8313222 100644 --- a/web/src/components/config-form/section-configs/notifications.ts +++ b/web/src/components/config-form/section-configs/notifications.ts @@ -7,7 +7,7 @@ const notifications: SectionConfigOverrides = { fieldOrder: ["enabled", "email"], fieldGroups: {}, hiddenFields: ["enabled_in_config"], - advancedFields: [], + advancedFields: ["ttl"], fieldMessages: [ { key: "profile-base-notifications-disabled", @@ -24,6 +24,14 @@ const notifications: SectionConfigOverrides = { camera.notifications.enabled_in_config, ), }, + { + key: "notifications-ttl-delay", + field: "ttl", + messageKey: "configMessages.notifications.ttlDelay", + severity: "warning", + position: "after", + condition: (ctx) => ctx.formData?.ttl === "reliable", + }, ], }, global: { @@ -31,12 +39,13 @@ const notifications: SectionConfigOverrides = { "ui:before": { render: "NotificationsSettingsExtras" }, enabled: { "ui:widget": "hidden" }, email: { "ui:widget": "hidden" }, + ttl: { "ui:options": { enumI18nPrefix: "notificationTtl" } }, cooldown: { "ui:widget": "hidden" }, enabled_in_config: { "ui:widget": "hidden" }, }, }, camera: { - hiddenFields: ["enabled_in_config", "email"], + hiddenFields: ["enabled_in_config", "email", "ttl"], }, }; diff --git a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx index 98c2e9caaa..3db2a8203b 100644 --- a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx +++ b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx @@ -340,7 +340,7 @@ export default function NotificationsSettingsExtras({ set(nextNotifications, "enabled", desiredEnabled); const sanitizedNotifications = sanitizeSectionData( nextNotifications as ConfigSectionData, - ["enabled_in_config", "email"], + ["enabled_in_config", "email", "ttl"], ); onPendingDataChange("notifications", camera.name, sanitizedNotifications); pendingCameraOverridesRef.current.add(camera.name); @@ -490,7 +490,7 @@ export default function NotificationsSettingsExtras({ return (
-
+
{isAdmin && (