blueprint:
name: Frigate LLM Notification (v0.8)
author: whag
description: >-
Basic requirements:
- Home Assistant with an MQTT broker setup.
- Frigate integration with MQTT setup. Link: https://github.com/blakeblackshear/frigate-hass-integration
- LLM Vision integration with provider setup. Link: https://github.com/valentinfrlch/ha-llmvision
- Optional Downloader integration. (Ensure the Use Downloader Input is Set Accordingly)
Link: https://www.home-assistant.io/integrations/downloader/
Information:
- Only suitable for one Frigate camera, use multiple instances of this
automation for multiple cameras if required.
- LLM Vision requires a provider setup for
the automation to function, remember and memory options are optional.
Description:
Use LLM Vision to analyse frigate snapshots and clips and send notifications
via the Home Assistant companion app.
Monitors MQTT frigate/reviews topic of types [new]
and [end].
If you decide to use the Downloader integration for storing the frigate files
locally before sending them to LLM the downloader folder will need maintained separately (i.e.
old files deleted or backed up). This can be achieved automatically via a shell command or
similar.
This automation runs in parallel mode.
Tested on Android and iOS.
domain: automation
input:
frigate:
name: Frigate Options
icon: mdi:cctv
description: Frigate options
collapsed: true
input:
in_camera:
name: Frigate Camera
description: Frigate cameras only
selector:
entity:
multiple: false
filter:
- integration: frigate
domain:
- camera
reorder: false
in_severity:
name: Severity
description: Frigate event severity.
default:
- alert
- detection
selector:
select:
options:
- label: Alert
value: alert
- label: Detection
value: detection
multiple: true
sort: false
custom_value: false
in_zones:
name: Frigate Zones
description: Zones required for the alert. Filter will show all frigate devices (zones and cameras.)
default: []
selector:
device:
multiple: true
filter:
- integration: frigate
entity:
- domain:
- binary_sensor
in_custom_zones:
name: Custom zones
description: Add any custom zones not shown in the above selector.
default: []
selector:
text:
multiline: false
multiple: true
in_all_zones:
name: All Zones
description: All zones have to be entered before processing rather than any.
default: false
selector:
boolean: {}
in_objects:
name: Required Objects
description: Frigate objects required in the selected zones if any.
default: []
selector:
select:
multiple: true
custom_value: true
options:
- person
- bicycle
- car
- motorcycle
- airplane
- bus
- train
- car
- boat
- traffic light
- fire hydrant
- street sign
- stop sign
- parking meter
- bench
- bird
- cat
- dog
- horse
- sheep
- cow
- elephant
- bear
- zebra
- giraffe
- hat
- backpack
- umbrella
- shoe
- eye glasses
- handbag
- tie
- suitcase
- frisbee
- skis
- snowboard
- sports ball
- kite
- baseball bat
- baseball glove
- skateboard
- surfboard
- tennis racket
- bottle
- plate
- wine glass
- cup
- fork
- knife
- spoon
- bowl
- banana
- apple
- sandwich
- orange
- broccoli
sort: false
in_sub_labels:
name: Sub Labels Required
description: Frigate sub labels required in the selected zones if any.
default: []
selector:
text:
multiline: false
multiple: true
in_host:
name: Home Assistant IP Address.
description: Home Assistant ip address.
default: http://192.168.1.1:8123
selector:
text:
multiline: false
multiple: false
llm:
name: LLM Vision Provider / Model
icon: mdi:robot
description: LLM Vision options
collapsed: true
input:
provider_image:
name: Provider For Image Analysis
description: Provider to use for image analyse action.
selector:
config_entry:
integration: llmvision
model_image:
name: AI Model For Image Analysis
description: AI Model for image analysis
default: gemini-3.1-flash-lite
selector:
select:
options:
- gpt-5.5
- gpt-5.4
- gpt-5.4-mini
- gpt-5.4-nano
- gpt-4.1
- gpt-4o
- gpt-4o-mini
- claude-fable-5
- claude-opus-4-8
- claude-sonnet-5
- claude-haiku-4-5
- gemini-3.5-flash
- gemini-3.1-flash-lite
- gemini-2.5-pro
- gemini-2.5-flash
- gemini-2.5-flash-lite
- gemini-flash-latest
- qwen/qwen3.6-27b
- gemma-4-31B-it
multiple: false
custom_value: true
sort: false
provider_clip:
name: Provider For Clip Analysis
description: Provider to use for the clip analyse action.
selector:
config_entry:
integration: llmvision
model_clip:
name: AI Model For Clip Analysis
description: AI Model for clip analysis
default: gemini-3.1-flash-lite
selector:
select:
options:
- gpt-5.5
- gpt-5.4
- gpt-5.4-mini
- gpt-5.4-nano
- gpt-4.1
- gpt-4o
- gpt-4o-mini
- claude-fable-5
- claude-opus-4-8
- claude-sonnet-5
- claude-haiku-4-5
- gemini-3.5-flash
- gemini-3.1-flash-lite
- gemini-2.5-pro
- gemini-2.5-flash
- gemini-2.5-flash-lite
- gemini-flash-latest
- qwen/qwen3.6-27b
- gemma-4-31B-it
multiple: false
custom_value: true
sort: false
filters:
name: Filters
icon: mdi:account
description: Master Conditions to evaluate before running the automation actions.
collapsed: true
input:
in_person_zone:
name: Vacant Zone
description: The zone required to be vacant.
default:
selector:
entity:
domain:
- zone
multiple: false
reorder: false
in_person_check:
name: Persons Away
description: Select the people to validate against the vacant zone.
default: []
selector:
entity:
domain:
- person
multiple: true
reorder: false
in_custom_condition:
name: Custom Condition
description: Add any custom logic to evaluate before running the automation, the result must be true to continue.
default: []
selector:
condition: {}
image_analyse:
name: Snapshot Analyse
icon: mdi:image
description: Frigate event snapshot analyse options
collapsed: true
input:
in_title_image:
name: Custom Notification Title
description: Notification title for snapshot. If you are using LLM for image analyse and want a AI generated title ensure [Generate Title] below is enabled.
default: "{{camera_name}} - {{sub_labels| join('', '')|replace('_', ' ')}} Alert"
selector:
text:
multiple: false
multiline: false
in_custom_msg:
name: Custom Notification Message
description: Initial notification message if not using LLM Vision for the snapshot analysis.
default: "A {{objects| join(' and a ')|replace('_', ' ')}} was detected in the {{after_zones| join(' and the ')|replace('_', ' ')}}"
selector:
text:
multiline: true
multiple: false
in_use_llm_image:
name: LLM Vision Snapshot
description: Use LLM Vision for initial snapshot analysis. If off then the custom message above will be used to generate the notification.
default: true
selector:
boolean: {}
prompt:
name: Prompt
description: Model prompt.
default: >-
Summarise the image in a 1 sentence response, concentrate on the {{input_objects| join(',')|replace('_', ' ')}} if seen within the {{zone_names| join(' and ,')|replace('_',' ')}}. Any person approaching the camera should take priority over all other events. If you see a known person from memory, mention this in the response. Do not mention the prompt or describe the environment in your response. The response is an initial alert for a mobile phone security notification.
selector:
text:
multiline: true
multiple: false
target_width:
name: Pixels
description: Width in pixels to downscale.
default: 1080
selector:
number:
min: 512
max: 1920
step: 10
mode: slider
tokens:
name: Tokens
description: Maximum number of tokens to generate.
default: 100
selector:
number:
min: 1
max: 300
step: 1
mode: slider
in_gen_title:
name: Generate Title
description: AI generated notification title.
default: false
selector:
boolean: {}
use_memory:
name: Use Memory
description: Use information stored in memory to provide additional context. Memory must be setup in LLM Vision.
default: false
selector:
boolean: {}
use_remember:
name: Use Remember
description: Store this event in the timeline. Timeline must be setup in LLM Vision.
default: false
selector:
boolean: {}
expose_image:
name: Expose Image
description: Save image in llmvision for Timeline. Timeline must be setup in LLM Vision.
default: false
selector:
boolean: {}
delay_image:
name: Delay Before Image
description: Delay in seconds before processing Frigate snapshot.
default: 2
selector:
number:
min: 0
max: 60
step: 1
mode: slider
clip_analyse:
name: Review Clip Analyse
icon: mdi:video
collapsed: true
description: Frigate review clip analyse options
input:
in_title_clip:
name: No AI Notification Title
description: Notification title for review clip. If you want a AI generated title ensure [Generate Title] below is enabled.
default: "{{camera_name}} - {{sub_labels| join('', '')|replace('_', ' ')}} Alert Update"
selector:
text:
multiline: false
multiple: false
prompt2:
name: Prompt
description: Model prompt.
default: >-
Analyse the video clip which should contain the following objects {{input_objects| join(',')|replace('_', ' ')}} The areas of interest are the {{zone_names| join(' and ,')|replace('_',' ')}} Focus on the objects referenced previously which are entering the areas of interest and describe what they are doing. Consider what the {{input_objects|join(',')|replace('_', ' ')}} is doing, why, and what it might do next. Do not mention the prompt or describe the environment in your response. Your response will be used for a mobile phone notification message with the clip attached.
selector:
text:
multiline: true
multiple: false
target_width2:
name: Pixels
description: Width in pixels to downscale.
default: 1080
selector:
number:
min: 512
max: 1920
step: 10
mode: slider
tokens2:
name: Tokens
description: Maximum number of tokens to generate.
default: 100
selector:
number:
min: 1
max: 300
step: 1
mode: slider
max_frames:
name: Max Frames
description: How many frames to analyse for clip.
default: 3
selector:
number:
min: 1
max: 10
step: 1
mode: slider
in_gen_title2:
name: Generate Title
description: AI generated clip title.
default: false
selector:
boolean: {}
use_memory2:
name: Use Memory
description: Use information stored in memory to provide additional context. Memory must be setup in LLM Vision.
default: false
selector:
boolean: {}
use_remember2:
name: Use Remember
description: Store this event in the timeline. Timeline must be setup in LLM Vision.
default: false
selector:
boolean: {}
expose_clip:
name: Expose Clip
description: Save clip in llmvision for Timeline. Timeline must be setup in LLM Vision.
default: false
selector:
boolean: {}
delay_clip:
name: Delay Before Clip
description: Time in seconds before processing the Frigate clip.
default: 2
selector:
number:
min: 0
max: 60
step: 1
mode: slider
frigate_retry_attempts:
name: Frigate Max Retry Amount
description: In the event the frigate clip is not available set the amount of times to retry fetching the file.
default: 3
selector:
number:
min: 1
max: 10
step: 1
mode: slider
frigate_retry_seconds:
name: Frigate Max Retry Delay In Seconds
description: Time between retrys
default: 1
selector:
number:
min: 1
max: 10
step: 1
mode: slider
Downloader:
name: Downloader Options
icon: mdi:download
collapsed: true
input:
in_use_downloader:
name: Use The Downloader Integration
description: Store the image/clip locally using HA Downloader integration, and use the local files for LLM Vision analysis. If this input is false this section can be ignored.
default: false
selector:
boolean: {}
in_downloader:
name: Downloader Directory
description: Downloader integration root directory.
default: downloader
selector:
text:
multiline: false
multiple: false
in_downloader_sub:
name: Downloader Sub Directory
description: Define a sub directory within your downloader root directory.
default: ''
selector:
text:
multiline: false
multiple: false
download_image_wait:
name: Image Download Max Wait Time
description: Maximum time to wait in seconds for the frigate image to be downloaded before retrying download.
default: 5
selector:
number:
min: 1
max: 120
step: 1
mode: slider
download_clip_wait:
name: Clip Download Max Wait Time
description: Maximum time to wait in seconds for the frigate clip to be downloaded before retrying download.
default: 30
selector:
number:
min: 1
max: 120
step: 1
mode: slider
notify:
name: Notification Options
icon: mdi:phone
collapsed: true
input:
in_notify_devices:
name: Notify Devices
description: The devices to send the notification to. Multiple devices may be used. Only works with Home Assistant Companion mobile app.
default: []
selector:
device:
multiple: true
filter:
- integration: mobile_app
in_ios_devices:
name: iOS Devices
description: Select which of the notify devices are iOS. Used to send m3u8 links to iOS and mp4 to Android.
default: []
selector:
device:
multiple: true
filter:
- integration: mobile_app
click_action1:
name: Click Action New Event
description: Notification Click Action for New Event Notification.
default: '{{image}}'
selector:
select:
options:
- label: Lovelace URL
value: /Lovelace
- label: View Snapshot
value: '{{image}}'
- label: View Clip Android
value: '{{video}}'
- label: View Clip IOS
value: '{{video_ios}}'
- label: View Thumbnail
value: '{{thumb}}'
- label: View Snapshot with bounding box
value: '{{image}}?bbox=1'
- label: View Review GIF
value: '{{gif}}'
mode: dropdown
custom_value: true
sort: false
multiple: false
in_attachment1:
name: Attachment New Event
description: Notification Attachment for New Event.
default: '{{thumb}}'
selector:
select:
options:
- label: Thumbnail
value: '{{thumb}}'
- label: Snapshot
value: '{{image}}'
- label: Snapshot 2:1
value: '{{image}}?format=android'
- label: Snapshot with bounding box
value: '{{image}}?bbox=1'
- label: Snapshot Cropped
value: '{{image}}?crop=1'
- label: Snapshot Cropped And Bounding Box
value: '{{image}}?bbox=1&crop=1'
- label: Review GIF
value: '{{gif}}'
mode: dropdown
custom_value: false
sort: false
multiple: false
click_action2:
name: Click Action End Event
description: Notification Click Action For End Event Notification.
default: '{{video}}'
selector:
select:
options:
- label: Lovelace URL
value: /Lovelace
- label: View Snapshot
value: '{{image}}'
- label: View Clip
value: '{{video}}'
- label: View Thumbnail
value: '{{thumb}}'
- label: View Snapshot with bounding box
value: '{{image}}?bbox=1'
- label: View Review GIF
value: '{{gif}}'
mode: dropdown
custom_value: true
sort: false
multiple: false
in_attachment2:
name: Attachment End Event
description: Notification Attachment for End Event.
default: '{{gif}}'
selector:
select:
options:
- label: Thumbnail
value: '{{thumb}}'
- label: Snapshot
value: '{{image}}'
- label: Snapshot 2:1
value: '{{image}}?format=android'
- label: Snapshot with bounding box
value: '{{image}}?bbox=1'
- label: Snapshot Cropped
value: '{{image}}?crop=1'
- label: Snapshot Cropped And Bounding Box
value: '{{image}}?bbox=1&crop=1'
- label: Clip (IOS Only)
value: '{{video_ios}}'
- label: Review GIF
value: '{{gif}}'
cooldown:
name: Cooldown
description: Time in minutes to wait before running this automation again.
default: 2
selector:
number:
min: 0
max: 60
step: 1
mode: slider
in_button1_title:
name: Button 1 Title (New Event)
description: Title for the first notification button (New Event).
default: View Live
selector:
select:
options:
- label: View Live
value: View Live
- label: Silence
value: Silence
- label: View Snapshot
value: View Snapshot
- label: View Clip
value: View Clip
- label: Call
value: Call
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button1_action:
name: Button 1 Action (New Event)
description: Action for the first notification button (New Event).
default: VIEW_LIVE
selector:
select:
options:
- label: View Live
value: VIEW_LIVE
- label: Silence
value: SILENCE
- label: View Snapshot
value: VIEW_SNAPSHOT
- label: View Clip
value: VIEW_CLIP
- label: Call
value: CALL
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button2_title:
name: Button 2 Title (New Event)
description: Title for the second notification button (New Event).
default: View Snapshot
selector:
select:
options:
- label: View Live
value: View Live
- label: Silence
value: Silence
- label: View Snapshot
value: View Snapshot
- label: View Clip
value: View Clip
- label: Call
value: Call
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button2_action:
name: Button 2 Action (New Event)
description: Action for the second notification button (New Event).
default: VIEW_SNAPSHOT
selector:
select:
options:
- label: View Live
value: VIEW_LIVE
- label: Silence
value: SILENCE
- label: View Snapshot
value: VIEW_SNAPSHOT
- label: View Clip
value: VIEW_CLIP
- label: Call
value: CALL
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button3_title:
name: Button 3 Title (New Event)
description: Title for the third notification button (New Event).
default: Call
selector:
select:
options:
- label: View Live
value: View Live
- label: Silence
value: Silence
- label: View Snapshot
value: View Snapshot
- label: View Clip
value: View Clip
- label: Call
value: Call
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button3_action:
name: Button 3 Action (New Event)
description: Action for the third notification button (New Event).
default: CALL
selector:
select:
options:
- label: View Live
value: VIEW_LIVE
- label: Silence
value: SILENCE
- label: View Snapshot
value: VIEW_SNAPSHOT
- label: View Clip
value: VIEW_CLIP
- label: Call
value: CALL
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button1_title2:
name: Button 1 Title (End Event)
description: Title for the first notification button (End Event).
default: View Live
selector:
select:
options:
- label: View Live
value: View Live
- label: Silence
value: Silence
- label: View Snapshot
value: View Snapshot
- label: View Clip
value: View Clip
- label: Call
value: Call
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button1_action2:
name: Button 1 Action (End Event)
description: Action for the first notification button (End Event).
default: VIEW_LIVE
selector:
select:
options:
- label: View Live
value: VIEW_LIVE
- label: Silence
value: SILENCE
- label: View Snapshot
value: VIEW_SNAPSHOT
- label: View Clip
value: VIEW_CLIP
- label: Call
value: CALL
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button2_title2:
name: Button 2 Title (End Event)
description: Title for the second notification button (End Event).
default: View Snapshot
selector:
select:
options:
- label: View Live
value: View Live
- label: Silence
value: Silence
- label: View Snapshot
value: View Snapshot
- label: View Clip
value: View Clip
- label: Call
value: Call
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button2_action2:
name: Button 2 Action (End Event)
description: Action for the second notification button (End Event).
default: VIEW_SNAPSHOT
selector:
select:
options:
- label: View Live
value: VIEW_LIVE
- label: Silence
value: SILENCE
- label: View Snapshot
value: VIEW_SNAPSHOT
- label: View Clip
value: VIEW_CLIP
- label: Call
value: CALL
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button3_title2:
name: Button 3 Title (End Event)
description: Title for the third notification button (End Event).
default: Call
selector:
select:
options:
- label: View Live
value: View Live
- label: Silence
value: Silence
- label: View Snapshot
value: View Snapshot
- label: View Clip
value: View Clip
- label: Call
value: Call
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_button3_action2:
name: Button 3 Action (End Event)
description: Action for the third notification button (End Event).
default: CALL
selector:
select:
options:
- label: View Live
value: VIEW_LIVE
- label: Silence
value: SILENCE
- label: View Snapshot
value: VIEW_SNAPSHOT
- label: View Clip
value: VIEW_CLIP
- label: Call
value: CALL
- label: Hide
value: ''
multiple: false
sort: false
custom_value: false
in_call_number:
name: Call Phone Number
description: Phone number to dial when Call button is pressed (e.g. 07777777777).
default: ''
selector:
text:
multiline: false
multiple: false
Actions:
name: Custom Actions
icon: mdi:arrow-all
collapsed: true
input:
in_action1:
name: New Event Custom Actions
description: Custom action to run on new event.
default: []
selector:
action: {}
in_action2:
name: End Event Custom Actions
description: Custom action to run on end event.
default: []
selector:
action: {}
Logbook:
name: Logbook
icon: mdi:pen
collapsed: true
input:
in_enable_log:
name: Enable Logbook Event
description: Enable automaton logbook entry.
default: false
selector:
boolean: {}
source_url: https://github.com/willhaggan/HA_Frigate_VLLM_Notification/blob/main/Latest.yaml
triggers:
- trigger: mqtt
topic: '{{ frigate_topic }}'
payload: new
value_template: '{{ value_json["type"] }}'
id: New event
- trigger: mqtt
topic: '{{ frigate_topic }}'
payload: end
value_template: '{{ value_json["type"] }}'
id: End event
- trigger: event
event_type: mobile_app_notification_action
id: Button pressed
conditions:
- condition: template
value_template: '{{ camera_match or trigger.id == "Button pressed" }}'
alias: Valid Camera
- condition: template
value_template: '{{ objects_match or trigger.id == "Button pressed" }}'
alias: Object Matched
- condition: template
value_template: '{{ sub_labels_match or trigger.id == "Button pressed" }}'
alias: Sub Label Matched
- condition: template
value_template: '{{ zone_match or trigger.id == "Button pressed" }}'
alias: Valid Zone
- condition: template
value_template: '{{ severity_match or trigger.id == "Button pressed" }}'
alias: Valid Alert
- condition: template
value_template: '{{ zone_vacant or trigger.id == "Button pressed" }}'
alias: Zone Vacant
- condition: template
value_template: '{{ custom_condition | length == 0 or custom_condition or trigger.id == "Button pressed" }}'
alias: Custom Condition
actions:
- if:
- condition: template
value_template: '{{ enable_log and trigger.id != "Button pressed" }}'
then:
- action: logbook.log
data:
name: LLM Vision Frigate Notification log
entity_id: '{{ this.entity_id }}'
message: >-
___Event type: {{ type }} ___Review id: {{ review_id }} ___Input camera: {{ input_camera }}
___Camera triggered: {{ input_camera_name }} ___Detections ID's: {{ detections }}
___Detection 1 ID: {{ id }} ___Objects required: {{ input_objects }} ___Objects found: {{ objects }}
___Object match: {{ objects_match }} ___Sub Labels required: {{ input_sub_labels }} ___Sub Labels found: {{ sub_labels }}
___Sub Labels match: {{ sub_labels_match }} ___Zones required: {{ zone_names }}
___Zones triggered before: {{ before_zones }} ___Zones triggered after: {{ after_zones }}
___Zone match: {{ zone_match }} ___Vacant Zone: {{ zone_vacant }} ___Notify devices: {{ notify_names }}
___Downloader root: {{ root_dir }} ___Downloader sub: {{ sub_dir }} ___Thumb url: {{ thumb }}
___Snapshot url: {{ image }} ___Clip url: {{ video }} ___Clip url(IOS): {{ video_ios }} ___GIF url: {{ gif }}
- alias: Event is new start LLM image analysis and send notification
if:
- condition: trigger
id:
- New event
then:
- delay:
seconds: 2
- parallel:
- alias: Run Custom Actions New
choose:
- conditions: '{{ action1|length != 0 }}'
sequence: !input in_action1
- alias: Repeat Until file exists or 2 retries
repeat:
sequence:
- alias: Downloader Integration Enabled
if:
- condition: template
value_template: '{{ use_downloader }}'
then:
- action: downloader.download_file
continue_on_error: true
data:
overwrite: true
url: '{{ host }}{{ image }}'
subdir: '{{ sub_dir }}'
filename: '{{ id }}_snapshot.jpg'
- alias: Wait for downloader to complete
wait_for_trigger:
- trigger: event
event_type: downloader_download_completed
event_data:
url: '{{ host }}{{ image }}'
filename: '{{ id }}_snapshot.jpg'
id: image_exists
continue_on_timeout: true
timeout:
seconds: !input download_image_wait
- alias: If Downloader is enabled wait until the image is downloaded then run LLM Image Analysis, if Downloader disabled continue
if:
- condition: template
value_template: '{{ (wait is defined and wait.trigger.id == "image_exists") or (use_downloader == false) }}'
then:
- delay:
seconds: !input delay_image
- variables:
image_file: '{{ image_local if use_downloader else image }}'
- if:
- condition: template
value_template: '{{ use_llm_image }}'
then:
- action: llmvision.video_analyzer
data:
provider: !input provider_image
model: !input model_image
store_in_timeline: !input use_remember
expose_images: !input expose_image
use_memory: !input use_memory
video_file: '{{ image_file }}'
max_frames: 1
message: !input prompt
include_filename: false
target_width: !input target_width
max_tokens: !input tokens
generate_title: '{{ gen_title }}'
frigate_retry_attempts: !input frigate_retry_attempts
frigate_retry_seconds: !input frigate_retry_seconds
response_variable: response
- delay:
seconds: 5
- variables:
title: '{{ response.response_title if gen_title and response.response_title|length > 0 else title_image }}'
msg: '{{ response.response_text }}'
response_out: '{{ response.response_text }}'
else:
- variables:
title: '{{ title_image }}'
msg: '{{ custom_msg }}'
- alias: Send Initial notification to notify devices
repeat:
for_each: '{{ notify_targets }}'
sequence:
- action: notify.{{ repeat.item.notify }}
data:
title: '{{ title }}'
message: '{{ msg }}'
data:
image: '{{ attachment1 }}'
url: '{{ attachment1 }}'
clickAction: "{{ (video_ios if repeat.item.is_ios else video) if 'video' in (ca1_in|string) else (ca1_in|string) }}"
group: '{{ camera }}-frigate-notification'
tag: '{{ id }}'
ttl: 0
priority: high
actions:
- action: '{{ "SILENCE_" ~ this.entity_id if button1_action == "SILENCE" else ("URI" if "/" in (button1_action | lower) or button1_action in ["VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"] else button1_action) }}'
uri: '{{ (button1_action == "VIEW_LIVE") and ("entityId:" ~ input_camera) or (button1_action == "VIEW_SNAPSHOT") and image or (button1_action == "VIEW_CLIP") and (video_ios if repeat.item.is_ios else video) or (button1_action == "CALL") and ("deep-link://tel:" ~ call_number) or (button1_action not in ["SILENCE","VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"]) and "/lovelace" }}'
title: '{{ button1_title }}'
behavior: '{{ "textInput" if button1_action == "SILENCE" else "" }}'
- action: '{{ "SILENCE_" ~ this.entity_id if button2_action == "SILENCE" else ("URI" if "/" in (button2_action | lower) or button2_action in ["VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"] else button2_action) }}'
uri: '{{ (button2_action == "VIEW_LIVE") and ("entityId:" ~ input_camera) or (button2_action == "VIEW_SNAPSHOT") and image or (button2_action == "VIEW_CLIP") and (video_ios if repeat.item.is_ios else video) or (button2_action == "CALL") and ("deep-link://tel:" ~ call_number) or (button2_action not in ["SILENCE","VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"]) and "/lovelace" }}'
title: '{{ button2_title }}'
behavior: '{{ "textInput" if button2_action == "SILENCE" else "" }}'
- action: '{{ "SILENCE_" ~ this.entity_id if button3_action == "SILENCE" else ("URI" if "/" in (button3_action | lower) or button3_action in ["VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"] else button3_action) }}'
uri: '{{ (button3_action == "VIEW_LIVE") and ("entityId:" ~ input_camera) or (button3_action == "VIEW_SNAPSHOT") and image or (button3_action == "VIEW_CLIP") and (video_ios if repeat.item.is_ios else video) or (button3_action == "CALL") and ("deep-link://tel:" ~ call_number) or (button3_action not in ["SILENCE","VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"]) and "/lovelace" }}'
title: '{{ button3_title }}'
behavior: '{{ "textInput" if button3_action == "SILENCE" else "" }}'
- delay:
minutes: !input cooldown
alias: Cooldown
until:
- condition: template
value_template: '{{ repeat.index >= 5 or (wait is defined and wait.trigger.id == "image_exists") or (use_downloader == false) }}'
- alias: Event is end Do LLM video analysis and send notification
if:
- condition: trigger
id:
- End event
then:
- delay:
seconds: 5
- parallel:
- alias: Run Custom Actions End
choose:
- conditions: '{{ action2|length != 0 }}'
sequence: !input in_action2
- repeat:
sequence:
- alias: Downloader Integration Enabled
if:
- condition: template
value_template: '{{ use_downloader }}'
then:
- action: downloader.download_file
continue_on_error: true
data:
overwrite: true
url: '{{ host }}{{ video }}'
subdir: '{{ sub_dir }}'
filename: '{{ id }}_clip.mp4'
- alias: Wait for downloader to complete
wait_for_trigger:
- trigger: event
event_type: downloader_download_completed
event_data:
url: '{{ host }}{{ video }}'
filename: '{{ id }}_clip.mp4'
id: clip_exists
continue_on_timeout: true
timeout:
seconds: !input download_clip_wait
- delay:
seconds: !input delay_clip
- variables:
video_file: '{{ video_local if use_downloader else video }}'
- alias: If Downloader is enabled wait until the clip is downloaded then run LLM video Analysis, if Downloader disabled continue
if:
- condition: template
value_template: '{{ (wait is defined and wait.trigger.id == "clip_exists") or (use_downloader == false) }}'
then:
- action: llmvision.video_analyzer
data:
provider: !input provider_clip
model: !input model_clip
store_in_timeline: !input use_remember2
expose_images: !input expose_clip
use_memory: !input use_memory2
video_file: '{{ video_file }}'
max_frames: !input max_frames
include_filename: false
target_width: !input target_width2
max_tokens: !input tokens2
generate_title: '{{ gen_title2 }}'
message: !input prompt2
frigate_retry_attempts: !input frigate_retry_attempts
frigate_retry_seconds: !input frigate_retry_seconds
response_variable: response
- variables:
video_file2: '{{ attachment2 if ".m3u8" in attachment2 else "" }}'
image_file2: '{{ "" if ".m3u8" in attachment2 else attachment2 }}'
response_out: '{{ response.response_text }}'
- delay:
seconds: 1
- alias: Update notification to notify devices
repeat:
for_each: '{{ notify_targets }}'
sequence:
- action: notify.{{ repeat.item.notify }}
data:
title: '{{ response.response_title if gen_title2 and response.response_title|length > 0 else title_clip }}'
message: '{{ response.response_text }}'
data:
video: "{{ video_file2 if repeat.item.is_ios else '' }}"
image: "{{ image_file2 if repeat.item.is_ios else (gif if (video_file2 | length > 0) else image_file2) }}"
url: "{{ (video_ios if repeat.item.is_ios else video) if 'video' in (ca2_in|string) else (ca2_in|string) }}"
clickAction: "{{ (video_ios if repeat.item.is_ios else video) if 'video' in (ca2_in|string) else (ca2_in|string) }}"
group: '{{ camera }}-frigate-notification'
tag: '{{ id }}'
interruption-level: active
ttl: 0
priority: high
actions:
- action: '{{ "SILENCE_" ~ this.entity_id if button1_action2 == "SILENCE" else ("URI" if "/" in (button1_action2 | lower) or button1_action2 in ["VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"] else button1_action2) }}'
uri: '{{ (button1_action2 == "VIEW_LIVE") and ("entityId:" ~ input_camera) or (button1_action2 == "VIEW_SNAPSHOT") and image or (button1_action2 == "VIEW_CLIP") and (video_ios if repeat.item.is_ios else video) or (button1_action2 == "CALL") and ("deep-link://tel:" ~ call_number) or (button1_action2 not in ["SILENCE","VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"]) and "/lovelace" }}'
title: '{{ button1_title2 }}'
behavior: '{{ "textInput" if button1_action2 == "SILENCE" else "" }}'
- action: '{{ "SILENCE_" ~ this.entity_id if button2_action2 == "SILENCE" else ("URI" if "/" in (button2_action2 | lower) or button2_action2 in ["VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"] else button2_action2) }}'
uri: '{{ (button2_action2 == "VIEW_LIVE") and ("entityId:" ~ input_camera) or (button2_action2 == "VIEW_SNAPSHOT") and image or (button2_action2 == "VIEW_CLIP") and (video_ios if repeat.item.is_ios else video) or (button2_action2 == "CALL") and ("deep-link://tel:" ~ call_number) or (button2_action2 not in ["SILENCE","VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"]) and "/lovelace" }}'
title: '{{ button2_title2 }}'
behavior: '{{ "textInput" if button2_action2 == "SILENCE" else "" }}'
- action: '{{ "SILENCE_" ~ this.entity_id if button3_action2 == "SILENCE" else ("URI" if "/" in (button3_action2 | lower) or button3_action2 in ["VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"] else button3_action2) }}'
uri: '{{ (button3_action2 == "VIEW_LIVE") and ("entityId:" ~ input_camera) or (button3_action2 == "VIEW_SNAPSHOT") and image or (button3_action2 == "VIEW_CLIP") and (video_ios if repeat.item.is_ios else video) or (button3_action2 == "CALL") and ("deep-link://tel:" ~ call_number) or (button3_action2 not in ["SILENCE","VIEW_LIVE","VIEW_SNAPSHOT","VIEW_CLIP","CALL"]) and "/lovelace" }}'
title: '{{ button3_title2 }}'
behavior: '{{ "textInput" if button3_action2 == "SILENCE" else "" }}'
- delay:
minutes: !input cooldown
alias: Cooldown
until:
- condition: template
value_template: '{{ repeat.index >= 5 or (wait is defined and wait.trigger.id == "clip_exists") or (use_downloader == false) }}'
- alias: Handle notification button actions
if:
- condition: template
value_template: '{{ trigger.id == "Button pressed" and trigger.event.data.action == "SILENCE_" ~ this.entity_id }}'
then:
- variables:
silence_timer: >-
{% if trigger.event.data.reply_text is defined and trigger.event.data.reply_text | regex_match('^\d+$') %}
{{ trigger.event.data.reply_text | int }}
{% else %}
5
{% endif %}
- service: persistent_notification.create
data:
title: Notifications Silenced
message: Notifications will be silenced for {{ silence_timer }} minutes.
- service: logbook.log
data:
name: LLM Vision Frigate Notification log
entity_id: '{{ this.entity_id }}'
message: Notifications silenced for {{ silence_timer }} minutes by {{ trigger.event.data.action }} button.
- action: automation.turn_off
data:
stop_actions: false
target:
entity_id: '{{ this.entity_id }}'
- delay:
minutes: '{{ silence_timer }}'
- action: automation.turn_on
data: {}
target:
entity_id: '{{ this.entity_id }}'
trigger_variables:
frigate_topic: frigate/reviews
variables:
frigate_topic: frigate/reviews
enable_log: !input in_enable_log
input_camera: !input in_camera
input_objects: !input in_objects
input_sub_labels: !input in_sub_labels
input_zones: !input in_zones
custom_zones: !input in_custom_zones
all_zones: !input in_all_zones
person_check: !input in_person_check
person_zone: !input in_person_zone
zone_vacant: >-
{% if not person_zone or person_zone | length == 0 or person_check | length == 0 %}
true
{% else %}
{% set zone_persons = state_attr(person_zone, "persons") or [] %}
{{ person_check | select('in', zone_persons) | list | count == 0 }}
{% endif %}
custom_condition: !input in_custom_condition
host: !input in_host
use_downloader: !input in_use_downloader
downloader: !input in_downloader
downloader_sub: !input in_downloader_sub
notify_devices: !input in_notify_devices
ios_devices: !input in_ios_devices
use_llm_image: !input in_use_llm_image
gen_title: !input in_gen_title
gen_title2: !input in_gen_title2
event: >-
{% if trigger.id == "Button pressed" %}
{}
{% else %}
{{ trigger.payload_json }}
{% endif %}
type: >-
{% if trigger.id == "Button pressed" %}
""
{% else %}
{{ event['type'] }}
{% endif %}
review_id: >-
{% if trigger.id == "Button pressed" %}
""
{% else %}
{{ event['after']['id'] }}
{% endif %}
input_camera_name: "{{ input_camera|expand|map(attribute='attributes.camera_name', default='none')|list }}"
camera: >-
{% if trigger.id == "Button pressed" %}
""
{% else %}
{{ event['after']['camera'] }}
{% endif %}
camera_name: "{{ camera | replace('_', ' ') | title }}"
camera_match: "{{ input_camera_name|select('equalto', camera)|list|length>0 }}"
detections: >-
{% if trigger.id == "Button pressed" %}
[]
{% else %}
{{ event['after']['data']['detections'] }}
{% endif %}
id: >-
{% if trigger.id == "Button pressed" %}
""
{% else %}
{{ detections[0] }}
{% endif %}
objects: >-
{% if trigger.id == "Button pressed" %}
[]
{% else %}
{{ event['after']['data']['objects'] }}
{% endif %}
objects_match: >-
{% set ns = namespace(matched = false) %}
{% if input_objects | length == 0 %}
{% set ns.matched = true %}
{% else %}
{% set ns.matched = false %}
{% for input_obj in input_objects %}
{% if input_obj == 'person' %}
{% if 'person' in objects or 'person-verified' in objects %}
{% set ns.matched = true %}
{% endif %}
{% elif input_obj in objects %}
{% set ns.matched = true %}
{% endif %}
{% endfor %}
{% endif %}
{{ ns.matched }}
sub_labels: >-
{% if trigger.id == "Button pressed" %}
[]
{% else %}
{{ event['after']['data']['sub_labels'] }}
{% endif %}
sub_labels_match: "{{ input_sub_labels | length == 0 or (input_sub_labels | select('in', sub_labels) | list | length > 0) }}"
zone_names: >-
{% set device_zones = input_zones %}
{% set zones = device_zones + custom_zones %}
{% set ns = namespace(device_names=[]) %}
{% for device_id in zones %}
{% if device_attr(device_id, 'name') != None %}
{% set device_name = device_attr(device_id, 'name') %}
{% set sanitized_name = device_name | lower | regex_replace('[^a-z0-9_\- ]', '') | replace(' ', '_') | replace('-', '_') %}
{% set ns.device_names = ns.device_names + [sanitized_name] %}
{% else %}
{% set ns.device_names = ns.device_names + [device_id | lower | regex_replace('[^a-z0-9_\- ]', '') | replace(' ', '_') | replace('-', '_')] %}
{% endif %}
{% endfor %}
{{ ns.device_names }}
before_zones: >-
{% if trigger.id == "Button pressed" %}
[]
{% else %}
{{ event['before']['data']['zones'] | lower | default([]) }}
{% endif %}
after_zones: >-
{% if trigger.id == "Button pressed" %}
[]
{% else %}
{{ event['after']['data']['zones'] | lower | default([]) }}
{% endif %}
zone_entered: "{{ zone_names | length == 0 or ((zone_names | select('in', (before_zones + after_zones)) | list | length) > 0) }}"
all_zones_entered: "{{ true if (zone_names | reject('in', (before_zones + after_zones)) | list | length == 0 and all_zones) or all_zones == false else false }}"
zone_match: "{{ zone_entered and all_zones_entered }}"
input_severity: !input in_severity
severity: >-
{% if trigger.id == "Button pressed" %}
[]
{% else %}
{{ (event['before']['severity'] | default([])) + (event['after']['severity'] | default([])) }}
{% endif %}
severity_match: "{{ input_severity | select('in', severity) | list | length > 0 }}"
notify_names: >-
{% set ns = namespace(device_names=[]) %}
{% for device_id in notify_devices %}
{% set device_name = device_attr(device_id, 'name') %}
{% set sanitized_name = 'mobile_app_' + device_name | lower | regex_replace('[^a-z0-9_\- ]', '') | replace(' ', '_') | replace('-', '_') %}
{% set ns.device_names = ns.device_names + [sanitized_name] %}
{% endfor %}
{{ ns.device_names }}
notify_targets: >-
{% set ns = namespace(targets=[]) %}
{% set ios_ids = ios_devices %}
{% for device_id in notify_devices %}
{% set device_name = device_attr(device_id, 'name') %}
{% set sanitized_name = 'mobile_app_' + device_name | lower | regex_replace('[^a-z0-9_\- ]', '') | replace(' ', '_') | replace('-', '_') %}
{% set is_ios_dev = device_id in ios_ids %}
{% set ns.targets = ns.targets + [{'notify': sanitized_name, 'is_ios': is_ios_dev}] %}
{% endfor %}
{{ ns.targets }}
root_dir: >-
{% set dir = downloader | trim %}
{% if dir | length > 0 %}
{% if dir[-1] != '/' %}
{% set dir = dir + '/' %}
{% endif %}
{% endif %}
{{ dir }}
sub_dir: >-
{% set dir = downloader_sub | trim %}
{% if dir | length > 0 %}
{% if dir[-1] != '/' %}
{% set dir = dir + '/' %}
{% endif %}
{% endif %}
{{ dir }}
video: "/api/frigate/notifications/{{ id }}/{{ camera }}/clip.mp4"
video_ios: "/api/frigate/notifications/{{ id }}/{{ camera }}/master.m3u8"
video_local: "{{ root_dir }}{{ sub_dir }}{{ id }}_clip.mp4"
thumb: "/api/frigate/notifications/{{ id }}/thumbnail.jpg"
image: "/api/frigate/notifications/{{ id }}/snapshot.jpg"
image_local: "{{ root_dir }}{{ sub_dir }}{{ id }}_snapshot.jpg"
gif: "/api/frigate/notifications/{{ review_id }}/review_preview.gif"
custom_msg: !input in_custom_msg
title_image: !input in_title_image
title_clip: !input in_title_clip
attachment1: !input in_attachment1
attachment2: !input in_attachment2
action1: !input in_action1
action2: !input in_action2
ca1_in: !input click_action1
ca2_in: !input click_action2
button1_title: !input in_button1_title
button1_action: !input in_button1_action
button2_title: !input in_button2_title
button2_action: !input in_button2_action
button3_title: !input in_button3_title
button3_action: !input in_button3_action
button1_title2: !input in_button1_title2
button1_action2: !input in_button1_action2
button2_title2: !input in_button2_title2
button2_action2: !input in_button2_action2
button3_title2: !input in_button3_title2
button3_action2: !input in_button3_action2
call_number: !input in_call_number
response_out: "No Response"
mode: parallel
max: 4