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