# Fix Group and Channel to First Camera Entity, user edits - DONE # Add time to notificaion title or message, with user choose between 12/24h - DONE # Take snapshot of triggered camera entity if using multiple cameras (? help needed) # Snooze blueprint or notifications blueprint: name: AI Event Summary (v1.5.2) author: valentinfrlch homeassistant: min_version: 2024.10.0 description: > AI-powered summaries for security camera events. Sends a notification with a preview to your phone that is updated dynamically when the AI summary is available. domain: automation source_url: https://github.com/valentinfrlch/ha-llmvision/blob/main/blueprints/event_summary.yaml input: run_conditions: name: Run Conditions description: All conditions must be true in order for the blueprint to run. default: [] selector: condition: cooldown: name: Cooldown description: Time to wait before running automation again. Strongly recommended for busy areas. default: minutes: 10 selector: duration: {} # Camera & Sensor Section camera_sensor_section: name: Camera & Sensor Settings description: Settings for the camera and sensor entities. icon: mdi:camera input: camera_entities: name: Camera Entities description: List of camera entities to monitor default: [] selector: entity: multiple: true filter: domain: camera trigger_state: name: Trigger State description: Automation starts when one of your camera changes to this state. default: 'recording' selector: text: multiline: false motion_sensors: name: Motion Sensor description: Set if your cameras don't change state (Frigate). Use the same order used for camera entities. default: [] selector: entity: multiple: true filter: domain: binary_sensor duration: name: Duration description: Duration to record for analysis (in seconds). default: 5 selector: number: min: 1 max: 60 max_frames: name: Max Frames description: How many frames to analyze. Picks frames with the most movement. default: 3 selector: number: min: 1 max: 60 # AI Section ai_section: name: AI Settings description: AI settings for the analysis. icon: mdi:brain collapsed: true input: remember: name: Store in Timeline description: Stores this event in the Timeline so you can ask about it. If important is enabled, only events classified as Normal or Critical will be saved. default: true selector: boolean: use_memory: name: Use Memory description: 'Use information stored in memory to provide additional context. Memory must be set up.' default: false selector: boolean: message: name: Prompt description: Model prompt for the video_analyzer action default: "Summarize the events based on a series of images captured at short intervals. Focus only on moving subjects such as people, vehicles, and other active elements. Ignore static objects and scenery. Provide a clear and concise account of movements and interactions. Do not mention or imply the existence of images—present the information as if directly observing the events. If no movement is detected, respond with: 'No activity observed.'" selector: text: multiline: true provider: name: Provider description: Provider to use for analysis. See docs for additional information. selector: config_entry: integration: llmvision model: name: Model description: Which model to use. Depends on chosen provider. selector: text: multiline: false target_width: name: Target Width description: Downscale images (uses less tokens and speeds up processing) default: 1280 selector: number: min: 512 max: 3840 max_tokens: name: Maximum Tokens description: "Maximum number of tokens to generate. **Note: Newer and reasoning models may require significantly more tokens. At least 5000 is recommended.**" default: 5000 selector: number: # Notificaion Section notification_section: name: Notification Settings description: Settings for the notification delivery. icon: mdi:bell collapsed: true input: # Notfify a Device? notify: name: Notify description: Send a notification to your phone. default: true selector: boolean: # Condition to Notify condition_notify: name: Condition to Notify description: Condition to notify the device. default: [] selector: condition: notify_device: name: Notify Device description: The devices to send the notification to. Multiple devices may be used. Only works with Home Assistant mobile app. default: [] selector: device: multiple: true filter: integration: mobile_app notification_delivery: name: Notification Delivery description: "Controls how notifications are delivered. \n \n **Dynamic** immediately notifies with a Live Preview (iOS only) or Snapshot before analysis and updates the notification silently with a summary once it is available, or after analysis. \n **Consolidated** Delays the notification until the event summary is generated. Use this if you're receiving multiple notifications for the same event." default: 'Dynamic' selector: select: options: - Dynamic - Consolidated preview_mode: name: Preview Mode description: |- Choose between a live preview or a snapshot of the event before analysis. > [!IMPORTANT] > **IMPORTANT**: Live Preview is only supported on iOS. default: Snapshot selector: select: options: - Snapshot - Live Preview analysis_mode: name: Analysis Mode description: |- Choose between a live preview or a snapshot of the event after analysis. > [!IMPORTANT] > **IMPORTANT**: Live Preview is only supported on iOS. default: Snapshot selector: select: options: - Snapshot - Live Preview notification_time: name: Notification Time description: "Add time to Notification Title and choose between 12-hour or 24-hour time format for the notification title." default: '' selector: select: options: - label: No Time Added value: '' - label: 12 Hour value: 'at {{ now().strftime("%-I:%M %p") }}' - label: 24 Hour value: 'at {{ now().strftime("%H:%M") }}' file_path: name: File Path - To send snapshot in Dynamic Mode with Snapshot Preview Mode description: "The file path to store the most current snapshot for the FIRST camera if using multiple camera entities. You can specifiy a custom location as well.\n\nDefaults to `Media Folder - Secured` that references **/media/snapshots//last_motion.jpg** \n\n Try the `Public Folder - Unsecured` option that references **/config/www/snapshots/{{ camera_file_path }}/last_motion.jpg** if you are having issues ***Note that this is unsecured and exposes the images to the web.***" default: '/media/llmvision/snapshots_blueprint/{{ camera_file_path }}/last_motion.jpg' selector: select: options: - label: Media Folder - Secured value: '/media/llmvision/snapshots_blueprint/{{ camera_file_path }}/last_motion.jpg' - label: Public Folder - Unsecured value: '/config/www/snapshots/{{ camera_file_path }}/last_motion.jpg' custom_value: true tap_navigate: name: Tap Navigate description: >- Home Assistant dashboard to navigate to when notification is opened (e.g. /lovelace/cameras). default: /lovelace/0 selector: text: multiline: false # Delay Notification delay_notification: name: Notification Cooldown description: Time in seconds to wait before sending another notification. default: 60 selector: number: min: 0.0 max: 86400.0 unit_of_measurement: seconds mode: box step: 1.0 notification_sticky: name: Sticky - Android Only description: 'When enabled, the notification will stay active on the device after tapping it and remain unless swiped. ' default: true selector: boolean: {} notification_color: name: Notification Color - Android Only description: 'Set the color of the notification on your Android device, in HEX. (default = Steelblue - #03a9f4)' default: '#03a9f4' selector: select: options: - label: 'Steelblue - #03a9f4' value: '#03a9f4' - label: 'Red - #f44336' value: '#f44336' - label: 'Pink - #e91e63' value: '#e91e63' - label: 'Purple - #926bc7' value: '#926bc7' - label: 'Deep Purple - #6e41ab' value: '#6e41ab' - label: 'Indigo - #3f51b5' value: '#3f51b5' - label: 'Blue - #2196f3' value: '#2196f3' - label: 'Light Blue - #03a9f4' value: '#03a9f4' - label: 'Cyan - #00bcd4' value: '#00bcd4' - label: 'Teal - #009688' value: '#009688' - label: 'Green - #4caf50' value: '#4caf50' - label: 'Light Green - #8bc34a' value: '#8bc34a' - label: 'Lime - #cddc39' value: '#cddc39' - label: 'Yellow - #ffeb3b' value: '#ffeb3b' - label: 'Amber - #ffc107' value: '#ffc107' - label: 'Orange - #ff9800' value: '#ff9800' - label: 'Deep Orange - #ff5722' value: '#ff5722' - label: 'Brown - #795548' value: '#795548' - label: 'Light Grey - #bdbdbd' value: '#bdbdbd' - label: 'Grey - #9e9e9e' value: '#9e9e9e' - label: 'Dark Grey - #606060' value: '#606060' - label: 'Blue Grey - #607d8b' value: '#607d8b' - label: 'Black - #000000' value: '#000000' - label: White -#ffffff value: '#ffffff' custom_value: true sort: false multiple: false notification_icon: name: Notification Status Bar Icon - Android Only description: Change the icon that displays in the notification. default: mdi:cctv selector: icon: placeholder: mdi:cctv notification_channel: name: Custom Notification Channel or Alarm Mode - Android Only description: Create a new channel for notifications to allow custom notification sounds, vibration patterns, and override Do Not Disturb mode. Camera Name uses first Camera Entity if using multiple cameras. Configured directly on the Android device -> Home Assistant App Setting -> Notifications. Use `Alarm` to use the device's loud alarm notification sound. You can also type in your own channel name. [Learn More](https://companion.home-assistant.io/docs/notifications/notification-commands/#volume-level) (default = Camera Name == {{ camera_entity_snapshot }} Snapshot). default: '{{ camera }} Snapshot' selector: select: options: - label: Camera Name value: '{{ camera }} Snapshot' - label: Alarm value: alarm_stream custom_value: true # TTS Notification tts_notification: name: Use Text-to-Speech (TTS) on your device to speak the notification. Android Only description: Enable TTS notification. default: false selector: boolean: tts_volume: name: TTS Volume (Android Only) description: |- Set TTS volume to device's current alarm volume or Max volume. > [!NOTE] > ALARM VOLUME WILL IGNORE DO NOT DISTURB! default: 'notification_stream' selector: select: options: - label: Notification Volume value: notification_stream - label: Alarm Volume/Sound value: alarm_stream - label: Max Alarm Volume/Sound value: alarm_stream_max # Condition to TTS Notify condition_notify_tts: name: Condition to TTS Notify description: Condition to TTS notify the device. default: [] selector: condition: notification_sound: name: Notification Sound - iOS Only description: 'You can specify a sound file on your device that will play for the notifications. You can import the sound file into Home Assistant or enter the filename of the [pre-installed notification sound](https://companion.home-assistant.io/docs/notifications/notification-sounds/#pre-installed-notification-sounds) (example: US-EN-Alexa-Motion-Detected-Generic.wav).' default: default selector: select: options: - label: Default value: default - label: None value: none - label: Alexa Motion Detected value: US-EN-Alexa-Motion-Detected-Generic.wav - label: Morgan Freeman Motion Detected value: US-EN-Morgan-Freeman-Motion-Detected.wav custom_value: true sort: false multiple: false notification_volume: name: Volume Sound - iOS Only description: Specify a sound level % default: 1 selector: number: max: 1.0 min: 0.0 unit_of_measurement: '%' step: 1.0 mode: slider notification_critical: name: Critical Notification - iOS Only description: Send as a critical notification to the mobile device. This will ignore silent/vibrate modes. default: false selector: boolean: {} # Experimental Section experimental_section: name: Experimental Settings description: Experimental features. Use with caution. icon: mdi:apple-keyboard-option collapsed: true input: important: name: Important (Experimental) description: > Use AI to classify events as Critical, Normal or Low. Notifications are sent only for events classified as Normal or higher. Critical events override 'Do Not Disturb' settings. Use with caution: AI can make mistakes. default: false selector: boolean: # Add Customized Actions additional_actions: name: Additional Actions (Experimental) description: Additional actions to run after the AI analysis and notification. Some actions don't work and will prevent the automation from running, use with caution. default: [] selector: action: variables: important: !input important remember: !input remember cooldown: !input cooldown preview_mode: !input preview_mode analysis_mode: !input analysis_mode notify_devices: !input notify_device notification_delivery: !input notification_delivery notification_sticky: !input notification_sticky notification_color: !input notification_color notification_icon: !input notification_icon notification_channel: !input notification_channel notification_sound: !input notification_sound notification_volume: !input notification_volume notification_critical: !input notification_critical tts_volume: !input tts_volume tts_notification: !input tts_notification condition_notify_tts: !input condition_notify_tts additional_actions: !input additional_actions notify: !input notify condition_notify: !input condition_notify delay_notification: !input delay_notification # camera_snapshot: !input camera_snapshot # camera_name: '{{ states[camera_snapshot].name.replace(" ", "_") }}' device_name_map: > {% 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 | slugify %} {% set ns.device_names = ns.device_names + [sanitized_name] %} {% endfor %} {{ ns.device_names }} camera_entities_list: !input camera_entities motion_sensors_list: !input motion_sensors camera_entity: > {% if motion_sensors_list and trigger is defined and trigger.entity_id is defined and not trigger.entity_id.startswith("camera") %} {% set sensor_index = motion_sensors_list.index(trigger.entity_id) if trigger.entity_id in motion_sensors_list else 0 %} {{ camera_entities_list[sensor_index] if sensor_index < camera_entities_list|length else camera_entities_list[0] }} {% elif trigger is defined and trigger.entity_id is defined %} {{ trigger.entity_id }} {% else %} {{ camera_entities_list[0] if camera_entities_list else '' }} {% endif %} tag: > {{ camera_entity + int(as_timestamp(now()))|string }} label: Motion detected camera_message: > {{ camera_entity.replace("camera.", "").replace("_", " ")|capitalize }} camera: > {{ camera_entities_list[0].replace("camera.", "").replace("_", " ") | title }} importance_prompt: > Classify the security event based on this image. Choose from the following options: "passive" for unimportant events, "time-sensitive" for notable but non-critical events such as a person at the front door, and "critical" only for potential burglaries or highly suspicious activity. Respond with one of these options exactly, without additional explanation. camera_entity_snapshot: > {{ camera_entities_list[0] }} camera_file_path: > {{ camera_entity_snapshot.replace("camera.", "")}} file_path: !input file_path snapshot_access_file_path: '{{ file_path | replace(''/config/www'', ''/local'') | replace(''/media'', ''/media/local'') }}' notification_time: !input notification_time max_exceeded: silent mode: single trigger: - platform: state entity_id: !input camera_entities to: !input trigger_state id: 'camera_trigger' - platform: state entity_id: !input motion_sensors to: 'on' id: 'motion_sensor_trigger' condition: - condition: and conditions: !input run_conditions action: - choose: - conditions: - condition: template value_template: "{{ important }}" sequence: - action: llmvision.image_analyzer data: image_entity: "{{[camera_entity]}}" provider: !input provider model: !input model message: "{{importance_prompt}}" include_filename: true target_width: 1280 max_tokens: 3 response_variable: importance # Cancel automation if event not deemed important - choose: - conditions: - condition: template value_template: "{{ important and importance.response_text|lower == 'passive' }}" sequence: - stop: "Event is not important" # Only send notification if enabled, and if condtions and cooldown are met - if: - condition: template value_template: !input notify then: - if: - condition: !input condition_notify then: - if: - condition: template value_template: '{{ not this.attributes.last_triggered or (now() - this.attributes.last_triggered).seconds > delay_notification }}' then: - choose: - conditions: - condition: template value_template: "{{ notification_delivery == 'Dynamic' }}" sequence: - choose: - conditions: - condition: template value_template: "{{ preview_mode=='Snapshot' }}" sequence: # Save Camera Snapshot to file to send to Android devices for Dynamic Mode - service: camera.snapshot entity_id: !input camera_entities data: filename: !input file_path - alias: "Send instant notification snapshot to notify devices" repeat: for_each: "{{device_name_map}}" sequence: - action: "notify.{{ repeat.item }}" data: title: "{{ label }} {{ notification_time }}" message: "{{camera_message}} has detected activity." data: url: !input tap_navigate #iOS clickAction: !input tap_navigate #Android tag: "{{tag}}" group: !input notification_channel alert_once: true # Priority options ttl: 0 priority: high # Android only channel: !input notification_channel color: !input notification_color notification_icon: !input notification_icon sticky: !input notification_sticky image: "{{ snapshot_access_file_path }}" # iOS only attachment: url: '{{ snapshot_access_file_path }}' content_type: JPEG push: interruption-level: "{{importance.response_text|lower if importance is defined else 'active'}}" sound: name: !input notification_sound volume: !input notification_volume critical: '{{ notification_critical }}' - conditions: - condition: template value_template: "{{ preview_mode=='Live Preview' }}" sequence: - alias: "Send Live Feed to notify devices" repeat: for_each: "{{device_name_map}}" sequence: - action: "notify.{{ repeat.item }}" data: title: "{{ label }} {{ notification_time }}" message: "{{camera_message}} has detected activity." data: entity_id: "{{camera_entity}}" url: !input tap_navigate tag: "{{tag}}" group: !input notification_channel alert_once: true push: interruption-level: "{{importance.response_text|lower if importance is defined else 'active'}}" sound: name: !input notification_sound volume: !input notification_volume critical: '{{ notification_critical }}' # Priority options ttl: 0 priority: high - alias: "Analyze event" action: llmvision.stream_analyzer data: image_entity: "{{[camera_entity]}}" duration: !input duration provider: !input provider model: !input model message: !input message use_memory: !input use_memory remember: !input remember expose_images: "{{preview_mode == 'Snapshot' or remember}}" generate_title: !input remember include_filename: true max_frames: !input max_frames target_width: !input target_width max_tokens: !input max_tokens response_variable: response - alias: "Update label with title" variables: label: "{{response.title}}" # Only send notification if enabled, and if condtions and cooldown are met - if: - condition: template value_template: !input notify then: - if: - condition: !input condition_notify then: - if: - condition: template value_template: '{{ not this.attributes.last_triggered or (now() - this.attributes.last_triggered).seconds > delay_notification }}' then: - choose: - conditions: - condition: template value_template: "{{ analysis_mode=='Snapshot' }}" sequence: - alias: "(Snapshot) Update notification on notify devices" repeat: for_each: "{{device_name_map}}" sequence: - action: "notify.{{ repeat.item }}" data: title: "{{ label }} {{ notification_time }}" message: "{{response.response_text}}" data: image: "{{response.key_frame.replace('/media','/media/local') }}" url: !input tap_navigate #iOS clickAction: !input tap_navigate #Android tag: "{{tag}}" group: !input notification_channel alert_once: true # Priority options ttl: 0 priority: high # Android only channel: !input notification_channel color: !input notification_color notification_icon: !input notification_icon sticky: !input notification_sticky # iOS only push: interruption-level: "{{'passive' if notification_delivery=='Dynamic' else 'active'}}" sound: name: !input notification_sound volume: !input notification_volume critical: '{{ notification_critical }}' actions: - action: 'URI' title: 'See Snapshot' uri: "{{response.key_frame.replace('/media','/media/local') }}" - conditions: - condition: template value_template: "{{ analysis_mode=='Live Preview' }}" sequence: - alias: "Send Live Feed to notify devices" repeat: for_each: "{{device_name_map}}" sequence: - action: "notify.{{ repeat.item }}" data: title: "{{ label }} {{ notification_time }}" message: "{{ response.response_text }}" data: entity_id: "{{camera_entity}}" url: !input tap_navigate tag: "{{tag}}" group: !input notification_channel alert_once: true push: interruption-level: "{{importance.response_text|lower if importance is defined else 'active'}}" sound: name: !input notification_sound volume: !input notification_volume critical: '{{ notification_critical }}' # Priority options ttl: 0 priority: high actions: - action: 'URI' title: 'See Snapshot' uri: "{{response.key_frame.replace('/media','/media/local') }}" - if: - condition: template value_template: '{{ tts_notification }}' then: - if: - condition: !input condition_notify_tts then: - alias: "TTS notification on notify devices" repeat: for_each: "{{device_name_map}}" sequence: - action: "notify.{{ repeat.item }}" data: message: TTS data: ttl: 0 priority: high media_stream: !input tts_volume tts_text: "{{response.response_text}}" # Add Customized Actions - choose: [] default: !input additional_actions # - delay: '00:{{cooldown|int}}:00' - delay: !input cooldown