Skip to content

Instantly share code, notes, and snippets.

@dskindell
Last active November 1, 2024 08:40
Show Gist options
  • Save dskindell/6711ce2e2348ea0be0ecc664ea1438d4 to your computer and use it in GitHub Desktop.
Save dskindell/6711ce2e2348ea0be0ecc664ea1438d4 to your computer and use it in GitHub Desktop.
Fork of valentinfrlch/ha-llmvision blueprints/event_summary.yaml to support other notification platforms
blueprint:
name: AI Event Summary (LLM Vision)
author: valentinfrlch
description: >
AI-powered security event summaries for frigate or camera entities.
Sends a notification with a preview to your phone that is updated dynamically when the AI summary is available.
domain: automation
input:
mode:
name: Mode
description: Select the mode to use
selector:
select:
options:
- 'Frigate'
- 'Camera'
important:
name: Important
description: "Use AI to notify only on important events. Use with caution: AI can make mistakes."
default: false
selector:
boolean:
notify_device:
name: Notify Device
description: The device to send the notification to
selector:
device:
integration: mobile_app
frigate_url:
name: Frigate URL
description: (Frigate) Frigate's base url to fetch preview
default: "http://localhost:5000"
selector:
text:
multiline: false
camera_entities:
name: Camera Entities
description: (Camera) List of camera entities to monitor
default: []
selector:
entity:
multiple: true
filter:
domain: camera
motion_sensors:
name: Motion Sensor
description: (Camera) Set if your cameras don't change state. Use the same order used for camera entities.
default: []
selector:
entity:
multiple: true
filter:
domain: binary_sensor
trigger_state:
name: Trigger State
description: (Camera) Trigger the automation when your cameras change to this state.
default: 'recording'
selector:
text:
multiline: false
tap_navigate:
name: Tap Navigate
description: Path to navigate to when tapping notification (e.g. /lovelace/cameras)
default: "/lovelace/0"
selector:
text:
multiline: false
cooldown:
name: Cooldown
description: Time in minutes to wait before running again.
default: 10
selector:
number:
min: 0
max: 60
provider:
name: Provider
description: Configuration to use for the video_analyzer service. See docs for additional information.
selector:
config_entry:
integration: llmvision
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
duration:
name: Duration
description: (Camera) How long to record for (in seconds)
default: 5
selector:
number:
min: 1
max: 60
model:
name: Model
description: Model to use for the video_analyzer service
default: "gpt-4o-mini"
selector:
text:
multiline: false
message:
name: Prompt
description: Model prompt for the video_analyzer service
default: "Summarize briefly what's happening in the camera feed (one sentence max). Don't describe the scene! If there is a person, describe what they're doing. If nothing is happening, say so."
selector:
text:
multiline: true
target_width:
name: Target Width
description: Width in pixels to downscale (uses less tokens)
default: 1280
selector:
number:
min: 512
max: 3840
detail:
name: Detail
description: Detail parameter (OpenAI only)
default: 'low'
selector:
select:
options:
- 'high'
- 'low'
max_tokens:
name: Maximum Tokens
description: Maximum number of tokens to generate
default: 20
selector:
number:
min: 1
max: 100
temperature:
name: Temperature
description: Randomness. Lower is more accurate, higher is more creative
default: 0.1
selector:
number:
min: 0.1
max: 1.0
step: 0.1
variables:
important: !input important
base_url: !input frigate_url
cooldown: !input cooldown
mode: !input mode
camera_entities_list: !input camera_entities
motion_sensors_list: !input motion_sensors
camera_entity: >
{% if mode == 'Camera' %}
{% if motion_sensors_list %}
{% set index = motion_sensors_list.index(trigger.entity_id) %}
{{ camera_entities_list[index] }}
{% else %}
{{ trigger.entity_id }}
{% endif %}
{% else %}
{{ trigger.payload_json['after']['camera'] }}
{% endif %}
tag: >
{% if mode == 'Frigate' %}
{{ trigger.payload_json['after']['camera'] + int(as_timestamp(now()))|string }}
{% else %}
{{ camera_entity + int(as_timestamp(now()))|string }}
{% endif %}
group: >
{% if mode == 'Frigate' %}
{{ trigger.payload_json['after']['camera'] }}
{% else %}
{{ camera_entity }}
{% endif %}
label: >
{% if mode == 'Frigate' %}
{{ trigger.payload_json['after']['label']|capitalize }} seen
{% else %}
Motion detected
{% endif %}
camera: >
{% if mode == 'Frigate' %}
{{ trigger.payload_json['after']['camera'].replace('_', ' ')|capitalize }}
{% else %}
{{ camera_entity.replace("camera.", "").replace("_", " ")|capitalize }}
{% endif %}
video: >
{% if mode == 'Frigate' %}
{{ base_url }}/api/events/{{ trigger.payload_json['after']['id'] }}/clip.mp4
{% else %}
''
{% endif %}
image: >
{% if mode == 'Frigate' %}
''
{% else %}
{{ '/api/camera_proxy/' + camera_entity }}
{% endif %}
importance_prompt: >
Your job is to decide whether a camera recording is important (so the user gets a notification) or not.
Reply with 'yes' if the user should be notified or 'no' otherwise.
Reply with these replies exactly.
trigger:
- platform: mqtt
topic: "frigate/events"
id: frigate_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: template
value_template: >
{% if mode == 'Frigate' %}
{{ trigger.payload_json["type"] == "end" and (state_attr(this.entity_id, 'last_triggered') is none or (now() - state_attr(this.entity_id, 'last_triggered')).total_seconds() / 60 > cooldown) }}
{% else %}
{{ state_attr(this.entity_id, 'last_triggered') is none or (now() - state_attr(this.entity_id, 'last_triggered')).total_seconds() / 60 > cooldown }}
{% endif %}
action:
- choose:
- conditions:
- condition: template
value_template: "{{ important }}"
sequence:
- alias: "Decide Important"
choose:
- conditions:
- condition: template
value_template: "{{ mode == 'Frigate'}}"
sequence:
- service: llmvision.image_analyzer
data:
image_entity: "{{ ['camera.' + trigger.payload_json['after']['camera']|lower] }}"
provider: !input provider
model: !input model
message: "{{importance_prompt}}"
include_filename: true
target_width: 1280
detail: low
max_tokens: 1
temperature: 0.1
response_variable: importance
- conditions:
- condition: template
value_template: "{{ mode == 'Camera' }}"
sequence:
- service: llmvision.image_analyzer
data:
image_entity: "{{[camera_entity]}}"
provider: !input provider
model: !input model
message: "{{importance_prompt}}"
include_filename: true
target_width: 1280
detail: low
max_tokens: 1
temperature: 0.1
response_variable: importance
# Cancel automation if event not deemed important
- choose:
- conditions:
- condition: template
value_template: "{{ importance is defined and importance.response_text == 'no' }}"
sequence:
- stop: "Event is not important"
- choose:
- conditions:
- condition: template
value_template: "{{ base_url is not none and base_url != '' }}"
sequence:
- alias: "Send instant notification without summary"
domain: mobile_app
type: notify
device_id: !input notify_device
title: "{{ label }}"
message: "{{camera}}"
data:
video: "{{video}}"
image: "{{image}}"
entity_id: "{{camera_entity if mode=='Camera' else ''}}"
url: !input tap_navigate #iOS
clickAction: !input tap_navigate #Android
tag: "{{tag}}"
group: "{{group}}"
- alias: "Analyze event"
choose:
- conditions:
- condition: template
value_template: "{{ mode == 'Frigate' }}"
sequence:
- service: llmvision.video_analyzer
data:
event_id: "{{ trigger.payload_json['after']['id'] }}"
provider: !input provider
model: !input model
message: !input message
include_filename: true
max_frames: !input max_frames
target_width: !input target_width
detail: !input detail
max_tokens: !input max_tokens
temperature: !input temperature
response_variable: response
- conditions:
- condition: template
value_template: "{{ mode == 'Camera' }}"
sequence:
- service: llmvision.stream_analyzer
data:
image_entity: "{{[camera_entity]}}"
duration: !input duration
provider: !input provider
model: !input model
message: !input message
include_filename: true
max_frames: !input max_frames
target_width: !input target_width
detail: !input detail
max_tokens: !input max_tokens
temperature: !input temperature
response_variable: response
- alias: "Send notification with summary"
domain: mobile_app
type: notify
device_id: !input notify_device
title: "{{label}}"
message: "{{ response.response_text }}"
data:
tag: "{{tag}}"
group: "{{group}}"
image: "{{image}}"
video: "{{video}}"
entity_id: "{{camera_entity if mode=='Camera' else ''}}"
url: !input tap_navigate #iOS
clickAction: !input tap_navigate #Android
push:
interruption-level: passive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment