Compare commits

...

10 Commits

Author SHA1 Message Date
13132323ea Add debug logging and quiet hours support to Immich Album Watcher Telegram notifications
- Add debug persistent notifications after all 13 send_telegram_notification calls
  (periodic summary, scheduled per-album/combined, memory per-album/combined,
  album renamed/deleted, assets added text/media) logging chat ID, caption,
  assets count, reply-to ID, and service response when debug mode is enabled
- Replace removed ignore_quiet_hours parameter with new quiet_hours_start and
  quiet_hours_end time inputs (defaults: 23:00-07:00) on all Telegram service calls
- Update README with quiet hours documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 00:42:26 +03:00
a6ac4f4257 Add debug logging input and skip redundant on/off commands in Climate Device Controller
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:42:02 +03:00
584c970044 Make power_sensor optional in Climate Device Controller
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:27:48 +03:00
4c03bc849e Several minor updates to the existing blueprints 2026-02-28 22:10:11 +03:00
eefc8993e3 Add min_brightness setting to Light Color Mapper
Adds a minimum brightness floor (default: 5) that prevents lights from
becoming too dim. Applied to both sensor brightness and default color paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 00:51:03 +03:00
9d19dfa8d3 Fix multiple mode:restart state machine bugs in Motion Light
- Fix brightness threshold falsely resetting state to NONE during light
  transition ramp-up (brightness temporarily below threshold != light off)
- Fix false manual override from Zigbee attribute-only updates (on→on)
  by requiring meaningful state change (on→off or off→on)
- Fix disable guard to also accept ENABLING state (not just ENABLED)
- Move state updates before service calls to survive mode:restart cancellation
- Add ENABLING sub-case handler for motion-cleared-during-enable scenario
- Add CASE 1 default handler for restart recovery disable path
- Add comprehensive debug logging at automation entry and CASE 1 entry
- Change default timeout_delay to 0 and grace_period to 2s
- Remove unused is_debug/is_base_debug variables

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 01:57:45 +03:00
e98df855d9 Fix false manual override detection and brightness threshold guard in Motion Light
- Add configurable grace period (default 10s) to prevent false manual
  override detection from delayed device state reports (e.g., Zigbee)
- Fix any_device_on guard to respect brightness_threshold so lights
  below the threshold don't block the automation from triggering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:28:16 +03:00
29b1044d63 Add Light Color Mapper blueprint
Maps color sensor states to lights in real-time with index-based pairing.
Supports FPS throttling, brightness override, configurable behavior when
sensors are unavailable, and a post-update callback action.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:28:07 +03:00
006f9e532c Add {year} variable to asset templates in Immich Album Watcher
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:27:59 +03:00
9383f957b6 Add Dreame Vacuum Notifications blueprint
Sends customizable notifications for Dreame vacuum events (cleaning
status, consumable alerts, warnings, errors, and informational messages).
Requires the Dreame Vacuum integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:27:51 +03:00
10 changed files with 1784 additions and 145 deletions

View File

@@ -237,8 +237,11 @@ blueprint:
collapsed: false collapsed: false
input: input:
power_sensor: power_sensor:
name: Power Sensor name: Power Sensor (optional)
description: "Sensor reporting device power consumption (W)" description: >
Sensor reporting device power consumption (W).
Leave empty to disable power monitoring.
default:
selector: selector:
entity: entity:
domain: sensor domain: sensor
@@ -277,6 +280,22 @@ blueprint:
entity: entity:
domain: input_boolean domain: input_boolean
# -------------------------------------------------------------------------
# Debug
# -------------------------------------------------------------------------
debug_group:
name: "Debug"
collapsed: true
input:
enable_debug_notifications:
name: Enable Debug Notifications
description: >
Send persistent notifications for debugging automation behavior.
Shows current state of all variables and filtering decisions.
default: false
selector:
boolean:
# ============================================================================= # =============================================================================
# AUTOMATION MODE # AUTOMATION MODE
# ============================================================================= # =============================================================================
@@ -321,13 +340,6 @@ trigger:
- platform: state - platform: state
entity_id: !input env_sensors entity_id: !input env_sensors
# Power sensor dropped below threshold (potential malfunction)
- platform: numeric_state
entity_id: !input power_sensor
below: !input power_threshold
for:
seconds: !input power_decay_duration
# ============================================================================= # =============================================================================
# CONDITIONS # CONDITIONS
# ============================================================================= # =============================================================================
@@ -349,6 +361,11 @@ action:
force_on_entity: !input force_on_entity force_on_entity: !input force_on_entity
hysteresis_window: !input hysteresis_window hysteresis_window: !input hysteresis_window
# -----------------------------------------------------------------------
# Device State
# -----------------------------------------------------------------------
is_device_on: "{{ states(device_entity) not in ['off', 'unavailable', 'unknown'] }}"
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Force ON Check # Force ON Check
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -406,13 +423,13 @@ action:
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Power Monitoring # Power Monitoring
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
power_threshold: !input power_threshold
power_sensor: !input power_sensor power_sensor: !input power_sensor
power_threshold: !input power_threshold
power_problematic_indicator_entity: !input power_problematic_indicator_entity power_problematic_indicator_entity: !input power_problematic_indicator_entity
power_decay_duration: !input power_decay_duration power_decay_duration: !input power_decay_duration
power: "{{ states(power_sensor) | float(0) }}" power: "{{ states(power_sensor) | float(0) if power_sensor is not none else 0 }}"
# Device is problematic if it's consuming power but below threshold # Device is problematic if it's consuming power but below threshold
is_power_not_ok: "{{ (power > 0 and power < power_threshold) if power_threshold != 0 else false }}" is_power_not_ok: "{{ (power > 0 and power < power_threshold) if power_sensor is not none and power_threshold != 0 else false }}"
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Window/Door State # Window/Door State
@@ -472,7 +489,7 @@ action:
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Debug Flag # Debug Flag
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
is_debug: false is_debug: !input enable_debug_notifications
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# DEBUG: Log current state # DEBUG: Log current state
@@ -482,18 +499,29 @@ action:
sequence: sequence:
- service: persistent_notification.create - service: persistent_notification.create
data: data:
title: "Climate Controller (debug)" title: "Climate Device Controller Debug"
message: > message: >
is_force_on = {{ is_force_on }}, **Device State:**
room_closed = {{ room_closed }}, - Device: {{ states(device_entity) }} (on: {{ is_device_on }})
house_closed = {{ house_closed }}, - Control Switch: {{ states(control_switch) }}
is_value_below_threshold = {{ is_value_below_threshold }}, - Force ON: {{ is_force_on }}
is_value_below_turn_on_threshold = {{ is_value_below_turn_on_threshold }}, - Schedule Active: {{ schedule_active }}
is_value_at_or_above_target = {{ is_value_at_or_above_target }},
target_value = {{ target_value }}, **Environment:**
turn_on_threshold = {{ turn_on_threshold }}, - Target Value: {{ target_value }}
schedule_active = {{ schedule_active }}, - Turn-On Threshold: {{ turn_on_threshold }} (hysteresis: {{ hysteresis_window }})
control_switch = {{ states(control_switch) }} - Below Emergency Threshold: {{ is_value_below_threshold }}
- Below Turn-On Threshold: {{ is_value_below_turn_on_threshold }}
- At/Above Target: {{ is_value_at_or_above_target }}
**Doors & Windows:**
- House Closed: {{ house_closed }}
- Room Closed: {{ room_closed }}
**Power Monitoring:**
- Power Sensor: {{ power_sensor | default('not configured') }}
- Power: {{ power }} W
- Power OK: {{ not is_power_not_ok }}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# POWER MONITORING: Flag device if malfunctioning # POWER MONITORING: Flag device if malfunctioning
@@ -503,7 +531,7 @@ action:
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ states(device_entity) not in ['off', 'unavailable', 'unknown'] and power_problematic_indicator_entity is not none }}" value_template: "{{ power_sensor is not none and states(device_entity) not in ['off', 'unavailable', 'unknown'] and power_problematic_indicator_entity is not none }}"
sequence: sequence:
- variables: - variables:
# Check if enough time has passed since last power reading # Check if enough time has passed since last power reading
@@ -562,7 +590,11 @@ action:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ is_force_on }}" value_template: "{{ is_force_on }}"
sequence: !input turn_on_action sequence:
- if:
- condition: template
value_template: "{{ not is_device_on }}"
then: !input turn_on_action
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# PRIORITY 2: Emergency Override (Safety) # PRIORITY 2: Emergency Override (Safety)
@@ -571,7 +603,11 @@ action:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ is_value_below_threshold }}" value_template: "{{ is_value_below_threshold }}"
sequence: !input turn_on_action sequence:
- if:
- condition: template
value_template: "{{ not is_device_on }}"
then: !input turn_on_action
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# PRIORITY 3: Control Switch Off # PRIORITY 3: Control Switch Off
@@ -580,7 +616,11 @@ action:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ is_state(control_switch, 'off') }}" value_template: "{{ is_state(control_switch, 'off') }}"
sequence: !input turn_off_action sequence:
- if:
- condition: template
value_template: "{{ is_device_on }}"
then: !input turn_off_action
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# PRIORITY 4: Environment Not Ready # PRIORITY 4: Environment Not Ready
@@ -589,7 +629,11 @@ action:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ not (house_closed or room_closed) or not schedule_active }}" value_template: "{{ not (house_closed or room_closed) or not schedule_active }}"
sequence: !input turn_off_action sequence:
- if:
- condition: template
value_template: "{{ is_device_on }}"
then: !input turn_off_action
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# PRIORITY 5: Target Reached # PRIORITY 5: Target Reached
@@ -598,7 +642,11 @@ action:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ is_value_at_or_above_target }}" value_template: "{{ is_value_at_or_above_target }}"
sequence: !input turn_off_action sequence:
- if:
- condition: template
value_template: "{{ is_device_on }}"
then: !input turn_off_action
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# PRIORITY 6: Below Turn-On Threshold # PRIORITY 6: Below Turn-On Threshold
@@ -607,7 +655,11 @@ action:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ is_value_below_turn_on_threshold }}" value_template: "{{ is_value_below_turn_on_threshold }}"
sequence: !input turn_on_action sequence:
- if:
- condition: template
value_template: "{{ not is_device_on }}"
then: !input turn_on_action
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# DEFAULT: Maintain Current State (Hysteresis Zone) # DEFAULT: Maintain Current State (Hysteresis Zone)

View File

@@ -0,0 +1,94 @@
# Dreame Vacuum Notifications
Sends customizable notifications for Dreame vacuum events. Requires the [Dreame Vacuum](https://github.com/Tasshack/dreame-vacuum) integration.
## Features
- Notifications for cleaning started and completed
- Consumable end-of-life alerts (brush, filter, mop pad, sensor, etc.)
- Device warning and error notifications
- Informational alerts (e.g., action blocked by Do Not Disturb)
- Individual toggle for each event type
- Customizable message templates with variable substitution
- Multiple notification targets
## How It Works
The blueprint listens to events fired by the Dreame Vacuum integration:
| Event | Description |
| --- | --- |
| `dreame_vacuum_task_status` | Cleaning job started or completed |
| `dreame_vacuum_consumable` | Consumable reached end-of-life |
| `dreame_vacuum_warning` | Dismissible device warning |
| `dreame_vacuum_error` | Device fault |
| `dreame_vacuum_information` | Action blocked by user settings |
Events are filtered by the configured vacuum entity, so only the selected vacuum triggers notifications.
## Configuration
| Input | Description |
| --- | --- |
| **Vacuum Entity** | The Dreame vacuum entity to monitor |
| **Notification Targets** | One or more `notify` entities |
| **Event Toggles** | Enable/disable each event type independently |
| **Message Templates** | Customizable message for each event type |
## Message Template Variables
### Cleaning Started
| Variable | Description |
| --- | --- |
| `{vacuum_name}` | Friendly name of the vacuum |
| `{cleaning_mode}` | Cleaning mode (e.g., sweeping, mopping) |
| `{status}` | Current status |
### Cleaning Completed
| Variable | Description |
| --- | --- |
| `{vacuum_name}` | Friendly name of the vacuum |
| `{cleaning_mode}` | Cleaning mode used |
| `{status}` | Final status |
| `{cleaned_area}` | Area cleaned (m²) |
| `{cleaning_time}` | Cleaning duration (minutes) |
### Consumable Depleted
| Variable | Description |
| --- | --- |
| `{vacuum_name}` | Friendly name of the vacuum |
| `{consumable}` | Consumable name (e.g., Main Brush, Side Brush, Filter, Mop Pad) |
### Warning
| Variable | Description |
| --- | --- |
| `{vacuum_name}` | Friendly name of the vacuum |
| `{warning}` | Warning description |
| `{code}` | Warning code |
### Error
| Variable | Description |
| --- | --- |
| `{vacuum_name}` | Friendly name of the vacuum |
| `{error}` | Error description |
| `{code}` | Error code |
### Information
| Variable | Description |
| --- | --- |
| `{vacuum_name}` | Friendly name of the vacuum |
| `{information}` | Information message (e.g., Dust Collection, Cleaning Paused) |
## Debug Mode
Enable **Debug Notifications** in the Debug section to send persistent notifications with raw event data for troubleshooting.
## Author
Alexei Dolgolyov (<dolgolyov.alexei@gmail.com>)

View File

@@ -0,0 +1,424 @@
# Dreame Vacuum Notifications
# Sends notifications for Dreame vacuum events (cleaning, errors, consumables).
# See README.md for detailed documentation.
#
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
blueprint:
name: "Custom: Dreame Vacuum Notifications"
description: >
Sends customizable notifications for Dreame vacuum events including
cleaning status, consumable alerts, warnings, and errors.
Requires the Dreame Vacuum integration (github.com/Tasshack/dreame-vacuum).
domain: automation
# ===========================================================================
# INPUT CONFIGURATION
# ===========================================================================
input:
# -------------------------------------------------------------------------
# Vacuum Configuration
# -------------------------------------------------------------------------
vacuum_group:
name: "Vacuum"
collapsed: false
input:
vacuum_entity:
name: Vacuum Entity
description: "The Dreame vacuum entity to monitor."
selector:
entity:
integration: dreame_vacuum
domain: vacuum
# -------------------------------------------------------------------------
# Notification Configuration
# -------------------------------------------------------------------------
notification_group:
name: "Notification"
collapsed: false
input:
notify_targets:
name: Notification Targets
description: "Notification service entities to send messages to (select one or more)."
selector:
entity:
domain: notify
multiple: true
# -------------------------------------------------------------------------
# Event Toggles
# -------------------------------------------------------------------------
toggles_group:
name: "Event Toggles"
collapsed: false
input:
enable_cleaning_started:
name: Cleaning Started
description: "Send notification when the vacuum starts cleaning."
default: true
selector:
boolean:
enable_cleaning_completed:
name: Cleaning Completed
description: "Send notification when the vacuum finishes cleaning."
default: true
selector:
boolean:
enable_consumable:
name: Consumable Depleted
description: "Send notification when a consumable reaches end-of-life."
default: true
selector:
boolean:
enable_warning:
name: Warning
description: "Send notification for device warnings."
default: true
selector:
boolean:
enable_error:
name: Error
description: "Send notification for device errors."
default: true
selector:
boolean:
enable_information:
name: Information
description: "Send notification for informational alerts (e.g., blocked by DND)."
default: true
selector:
boolean:
# -------------------------------------------------------------------------
# Message Templates
# -------------------------------------------------------------------------
messages_group:
name: "Message Templates"
collapsed: true
input:
message_cleaning_started:
name: "Cleaning Started Message"
description: >
Message sent when the vacuum starts cleaning.
Variables: `{vacuum_name}`, `{cleaning_mode}`, `{status}`
default: "🧹 {vacuum_name} started cleaning ({cleaning_mode})."
selector:
text:
multiline: true
message_cleaning_completed:
name: "Cleaning Completed Message"
description: >
Message sent when the vacuum finishes cleaning.
Variables: `{vacuum_name}`, `{cleaning_mode}`, `{status}`,
`{cleaned_area}`, `{cleaning_time}`
default: "✅ {vacuum_name} finished cleaning. Area: {cleaned_area} m², time: {cleaning_time} min."
selector:
text:
multiline: true
message_consumable:
name: "Consumable Depleted Message"
description: >
Message sent when a consumable reaches end-of-life.
Variables: `{vacuum_name}`, `{consumable}`
default: "🔧 {vacuum_name}: {consumable} needs replacement."
selector:
text:
multiline: true
message_warning:
name: "Warning Message"
description: >
Message sent for device warnings.
Variables: `{vacuum_name}`, `{warning}`, `{code}`
default: "⚠️ {vacuum_name} warning: {warning} (code: {code})."
selector:
text:
multiline: true
message_error:
name: "Error Message"
description: >
Message sent for device errors.
Variables: `{vacuum_name}`, `{error}`, `{code}`
default: "❌ {vacuum_name} error: {error} (code: {code})."
selector:
text:
multiline: true
message_information:
name: "Information Message"
description: >
Message sent for informational alerts.
Variables: `{vacuum_name}`, `{information}`
default: " {vacuum_name}: {information}."
selector:
text:
multiline: true
# -------------------------------------------------------------------------
# Debug
# -------------------------------------------------------------------------
debug:
name: "Debug"
collapsed: true
input:
enable_debug_notifications:
name: Enable Debug Notifications
description: >
Send persistent notifications for debugging automation behavior.
Shows raw event data and filtering decisions.
default: false
selector:
boolean:
# Queued mode to avoid dropping rapid events
mode: queued
max: 5
# =============================================================================
# TRIGGERS
# =============================================================================
trigger:
# Cleaning job started or completed
- platform: event
event_type: dreame_vacuum_task_status
id: "task_status"
# Consumable reached end-of-life
- platform: event
event_type: dreame_vacuum_consumable
id: "consumable"
# Informational alert (job blocked by user settings)
- platform: event
event_type: dreame_vacuum_information
id: "information"
# Dismissible device warning
- platform: event
event_type: dreame_vacuum_warning
id: "warning"
# Device fault
- platform: event
event_type: dreame_vacuum_error
id: "error"
# =============================================================================
# VARIABLES
# =============================================================================
variables:
# ---------------------------------------------------------------------------
# Input References
# ---------------------------------------------------------------------------
vacuum_entity: !input vacuum_entity
notify_targets: !input notify_targets
enable_debug_notifications: !input enable_debug_notifications
# Event toggles
enable_cleaning_started: !input enable_cleaning_started
enable_cleaning_completed: !input enable_cleaning_completed
enable_consumable: !input enable_consumable
enable_warning: !input enable_warning
enable_error: !input enable_error
enable_information: !input enable_information
# Message templates
message_cleaning_started_template: !input message_cleaning_started
message_cleaning_completed_template: !input message_cleaning_completed
message_consumable_template: !input message_consumable
message_warning_template: !input message_warning
message_error_template: !input message_error
message_information_template: !input message_information
# ---------------------------------------------------------------------------
# Vacuum Info
# ---------------------------------------------------------------------------
vacuum_name: "{{ state_attr(vacuum_entity, 'friendly_name') | default(vacuum_entity) }}"
# ---------------------------------------------------------------------------
# Event Data (flat structure — fields are directly on trigger.event.data)
# ---------------------------------------------------------------------------
event_entity_id: "{{ trigger.event.data.entity_id | default('') }}"
# Task status fields
task_cleaning_mode: "{{ trigger.event.data.cleaning_mode | default('unknown') }}"
task_status_value: "{{ trigger.event.data.status | default('unknown') }}"
task_completed: "{{ trigger.event.data.completed | default(false) }}"
task_cleaned_area: "{{ trigger.event.data.cleaned_area | default(0) }}"
task_cleaning_time: "{{ trigger.event.data.cleaning_time | default(0) }}"
# Consumable fields
consumable_name: "{{ (trigger.event.data.consumable | default('unknown')) | replace('_', ' ') | title }}"
# Warning fields
warning_description: "{{ trigger.event.data.warning | default('unknown') }}"
warning_code: "{{ trigger.event.data.code | default('') }}"
# Error fields
error_description: "{{ trigger.event.data.error | default('unknown') }}"
error_code: "{{ trigger.event.data.code | default('') }}"
# Information fields
information_description: "{{ (trigger.event.data.information | default('unknown')) | replace('_', ' ') | title }}"
# =============================================================================
# CONDITIONS
# =============================================================================
condition:
# Only process events from the configured vacuum entity.
# The Dreame Vacuum integration uses generate_entity_id() for the entity_id
# in event data, which may append a numeric suffix (e.g., _2) since the
# actual vacuum entity already occupies the base entity_id.
- condition: template
value_template: >
{{ event_entity_id == vacuum_entity
or event_entity_id.startswith(vacuum_entity ~ '_') }}
# =============================================================================
# ACTIONS
# =============================================================================
action:
# ---------------------------------------------------------------------------
# Debug Logging
# ---------------------------------------------------------------------------
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Dreame Vacuum Debug"
message: >
**Trigger:** {{ trigger.id }}
**Entity:** {{ event_entity_id }}
**Vacuum:** {{ vacuum_name }}
**Event Data:** {{ trigger.event.data }}
# ---------------------------------------------------------------------------
# Send Notification Based on Event Type
# ---------------------------------------------------------------------------
- choose:
# CASE 1: Cleaning Started
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'task_status'
and not task_completed
and enable_cleaning_started }}
sequence:
- variables:
message: >
{% set tpl = message_cleaning_started_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{cleaning_mode}', task_cleaning_mode)
| replace('{status}', task_status_value) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 2: Cleaning Completed
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'task_status'
and task_completed
and enable_cleaning_completed }}
sequence:
- variables:
message: >
{% set tpl = message_cleaning_completed_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{cleaning_mode}', task_cleaning_mode)
| replace('{status}', task_status_value)
| replace('{cleaned_area}', task_cleaned_area | string)
| replace('{cleaning_time}', task_cleaning_time | string) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 3: Consumable Depleted
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'consumable' and enable_consumable }}
sequence:
- variables:
message: >
{% set tpl = message_consumable_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{consumable}', consumable_name) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 4: Warning
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'warning' and enable_warning }}
sequence:
- variables:
message: >
{% set tpl = message_warning_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{warning}', warning_description)
| replace('{code}', warning_code | string) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 5: Error
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'error' and enable_error }}
sequence:
- variables:
message: >
{% set tpl = message_error_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{error}', error_description)
| replace('{code}', error_code | string) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 6: Information
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'information' and enable_information }}
sequence:
- variables:
message: >
{% set tpl = message_information_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{information}', information_description) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"

View File

@@ -4,8 +4,7 @@ This blueprint monitors Immich album changes and sends notifications when assets
## Features ## Features
- Filter by hub/instance name (for multi-hub setups) - Monitor specific albums by ID (whitelist)
- Monitor specific albums by name (whitelist)
- Filter by asset type (track images only, videos only, or both) - Filter by asset type (track images only, videos only, or both)
- Filter by favorites only (only notify about favorite assets) - Filter by favorites only (only notify about favorite assets)
- Sort assets by date, rating, name, or keep original order - Sort assets by date, rating, name, or keep original order
@@ -107,6 +106,7 @@ These variables can be used in the image and video asset templates. Also used fo
| `{type}` | Asset type (IMAGE or VIDEO) | | `{type}` | Asset type (IMAGE or VIDEO) |
| `{created}` | Creation date/time (always shown) | | `{created}` | Creation date/time (always shown) |
| `{created_if_unique}` | Creation date/time formatted with template if dates differ, empty if all same | | `{created_if_unique}` | Creation date/time formatted with template if dates differ, empty if all same |
| `{year}` | Year the asset was created (4-digit, e.g., 2024) |
| `{owner}` | Owner display name | | `{owner}` | Owner display name |
| `{url}` | Public URL to view the asset (empty if no shared link) | | `{url}` | Public URL to view the asset (empty if no shared link) |
| `{download_url}` | Direct download URL for the original file | | `{download_url}` | Direct download URL for the original file |
@@ -146,6 +146,7 @@ Select input_text entities containing Telegram chat IDs. Can be user IDs (positi
- Large media lists are automatically split into multiple groups (2-10 items per group) - Large media lists are automatically split into multiple groups (2-10 items per group)
- Optional chat action indicator (typing, uploading photo/video) while processing - Optional chat action indicator (typing, uploading photo/video) while processing
- Optional maximum asset size filter to skip large files - Optional maximum asset size filter to skip large files
- Respects integration quiet hours — notifications are queued and sent when quiet hours end (configurable bypass per blueprint instance)
### Limitations ### Limitations
@@ -158,6 +159,8 @@ Select input_text entities containing Telegram chat IDs. Can be user IDs (positi
Sends a summary notification of tracked albums at configured times. Album names and share URLs are automatically read from the Album ID Entity's `album_name` and `share_url` attributes (if available). You can configure multiple notification times (e.g., "12:00, 18:00") using comma-separated 24-hour format with leading zeros. Sends a summary notification of tracked albums at configured times. Album names and share URLs are automatically read from the Album ID Entity's `album_name` and `share_url` attributes (if available). You can configure multiple notification times (e.g., "12:00, 18:00") using comma-separated 24-hour format with leading zeros.
Use **Summary Interval** to control how often summaries are sent (default: every day). Set it to `7` for weekly, `14` for bi-weekly, etc. The **Summary Start Date** anchors the interval — summaries are sent on that date and every N days after. For example, setting the start date to a Monday with an interval of 7 sends summaries every Monday.
When Telegram media is enabled, an optional image can be attached to the summary message. By default, the official Immich logo is used. Set the **Summary Image URL** to empty to send text-only notifications. When Telegram media is enabled, an optional image can be attached to the summary message. By default, the official Immich logo is used. Set the **Summary Image URL** to empty to send text-only notifications.
### Summary Message Template Variables ### Summary Message Template Variables

View File

@@ -19,20 +19,9 @@ blueprint:
# Hub & Album Configuration # Hub & Album Configuration
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
albums_group: albums_group:
name: "Hub & Albums" name: "Albums"
collapsed: false collapsed: false
input: input:
hub_names:
name: Hub Names to Track
description: >
List of Immich hub/instance names to monitor.
Only events from matching hubs will trigger notifications.
Leave empty to track all hubs.
default: []
selector:
text:
multiple: true
album_id_entities: album_id_entities:
name: Album ID Entities name: Album ID Entities
description: > description: >
@@ -236,7 +225,7 @@ blueprint:
name: "Image Asset Template" name: "Image Asset Template"
description: > description: >
Template for IMAGE assets in the list. Also used for Telegram media captions. Template for IMAGE assets in the list. Also used for Telegram media captions.
Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}` Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{year}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}`
default: "\n • 🖼️ {filename}" default: "\n • 🖼️ {filename}"
selector: selector:
text: text:
@@ -246,7 +235,7 @@ blueprint:
name: "Video Asset Template" name: "Video Asset Template"
description: > description: >
Template for VIDEO assets in the list. Also used for Telegram media captions. Template for VIDEO assets in the list. Also used for Telegram media captions.
Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}` Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{year}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}`
default: "\n • 🎬 {filename}" default: "\n • 🎬 {filename}"
selector: selector:
text: text:
@@ -472,6 +461,23 @@ blueprint:
- label: "Uploading Document..." - label: "Uploading Document..."
value: "upload_document" value: "upload_document"
telegram_quiet_hours_start:
name: Quiet Hours Start
description: >
Start time for quiet hours. During quiet hours, Telegram notifications
are queued and sent when quiet hours end.
default: "23:00"
selector:
time:
telegram_quiet_hours_end:
name: Quiet Hours End
description: >
End time for quiet hours. Queued notifications are sent after this time.
default: "07:00"
selector:
time:
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Periodic Summary # Periodic Summary
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -489,6 +495,28 @@ blueprint:
selector: selector:
boolean: boolean:
periodic_summary_interval:
name: Summary Interval (Days)
description: >
Number of days between periodic summaries.
Set to 1 for daily, 7 for weekly, etc.
default: 1
selector:
number:
min: 1
max: 365
step: 1
mode: box
periodic_summary_start_date:
name: Summary Start Date
description: >
The date from which to start counting the summary interval.
Summaries are sent on this date and every N days after.
default: "2025-01-01"
selector:
date:
periodic_notification_times: periodic_notification_times:
name: Summary Notification Times name: Summary Notification Times
description: > description: >
@@ -886,7 +914,7 @@ variables:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Input Variables # Input Variables
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
hub_names: !input hub_names
album_id_entities: !input album_id_entities album_id_entities: !input album_id_entities
automation_id: !input automation_id automation_id: !input automation_id
@@ -926,9 +954,13 @@ variables:
telegram_disable_url_preview: !input telegram_disable_url_preview telegram_disable_url_preview: !input telegram_disable_url_preview
telegram_chat_action: !input telegram_chat_action telegram_chat_action: !input telegram_chat_action
telegram_max_asset_size: !input telegram_max_asset_size telegram_max_asset_size: !input telegram_max_asset_size
telegram_quiet_hours_start: !input telegram_quiet_hours_start
telegram_quiet_hours_end: !input telegram_quiet_hours_end
# Periodic Summary Settings # Periodic Summary Settings
enable_periodic_summary: !input enable_periodic_summary enable_periodic_summary: !input enable_periodic_summary
periodic_summary_interval: !input periodic_summary_interval
periodic_summary_start_date: !input periodic_summary_start_date
periodic_summary_message_template: !input periodic_summary_message periodic_summary_message_template: !input periodic_summary_message
periodic_album_template: !input periodic_album_template periodic_album_template: !input periodic_album_template
periodic_summary_image_url: !input periodic_summary_image_url periodic_summary_image_url: !input periodic_summary_image_url
@@ -1033,10 +1065,6 @@ variables:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Computed Values # Computed Values
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Check if this hub should be tracked (empty list = track all)
is_hub_tracked: >
{{ hub_names | length == 0 or event_hub_name in hub_names }}
# Check if this album should be tracked (empty list = track all) # Check if this album should be tracked (empty list = track all)
is_album_tracked: > is_album_tracked: >
{{ album_ids | length == 0 or event_album_id in album_ids }} {{ album_ids | length == 0 or event_album_id in album_ids }}
@@ -1160,6 +1188,7 @@ variables:
{% set raw_date = asset.created_at | default('', true) %} {% set raw_date = asset.created_at | default('', true) %}
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
{% set formatted_date = dt.strftime(date_format) if dt else '' %} {% set formatted_date = dt.strftime(date_format) if dt else '' %}
{% set year = dt.strftime('%Y') if dt else '' %}
{% set created_if_unique = '' if unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date)) %} {% set created_if_unique = '' if unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date)) %}
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %} {% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %} {% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
@@ -1190,7 +1219,8 @@ variables:
| replace('{location_if_unique}', location_if_unique) | replace('{location_if_unique}', location_if_unique)
| replace('{city}', city) | replace('{city}', city)
| replace('{state}', state) | replace('{state}', state)
| replace('{country}', country) %} | replace('{country}', country)
| replace('{year}', year) %}
{% set ns.items = ns.items ~ item %} {% set ns.items = ns.items ~ item %}
{% endfor %} {% endfor %}
{% set more_count = sorted_assets | length - max_items %} {% set more_count = sorted_assets | length - max_items %}
@@ -1247,13 +1277,15 @@ variables:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Periodic Summary Variables # Periodic Summary Variables
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Check if periodic summary should run (at configured times) # Check if periodic summary should run (at configured times and interval)
# Manual test event (immich_album_watcher_test_periodic_summary) bypasses time check # Manual test event (immich_album_watcher_test_periodic_summary) bypasses interval check
# If event data contains automation_id, only matching automations respond # If event data contains automation_id, only matching automations respond
should_send_periodic_summary: > should_send_periodic_summary: >
{% if enable_periodic_summary %} {% if enable_periodic_summary %}
{% if trigger.id == 'periodic_summary_timer' %} {% if trigger.id == 'periodic_summary_timer' %}
{{ true }} {% set start = periodic_summary_start_date | as_datetime %}
{% set days_elapsed = (now().date() - start.date()).days %}
{{ days_elapsed >= 0 and days_elapsed % periodic_summary_interval == 0 }}
{% elif trigger.platform == 'event' and trigger.event.event_type == 'immich_album_watcher_test_periodic_summary' %} {% elif trigger.platform == 'event' and trigger.event.event_type == 'immich_album_watcher_test_periodic_summary' %}
{% set event_automation_id = trigger.event.data.automation_id | default('') %} {% set event_automation_id = trigger.event.data.automation_id | default('') %}
{% if event_automation_id | length > 0 %} {% if event_automation_id | length > 0 %}
@@ -1382,7 +1414,7 @@ condition:
{% elif trigger.platform == 'event' and trigger.event.event_type in ['immich_album_watcher_test_periodic_summary', 'immich_album_watcher_test_scheduled_assets', 'immich_album_watcher_test_memory_mode'] %} {% elif trigger.platform == 'event' and trigger.event.event_type in ['immich_album_watcher_test_periodic_summary', 'immich_album_watcher_test_scheduled_assets', 'immich_album_watcher_test_memory_mode'] %}
{{ should_send_periodic_summary or should_send_scheduled_assets or should_send_memory_mode }} {{ should_send_periodic_summary or should_send_scheduled_assets or should_send_memory_mode }}
{% else %} {% else %}
{{ is_hub_tracked and is_album_tracked and should_notify }} {{ is_album_tracked and should_notify }}
{% endif %} {% endif %}
# ============================================================================= # =============================================================================
@@ -1424,6 +1456,25 @@ action:
assets: "{{ [{'url': periodic_summary_image_url, 'type': 'photo'}] if periodic_summary_image_url | length > 0 else [] }}" assets: "{{ [{'url': periodic_summary_image_url, 'type': 'photo'}] if periodic_summary_image_url | length > 0 else [] }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Periodic Summary - Send:**
- Chat ID: {{ repeat.item }}
- Caption: {{ periodic_summary_formatted[:200] }}...
- Has Image: {{ 'Yes' if periodic_summary_image_url | length > 0 else 'No' }}
- Response: {{ telegram_periodic_response }}
# Delay between periodic summary and scheduled assets if both trigger at the same hour # Delay between periodic summary and scheduled assets if both trigger at the same hour
- if: - if:
@@ -1565,6 +1616,7 @@ action:
{% set raw_date = asset.created_at | default('', true) %} {% set raw_date = asset.created_at | default('', true) %}
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
{% set formatted_date = dt.strftime(date_format) if dt else '' %} {% set formatted_date = dt.strftime(date_format) if dt else '' %}
{% set year = dt.strftime('%Y') if dt else '' %}
{% set created_if_unique = '' if scheduled_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %} {% set created_if_unique = '' if scheduled_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %} {% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %} {% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
@@ -1595,7 +1647,8 @@ action:
| replace('{location_if_unique}', location_if_unique) | replace('{location_if_unique}', location_if_unique)
| replace('{city}', city) | replace('{city}', city)
| replace('{state}', state) | replace('{state}', state)
| replace('{country}', country) %} | replace('{country}', country)
| replace('{year}', year) %}
{% set ns.items = ns.items ~ item %} {% set ns.items = ns.items ~ item %}
{% endfor %} {% endfor %}
{{ ns.items }} {{ ns.items }}
@@ -1666,6 +1719,24 @@ action:
caption: "{{ scheduled_message }}" caption: "{{ scheduled_message }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Scheduled Per-Album - Text Send:**
- Chat ID: {{ repeat.item }}
- Caption: {{ scheduled_message[:200] }}...
- Response: {{ telegram_scheduled_text_response }}
# Extract message ID for reply # Extract message ID for reply
- variables: - variables:
@@ -1695,6 +1766,25 @@ action:
max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}" max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}"
wait_for_response: false wait_for_response: false
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log media send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Scheduled Per-Album - Media Send:**
- Chat ID: {{ repeat.item }}
- Assets: {{ scheduled_media_urls | length }}
- Reply To: {{ scheduled_reply_to_id }}
- Response: {{ telegram_scheduled_media_response }}
# Combined Mode: Fetch from all albums and combine into one notification # Combined Mode: Fetch from all albums and combine into one notification
# Distributes the limit evenly across albums (e.g., limit=10 with 2 albums = 5 each) # Distributes the limit evenly across albums (e.g., limit=10 with 2 albums = 5 each)
@@ -1824,6 +1914,7 @@ action:
{% set raw_date = asset.created_at | default('', true) %} {% set raw_date = asset.created_at | default('', true) %}
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
{% set formatted_date = dt.strftime(date_format) if dt else '' %} {% set formatted_date = dt.strftime(date_format) if dt else '' %}
{% set year = dt.strftime('%Y') if dt else '' %}
{% set created_if_unique = '' if combined_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %} {% set created_if_unique = '' if combined_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %} {% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %} {% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
@@ -1854,7 +1945,8 @@ action:
| replace('{location_if_unique}', location_if_unique) | replace('{location_if_unique}', location_if_unique)
| replace('{city}', city) | replace('{city}', city)
| replace('{state}', state) | replace('{state}', state)
| replace('{country}', country) %} | replace('{country}', country)
| replace('{year}', year) %}
{% set ns.items = ns.items ~ item %} {% set ns.items = ns.items ~ item %}
{% endfor %} {% endfor %}
{{ ns.items }} {{ ns.items }}
@@ -1924,6 +2016,24 @@ action:
caption: "{{ combined_message }}" caption: "{{ combined_message }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Scheduled Combined - Text Send:**
- Chat ID: {{ repeat.item }}
- Caption: {{ combined_message[:200] }}...
- Response: {{ telegram_combined_text_response }}
- variables: - variables:
combined_reply_to_id: "{{ telegram_combined_text_response[album_id_entities[0]].message_id | default(0) | int }}" combined_reply_to_id: "{{ telegram_combined_text_response[album_id_entities[0]].message_id | default(0) | int }}"
@@ -1951,6 +2061,25 @@ action:
max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}" max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}"
wait_for_response: false wait_for_response: false
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log media send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Scheduled Combined - Media Send:**
- Chat ID: {{ repeat.item }}
- Assets: {{ combined_media_urls | length }}
- Reply To: {{ combined_reply_to_id }}
- Response: {{ telegram_combined_media_response }}
# Delay before memory mode if another scheduled notification was sent at the same hour # Delay before memory mode if another scheduled notification was sent at the same hour
- if: - if:
@@ -2087,6 +2216,7 @@ action:
{% set raw_date = asset.created_at | default('', true) %} {% set raw_date = asset.created_at | default('', true) %}
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
{% set formatted_date = dt.strftime(date_format) if dt else '' %} {% set formatted_date = dt.strftime(date_format) if dt else '' %}
{% set year = dt.strftime('%Y') if dt else '' %}
{% set created_if_unique = '' if memory_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %} {% set created_if_unique = '' if memory_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %} {% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %} {% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
@@ -2117,7 +2247,8 @@ action:
| replace('{location_if_unique}', location_if_unique) | replace('{location_if_unique}', location_if_unique)
| replace('{city}', city) | replace('{city}', city)
| replace('{state}', state) | replace('{state}', state)
| replace('{country}', country) %} | replace('{country}', country)
| replace('{year}', year) %}
{% set ns.items = ns.items ~ item %} {% set ns.items = ns.items ~ item %}
{% endfor %} {% endfor %}
{{ ns.items }} {{ ns.items }}
@@ -2188,6 +2319,24 @@ action:
caption: "{{ memory_message }}" caption: "{{ memory_message }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Memory Per-Album - Text Send:**
- Chat ID: {{ repeat.item }}
- Caption: {{ memory_message[:200] }}...
- Response: {{ telegram_memory_text_response }}
# Extract message ID for reply # Extract message ID for reply
- variables: - variables:
@@ -2217,6 +2366,25 @@ action:
max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}" max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}"
wait_for_response: false wait_for_response: false
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log media send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Memory Per-Album - Media Send:**
- Chat ID: {{ repeat.item }}
- Assets: {{ memory_media_urls | length }}
- Reply To: {{ memory_reply_to_id }}
- Response: {{ telegram_memory_media_response }}
# Combined Mode: Fetch from all albums and combine into one notification # Combined Mode: Fetch from all albums and combine into one notification
- conditions: - conditions:
@@ -2336,6 +2504,7 @@ action:
{% set raw_date = asset.created_at | default('', true) %} {% set raw_date = asset.created_at | default('', true) %}
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %} {% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
{% set formatted_date = dt.strftime(date_format) if dt else '' %} {% set formatted_date = dt.strftime(date_format) if dt else '' %}
{% set year = dt.strftime('%Y') if dt else '' %}
{% set created_if_unique = '' if memory_comb_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %} {% set created_if_unique = '' if memory_comb_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %} {% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %} {% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
@@ -2366,7 +2535,8 @@ action:
| replace('{location_if_unique}', location_if_unique) | replace('{location_if_unique}', location_if_unique)
| replace('{city}', city) | replace('{city}', city)
| replace('{state}', state) | replace('{state}', state)
| replace('{country}', country) %} | replace('{country}', country)
| replace('{year}', year) %}
{% set ns.items = ns.items ~ item %} {% set ns.items = ns.items ~ item %}
{% endfor %} {% endfor %}
{{ ns.items }} {{ ns.items }}
@@ -2436,6 +2606,24 @@ action:
caption: "{{ memory_comb_message }}" caption: "{{ memory_comb_message }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Memory Combined - Text Send:**
- Chat ID: {{ repeat.item }}
- Caption: {{ memory_comb_message[:200] }}...
- Response: {{ telegram_memory_comb_text_response }}
- variables: - variables:
memory_comb_reply_to_id: "{{ telegram_memory_comb_text_response[album_id_entities[0]].message_id | default(0) | int }}" memory_comb_reply_to_id: "{{ telegram_memory_comb_text_response[album_id_entities[0]].message_id | default(0) | int }}"
@@ -2463,6 +2651,25 @@ action:
max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}" max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}"
wait_for_response: false wait_for_response: false
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log media send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Memory Combined - Media Send:**
- Chat ID: {{ repeat.item }}
- Assets: {{ memory_comb_media_urls | length }}
- Reply To: {{ memory_comb_reply_to_id }}
- Response: {{ telegram_memory_comb_media_response }}
# Stop here if this was a scheduled trigger - don't continue to event-based actions # Stop here if this was a scheduled trigger - don't continue to event-based actions
- choose: - choose:
@@ -2502,7 +2709,6 @@ action:
- {{ event_people | join(', ') if event_people | length > 0 else '(none)' }} - {{ event_people | join(', ') if event_people | length > 0 else '(none)' }}
**Filtering:** **Filtering:**
- Is Hub Tracked: {{ is_hub_tracked }}
- Is Album Tracked: {{ is_album_tracked }} - Is Album Tracked: {{ is_album_tracked }}
- Should Notify: {{ should_notify }} - Should Notify: {{ should_notify }}
- Track Images: {{ track_images }} - Track Images: {{ track_images }}
@@ -2666,6 +2872,24 @@ action:
caption: "{{ message }}" caption: "{{ message }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Album Renamed - Text Send:**
- Chat ID: {{ repeat.item }}
- Caption: {{ message[:200] }}...
- Response: {{ telegram_renamed_response }}
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# CASE 5: Album Deleted # CASE 5: Album Deleted
@@ -2703,6 +2927,24 @@ action:
caption: "{{ message }}" caption: "{{ message }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Album Deleted - Text Send:**
- Chat ID: {{ repeat.item }}
- Caption: {{ message[:200] }}...
- Response: {{ telegram_deleted_response }}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Send Media to Telegram (if enabled) # Send Media to Telegram (if enabled)
@@ -2829,6 +3071,24 @@ action:
caption: "{{ telegram_message }}" caption: "{{ telegram_message }}"
disable_web_page_preview: "{{ telegram_disable_url_preview }}" disable_web_page_preview: "{{ telegram_disable_url_preview }}"
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log text send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Assets Added - Text Send:**
- Chat ID: {{ current_chat_id }}
- Caption: {{ telegram_message[:200] }}...
- Response: {{ telegram_text_response }}
# Extract message ID for replies # Extract message ID for replies
- variables: - variables:
@@ -2871,3 +3131,23 @@ action:
max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}" max_asset_data_size: "{{ telegram_max_asset_size | int * 1048576 }}"
wait_for_response: false wait_for_response: false
chat_action: "{{ telegram_chat_action }}" chat_action: "{{ telegram_chat_action }}"
quiet_hours_start: "{{ telegram_quiet_hours_start }}"
quiet_hours_end: "{{ telegram_quiet_hours_end }}"
# Debug: Log media send result
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Immich Album Watcher - Telegram Send Debug"
message: >
**Assets Added - Media Send:**
- Chat ID: {{ current_chat_id }}
- Assets: {{ media_urls | length }}
- Reply To: {{ reply_to_message_id }}
- Max Group Size: {{ max_media_per_group }}
- Response: {{ telegram_media_response }}

View File

@@ -0,0 +1,67 @@
# Light Color Mapper
Syncs light colors in real-time from color sensor entities. Sensors and lights are paired by list index (sensor 0 → light 0, sensor 1 → light 1, etc.).
## Features
- Real-time color sync from sensor entities to lights
- Index-based sensor-to-light mapping (add in matching order)
- FPS throttling for performance control (060 fps)
- Brightness override or per-sensor brightness from attributes
- Configurable behavior when sensors are unavailable (turn off, keep last, default color)
- Control entity (switch/input_boolean) to enable/disable
- Optional auto-off when the control entity is disabled
- Custom post-update callback action (runs after each update cycle)
## How It Works
The automation triggers on any state change from the configured color sensors. Each sensor is paired with a light by list position:
| Sensor Index | Light Index |
| --- | --- |
| Sensor 0 | Light 0 |
| Sensor 1 | Light 1 |
| Sensor 2 | Light 2 |
| ... | ... |
When a sensor updates, **all** sensor-light pairs are re-evaluated to keep lights in sync.
### FPS Throttling
The automation uses `mode: restart` with a trailing delay to throttle updates:
- **FPS = 0**: No throttle — every state change triggers an immediate update.
- **FPS > 0**: After processing all lights, a delay of `1000 / FPS` ms is added. If a new sensor state arrives during the delay, the automation restarts with the latest data. This naturally caps updates to the configured FPS.
### Sensor Requirements
Each color sensor should expose:
| Attribute | Description |
| --- | --- |
| **State** | Hex color string (e.g., `#FF8800`) or `None` when not processing |
| `rgb_color` | Color as `[r, g, b]` list (used to set light color) |
| `brightness` | Brightness value 0255 (used when no override is set) |
## Configuration
| Input | Description |
| --- | --- |
| **Control Entity** | Switch or input_boolean that enables/disables the automation |
| **Turn Off Lights on Disable** | Turn off all mapped lights when the control entity is switched off (default: true) |
| **Color Sensors** | List of color sensor entities (index-mapped to lights) |
| **Lights** | List of light entities (index-mapped to sensors) |
| **Maximum FPS** | Update rate limit, 0 = unlimited (default: 0) |
| **Brightness Override** | Fixed brightness for all lights, 0 = use sensor brightness (default: 0) |
| **When Sensor is Unavailable** | Action when sensor is None/unavailable: Turn Off, Keep Last, or Set Default Color |
| **Default Color** | Color to apply when sensor is unavailable and action is "Set Default Color" |
| **Post-Update Action** | Custom action to run after all lights are updated (e.g., fire an event, call a service) |
## Notes
- If the sensor and light lists have different lengths, only the shorter count of pairs is used (extra entities are ignored).
- When the control entity is turned off and "Turn Off Lights on Disable" is enabled, only the paired lights (up to `pair_count`) are turned off.
## Author
Alexei Dolgolyov (<dolgolyov.alexei@gmail.com>)

View File

@@ -0,0 +1,342 @@
# Light Color Mapper
# Syncs light colors in real-time from color sensor entities.
# See README.md for detailed documentation.
#
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
blueprint:
name: "Custom: Light Color Mapper"
description: >
Maps color sensor states to lights in real-time.
Sensors and lights are paired by list index (sensor 0 → light 0, etc.).
Supports FPS throttling, brightness override, and configurable behavior
when sensors are unavailable.
domain: automation
# ===========================================================================
# INPUT CONFIGURATION
# ===========================================================================
input:
# -------------------------------------------------------------------------
# Control
# -------------------------------------------------------------------------
control_group:
name: "Control"
collapsed: false
input:
control_entity:
name: Control Entity
description: "Switch or input_boolean that enables/disables this automation."
selector:
entity:
domain:
- input_boolean
- switch
turn_off_on_disable:
name: Turn Off Lights on Disable
description: "Turn off all mapped lights when the control entity is switched off."
default: true
selector:
boolean:
# -------------------------------------------------------------------------
# Devices
# -------------------------------------------------------------------------
devices_group:
name: "Devices"
collapsed: false
input:
color_sensors:
name: Color Sensors
description: >
List of color sensor entities. Each sensor's state is a hex color
string (e.g., #FF8800) with attributes: r, g, b, brightness,
rgb_color. Sensors are mapped to lights by list index.
selector:
entity:
domain: sensor
multiple: true
lights:
name: Lights
description: >
List of light entities. Each light is paired with the sensor at the
same list index (sensor 0 → light 0, sensor 1 → light 1, etc.).
selector:
entity:
domain: light
multiple: true
# -------------------------------------------------------------------------
# Settings
# -------------------------------------------------------------------------
settings_group:
name: "Settings"
collapsed: false
input:
max_fps:
name: Maximum FPS
description: >
Maximum updates per second. With mode: restart, a trailing delay
throttles updates naturally — new state changes during the delay
restart the automation with fresh data. Set to 0 for unlimited.
default: 0
selector:
number:
min: 0
max: 60
mode: slider
brightness_override:
name: Brightness Override
description: >
Override brightness for all lights (0255).
Set to 0 to use each sensor's brightness attribute instead.
default: 0
selector:
number:
min: 0
max: 255
mode: slider
min_brightness:
name: Minimum Brightness
description: >
Minimum brightness for all lights (1255). If the sensor or
override brightness is below this value, this minimum is used
instead. Prevents lights from becoming too dim to see.
default: 5
selector:
number:
min: 1
max: 255
mode: slider
none_action:
name: When Sensor is Unavailable
description: "Action to take when a sensor state is None, unknown, or unavailable."
default: "turn_off"
selector:
select:
options:
- label: "Turn Off Light"
value: "turn_off"
- label: "Keep Last Color"
value: "keep_last"
- label: "Set Default Color"
value: "default_color"
default_color:
name: Default Color
description: >
Color to apply when a sensor is unavailable.
Only used when "When Sensor is Unavailable" is set to "Set Default Color".
default: [255, 255, 255]
selector:
color_rgb:
# -------------------------------------------------------------------------
# Callback Actions
# -------------------------------------------------------------------------
actions_group:
name: "Callback Actions"
collapsed: true
input:
post_update_action:
name: Post-Update Action
description: >
Custom action to run after all sensor-light pairs have been processed.
Runs once per update cycle, after all lights are set.
Leave empty to do nothing.
default: []
selector:
action:
# -------------------------------------------------------------------------
# Debug
# -------------------------------------------------------------------------
debug:
name: "Debug"
collapsed: true
input:
enable_debug_notifications:
name: Enable Debug Notifications
description: >
Send persistent notifications showing sensor states and light
updates for troubleshooting.
default: false
selector:
boolean:
# Restart mode ensures the latest sensor state always wins.
# Combined with a trailing delay (FPS throttle), incoming state changes
# during the delay cancel the current run and restart with fresh data.
mode: restart
# =============================================================================
# TRIGGERS
# =============================================================================
trigger:
# Any color sensor changes state
- platform: state
entity_id: !input color_sensors
id: "sensor_changed"
# Control entity toggled on/off
- platform: state
entity_id: !input control_entity
id: "control_changed"
# =============================================================================
# VARIABLES
# =============================================================================
variables:
# ---------------------------------------------------------------------------
# Input References
# ---------------------------------------------------------------------------
control_entity: !input control_entity
turn_off_on_disable: !input turn_off_on_disable
color_sensors: !input color_sensors
lights: !input lights
max_fps: !input max_fps
brightness_override: !input brightness_override
min_brightness: !input min_brightness
none_action: !input none_action
default_color: !input default_color
enable_debug_notifications: !input enable_debug_notifications
post_update_action: !input post_update_action
# ---------------------------------------------------------------------------
# Computed Values
# ---------------------------------------------------------------------------
# Use the shorter list length to avoid index-out-of-range errors
pair_count: "{{ [color_sensors | length, lights | length] | min }}"
# =============================================================================
# ACTIONS
# =============================================================================
action:
# ---------------------------------------------------------------------------
# Debug Logging
# ---------------------------------------------------------------------------
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Light Color Mapper Debug"
message: >
**Trigger:** {{ trigger.id }}
**Control:** {{ states(control_entity) }}
**Pairs:** {{ pair_count }}
**FPS:** {{ max_fps }}
**Brightness Override:** {{ brightness_override }}
**Min Brightness:** {{ min_brightness }}
**Sensor States:**
{% for i in range(pair_count | int) %}
{{ i }}: {{ color_sensors[i] }} = {{ states(color_sensors[i]) }} (rgb: {{ state_attr(color_sensors[i], 'rgb_color') }}, brightness: {{ state_attr(color_sensors[i], 'brightness') }})
{% endfor %}
# ---------------------------------------------------------------------------
# Check Control Entity
# ---------------------------------------------------------------------------
- choose:
- conditions:
- condition: template
value_template: "{{ not is_state(control_entity, 'on') }}"
sequence:
# Turn off all mapped lights if configured
- choose:
- conditions:
- condition: template
value_template: "{{ turn_off_on_disable }}"
sequence:
- service: light.turn_off
target:
entity_id: "{{ lights[:pair_count | int] }}"
- stop: "Control entity is off"
# ---------------------------------------------------------------------------
# Process Each Sensor-Light Pair
# ---------------------------------------------------------------------------
- repeat:
count: "{{ pair_count | int }}"
sequence:
- variables:
idx: "{{ repeat.index - 1 }}"
current_sensor: "{{ color_sensors[idx] }}"
current_light: "{{ lights[idx] }}"
sensor_state: "{{ states(current_sensor) }}"
is_unavailable: "{{ sensor_state in ['None', 'none', 'unknown', 'unavailable', 'undefined'] or sensor_state | length == 0 }}"
- choose:
# Sensor is unavailable — apply configured action
- conditions:
- condition: template
value_template: "{{ is_unavailable }}"
sequence:
- choose:
# Turn off the light
- conditions:
- condition: template
value_template: "{{ none_action == 'turn_off' }}"
sequence:
- service: light.turn_off
target:
entity_id: "{{ current_light }}"
# Set default color
- conditions:
- condition: template
value_template: "{{ none_action == 'default_color' }}"
sequence:
- service: light.turn_on
target:
entity_id: "{{ current_light }}"
data:
rgb_color: "{{ default_color }}"
brightness: "{{ [brightness_override | int if brightness_override | int > 0 else 255, min_brightness | int] | max }}"
# keep_last — do nothing
# Sensor has valid state — apply color to light
default:
- variables:
sensor_rgb: "{{ state_attr(current_sensor, 'rgb_color') | default([255, 255, 255]) }}"
sensor_brightness: "{{ state_attr(current_sensor, 'brightness') | default(255) }}"
target_brightness: "{{ [brightness_override | int if brightness_override | int > 0 else sensor_brightness | int, min_brightness | int] | max }}"
- service: light.turn_on
target:
entity_id: "{{ current_light }}"
data:
rgb_color: "{{ sensor_rgb }}"
brightness: "{{ target_brightness }}"
# ---------------------------------------------------------------------------
# Post-Update Callback
# ---------------------------------------------------------------------------
- choose:
- conditions:
- condition: template
value_template: "{{ post_update_action | length > 0 }}"
sequence: !input post_update_action
# ---------------------------------------------------------------------------
# FPS Throttle
# ---------------------------------------------------------------------------
# With mode: restart, new triggers during this delay cancel and restart
# the automation, effectively capping updates to max_fps per second.
- choose:
- conditions:
- condition: template
value_template: "{{ max_fps | int > 0 }}"
sequence:
- delay:
milliseconds: "{{ (1000 / (max_fps | int)) | int }}"

View File

@@ -17,7 +17,7 @@ This blueprint creates a smart motion-activated light control system. It handles
- Day/Night mode (different light settings based on time) - Day/Night mode (different light settings based on time)
- Scene support (activate scenes instead of light parameters) - Scene support (activate scenes instead of light parameters)
- Dim before off (visual warning before turning off) - Dim before off (visual warning before turning off)
- Manual override detection (stops automation if user changes light) - Manual override detection (stops automation if user changes light) with configurable grace period
- Brightness threshold (only trigger if light is dim) - Brightness threshold (only trigger if light is dim)
- Custom light parameters (brightness, color, etc.) - Custom light parameters (brightness, color, etc.)
- Callback actions for enable/disable/manual events - Callback actions for enable/disable/manual events
@@ -37,7 +37,8 @@ The automation tracks these states via persistent storage:
## Behavior Notes ## Behavior Notes
- Will NOT turn on light if it's already ON (prevents hijacking user control) - Will NOT turn on light if it's already ON (prevents hijacking user control)
- If user changes light while automation is active, enters MANUAL mode - If user changes light while automation is active, enters MANUAL mode (after grace period)
- Grace period prevents false manual overrides from delayed device state reports (e.g., Zigbee)
- MANUAL mode exits when light is turned OFF (by any means) - MANUAL mode exits when light is turned OFF (by any means)
- Timeout delay only applies when turning OFF (motion cleared) - Timeout delay only applies when turning OFF (motion cleared)
- Time conditions support overnight windows (e.g., 22:00 to 06:00) - Time conditions support overnight windows (e.g., 22:00 to 06:00)

View File

@@ -37,6 +37,7 @@ blueprint:
- switch - switch
- group - group
- light - light
- input_boolean
multiple: true multiple: true
condition_switches: condition_switches:
@@ -140,7 +141,7 @@ blueprint:
description: > description: >
Delay before turning off the light after all motion sensors Delay before turning off the light after all motion sensors
clear. Set to 0 for immediate turn off. clear. Set to 0 for immediate turn off.
default: 120 default: 0
selector: selector:
number: number:
min: 0 min: 0
@@ -384,6 +385,22 @@ blueprint:
step: 1 step: 1
unit_of_measurement: "seconds" unit_of_measurement: "seconds"
manual_override_grace_period:
name: Manual Override Grace Period (seconds)
description: >
After the automation turns on a light, ignore state changes for
this many seconds to avoid false manual override detection.
Some devices (especially Zigbee) report delayed state updates
that can be mistaken for manual control. Increase this value
if you see false manual overrides in the debug log.
default: 2
selector:
number:
min: 0
max: 30
step: 1
unit_of_measurement: "seconds"
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Debug # Debug
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -510,12 +527,6 @@ condition: !input user_condition
# ============================================================================= # =============================================================================
variables: variables:
# ---------------------------------------------------------------------------
# Debug Flags
# ---------------------------------------------------------------------------
is_debug: false # Detailed debug for specific actions
is_base_debug: false # Basic debug info at start
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# State Machine Constants # State Machine Constants
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -546,6 +557,7 @@ variables:
min_on_duration: !input min_on_duration min_on_duration: !input min_on_duration
brightness_threshold: !input brightness_threshold brightness_threshold: !input brightness_threshold
transition_duration: !input transition_duration transition_duration: !input transition_duration
manual_override_grace_period: !input manual_override_grace_period
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Target Device Resolution # Target Device Resolution
@@ -591,11 +603,22 @@ variables:
# Reference light for state checks (first available) # Reference light for state checks (first available)
reference_light: "{{ resolved_all_lights[0] if resolved_all_lights | length > 0 else none }}" reference_light: "{{ resolved_all_lights[0] if resolved_all_lights | length > 0 else none }}"
# Check if any device is on # Check if any device is on (respects brightness_threshold)
any_device_on: > any_device_on: >
{% set lights_on = resolved_all_lights | select('is_state', 'on') | list | length > 0 %} {% set ns = namespace(lights_on=false) %}
{% for light in resolved_all_lights %}
{% if is_state(light, 'on') %}
{% if brightness_threshold | int(0) > 0 %}
{% if state_attr(light, 'brightness') | int(0) >= brightness_threshold | int(0) %}
{% set ns.lights_on = true %}
{% endif %}
{% else %}
{% set ns.lights_on = true %}
{% endif %}
{% endif %}
{% endfor %}
{% set switches_on = resolved_all_switches | select('is_state', 'on') | list | length > 0 %} {% set switches_on = resolved_all_switches | select('is_state', 'on') | list | length > 0 %}
{{ lights_on or switches_on }} {{ ns.lights_on or switches_on }}
all_devices_off: "{{ not any_device_on }}" all_devices_off: "{{ not any_device_on }}"
@@ -797,7 +820,7 @@ variables:
# Should we disable the light? (Motion cleared OR condition switch turned off) # Should we disable the light? (Motion cleared OR condition switch turned off)
must_be_disabled_preview: > must_be_disabled_preview: >
{{ ((not all_of_condition_switches_on) or motion_all_off) | bool }} {{ ((not all_of_condition_switches_on) or motion_all_off) | bool }}
must_be_disabled_guard: "{{ state_is_enabled }}" must_be_disabled_guard: "{{ state_is_enabled or state_is_enabling }}"
must_be_disabled: > must_be_disabled: >
{{ must_be_disabled_preview and must_be_disabled_guard }} {{ must_be_disabled_preview and must_be_disabled_guard }}
@@ -807,22 +830,61 @@ variables:
action: action:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# DEBUG: Log basic info (enable by setting is_base_debug: true) # DEBUG: Log entry state on every trigger (helps trace mode: restart issues)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
- choose: - choose:
- conditions: - conditions: "{{ enable_debug_notifications }}"
- condition: template
value_template: "{{ is_base_debug }}"
sequence: sequence:
- service: persistent_notification.create - service: persistent_notification.create
data: data:
title: "Debug Info - Motion Light" title: "Motion Light Debug - ENTRY"
message: > message: >
must_be_enabled_preview: {{ must_be_enabled_preview }}, === Automation Triggered ===
must_be_disabled_preview: {{ must_be_disabled_preview }}, Time: {{ now().strftime('%H:%M:%S.%f')[:12] }}
must_be_disabled: {{ must_be_disabled }}, Trigger ID: {{ trigger_id }}
must_be_disabled_guard: {{ must_be_disabled_guard }}, Trigger Entity: {{ trigger.entity_id | default('N/A') }}
trigger_id: {{ trigger.id }} Trigger From: {{ trigger.from_state.state | default('N/A') }}
Trigger To: {{ trigger.to_state.state | default('N/A') }}
=== State Machine ===
State: {{ motion_light_state }} (NONE=0, ENABLED=1, ENABLING=2, MANUAL=3)
state_is_none: {{ state_is_none }}
state_is_enabling: {{ state_is_enabling }}
state_is_enabled: {{ state_is_enabled }}
state_is_manual: {{ state_is_manual }}
=== Decision Variables ===
motion_on: {{ motion_on }} ({{ count_of_enabled_sensor }} sensors)
all_of_condition_switches_on: {{ all_of_condition_switches_on }}
luminance_ok: {{ luminance_ok }}
time_condition_ok: {{ time_condition_ok }}
any_device_on: {{ any_device_on }}
=== Enable/Disable Logic ===
must_be_enabled_preview: {{ must_be_enabled_preview }}
must_be_enabled_guard: {{ must_be_enabled_guard }}
must_be_enabled: {{ must_be_enabled }}
must_be_disabled_preview: {{ must_be_disabled_preview }}
must_be_disabled_guard: {{ must_be_disabled_guard }}
must_be_disabled: {{ must_be_disabled }}
=== Which CASE will match ===
CASE 1 (state changed): {{ trigger_id == 'light_state_changed' or trigger_id == 'switch_state_changed' }}
CASE 2 (enable): {{ must_be_enabled }}
CASE 3 (disable): {{ must_be_disabled }}
=== Grace Period ===
{%- set last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) -%}
{%- set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(2)) -%}
last_action_ts: {{ last_ts | default('not set') }}
grace_period: {{ grace }}s
{%- if last_ts is not none -%}
{%- set parsed = last_ts | as_datetime -%}
{%- if parsed is not none %}
time_since_action: {{ (now() - parsed).total_seconds() | round(2) }}s
grace_expired: {{ (now() - parsed).total_seconds() > grace }}
{%- endif -%}
{%- endif %}
# =========================================================================== # ===========================================================================
# MAIN STATE MACHINE # MAIN STATE MACHINE
@@ -838,6 +900,26 @@ action:
- condition: template - condition: template
value_template: "{{ trigger_id == 'light_state_changed' or trigger_id == 'switch_state_changed' }}" value_template: "{{ trigger_id == 'light_state_changed' or trigger_id == 'switch_state_changed' }}"
sequence: sequence:
# Debug: log which CASE 1 sub-case will fire
- choose:
- conditions: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Motion Light Debug - CASE 1"
message: >
CASE 1: light/switch state changed
Time: {{ now().strftime('%H:%M:%S.%f')[:12] }}
Trigger: {{ trigger_id }}
Entity: {{ trigger.entity_id | default('N/A') }}
From: {{ trigger.from_state.state | default('N/A') }} → To: {{ trigger.to_state.state | default('N/A') }}
Meaningful change: {{ trigger.from_state.state != trigger.to_state.state }}
Light brightness: {{ state_attr(reference_light, 'brightness') | default('N/A') }}
State: {{ motion_light_state }}
state_is_enabling: {{ state_is_enabling }}
state_is_enabled: {{ state_is_enabled }}
must_be_disabled_preview: {{ must_be_disabled_preview }}
- choose: - choose:
# ----- Sub-case: Light/Switch turned OFF ----- # ----- Sub-case: Light/Switch turned OFF -----
@@ -845,12 +927,13 @@ action:
- conditions: - conditions:
- condition: template - condition: template
value_template: > value_template: >
{# BUG FIX: Changed from 'res = false' to 'res = true' for AND logic #} {# Check actual on/off state only — do NOT use brightness_threshold here.
Brightness threshold is for the enable guard (any_device_on), not for
detecting whether the light was actually turned off. During transitions,
brightness may temporarily be below threshold while the light is still on. #}
{% set res = true %} {% set res = true %}
{% if light_entity is not none %} {% if light_entity is not none %}
{% set brightness = state_attr(light_entity, 'brightness') | int(0) %} {% set res = res and is_state(light_entity, 'off') %}
{% set light_off = is_state(light_entity, 'off') or brightness < brightness_threshold %}
{% set res = res and light_off %}
{% endif %} {% endif %}
{% if switch_entity is not none %} {% if switch_entity is not none %}
{% set res = res and is_state(switch_entity, 'off') %} {% set res = res and is_state(switch_entity, 'off') %}
@@ -868,25 +951,237 @@ action:
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %} {% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }} {{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Re-evaluate: if an external source turned off the light while
# the automation was actively controlling it, wait briefly and
# re-enable if conditions are still met.
# Safety: state_is_enabled/state_is_enabling reflect the state at
# run start (before reset). When CASE 3 turns off the light, it
# sets state to NONE first, so on restart state_is_none=true and
# this block is skipped — preventing unwanted re-enable.
- choose:
- conditions:
- condition: template
value_template: "{{ state_is_enabled or state_is_enabling }}"
sequence:
# Wait for the off-transition to finish
- delay:
seconds: "{{ transition_duration }}"
# Fresh condition check (evaluates current entity states)
- condition: template
value_template: >
{% set e = sensors if sensors is iterable else [sensors] %}
{% set motion_active = e | select('is_state', 'on') | list | length > 0 %}
{% set cond_ok = true %}
{% set cs = condition_switches if condition_switches is iterable else [condition_switches] %}
{% if cs | length > 0 %}
{% set cond_ok = (cs | select('is_state', 'on') | list | length) == (cs | length) %}
{% endif %}
{{ motion_active and cond_ok }}
# Re-read state from input_text (may have changed during delay)
- variables:
re_eval_state_global: >
{% set text = states(automation_state_entity) | string %}
{% if text in ['unknown','unavailable','none',''] %}
{{ dict() }}
{% else %}
{{ text | from_json }}
{% endif %}
re_eval_state: "{{ re_eval_state_global.get(automation_state_key, dict()) }}"
re_eval_motion_light_state: "{{ re_eval_state.get(state_motion_light_state, automation_state_none) }}"
# Only proceed if state is still NONE (no other run claimed it)
- condition: template
value_template: "{{ (re_eval_motion_light_state | string) == automation_state_none }}"
# --- Re-enable path (mirrors CASE 2) ---
# Set state to ENABLING before turning on
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (re_eval_state | combine({
state_motion_light_state: automation_state_enabling,
state_motion_light_last_action_timestamp: now(),
state_motion_light_last_brightness: 0
})) %}
{{ re_eval_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Scene or light activation
- choose:
- conditions:
- condition: template
value_template: "{{ use_scene_instead and effective_scene is not none }}"
sequence:
- service: scene.turn_on
target:
entity_id: "{{ effective_scene }}"
data:
transition: "{{ transition_duration }}"
default:
# Turn ON lights
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_lights | length > 0 }}"
sequence:
- service: light.turn_on
target:
entity_id: "{{ resolved_all_lights }}"
data: >
{% set d = effective_light_data if effective_light_data else {} %}
{% if transition_duration > 0 %}
{% set d = d | combine({'transition': transition_duration}) %}
{% endif %}
{{ d }}
# Turn ON switches
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_switches | length > 0 }}"
sequence:
- service: switch.turn_on
target:
entity_id: "{{ resolved_all_switches }}"
# Execute enable callback
- choose:
- conditions:
- condition: template
value_template: "{{ enable_action != [] }}"
sequence: !input enable_action
# Debug notification
- choose:
- conditions: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Motion Light Debug"
message: >
Action: RE-ENABLE (external turn-off recovery)
Time: {{ now().strftime('%H:%M:%S') }}
Lights: {{ resolved_all_lights }}
Switches: {{ resolved_all_switches }}
Scene: {{ effective_scene if use_scene_instead else 'N/A' }}
# ----- Sub-case: Automation just turned on the light ----- # ----- Sub-case: Automation just turned on the light -----
# Transition from ENABLING to ENABLED # Transition from ENABLING to ENABLED, or disable immediately
# if motion already cleared during the ENABLING phase
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ state_is_enabling }}" value_template: "{{ state_is_enabling }}"
sequence: sequence:
- service: input_text.set_value - choose:
target: # If disable conditions are already met (motion cleared
entity_id: "{{ automation_state_entity }}" # while light was still in ENABLING state), skip ENABLED
data: # and go straight to disable
value: > - conditions:
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_enabled })) %} - condition: template
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }} value_template: "{{ must_be_disabled_preview }}"
sequence:
# Reset state to NONE
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Turn OFF or restore lights
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_lights | length > 0 }}"
sequence:
- variables:
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
- choose:
- conditions:
- condition: template
value_template: "{{ last_brightness > 0 }}"
sequence:
- service: light.turn_on
target:
entity_id: "{{ resolved_all_lights }}"
data:
brightness: "{{ last_brightness }}"
transition: "{{ transition_duration }}"
default:
- service: light.turn_off
target:
entity_id: "{{ resolved_all_lights }}"
data:
transition: "{{ transition_duration }}"
# Turn OFF switches
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_switches | length > 0 }}"
sequence:
- service: switch.turn_off
target:
entity_id: "{{ resolved_all_switches }}"
# Execute disable callback
- choose:
- conditions:
- condition: template
value_template: "{{ disable_action != [] }}"
sequence: !input disable_action
# Debug notification
- choose:
- conditions: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Motion Light Debug"
message: >
Action: DISABLE (enabling interrupted)
Time: {{ now().strftime('%H:%M:%S') }}
Trigger: {{ trigger_id }}
# Normal case: motion still active, transition to ENABLED
default:
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_enabled })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# ----- Sub-case: User manually changed the light ----- # ----- Sub-case: User manually changed the light -----
# Transition from ENABLED to MANUAL (user took control) # Transition from ENABLED to MANUAL (user took control)
# Only triggers on meaningful state changes (on→off or off→on),
# NOT on attribute-only updates (on→on) which Zigbee devices
# commonly send as the light settles after a transition.
# Grace period: ignore state changes shortly after the automation
# turns on the light to avoid false manual override detection.
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ state_is_enabled }}" value_template: >
{% set meaningful_change = trigger.from_state.state != trigger.to_state.state %}
{% if not meaningful_change %}
{{ false }}
{% else %}
{% set last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) %}
{% set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(2)) %}
{% if state_is_enabled and last_ts is not none %}
{% set parsed = last_ts | as_datetime %}
{{ parsed is none or (now() - parsed).total_seconds() > grace }}
{% else %}
{{ state_is_enabled }}
{% endif %}
{% endif %}
sequence: sequence:
# BUG FIX: Fixed YAML structure - was 'data: >' instead of 'data:' with 'value: >' # BUG FIX: Fixed YAML structure - was 'data: >' instead of 'data:' with 'value: >'
- service: input_text.set_value - service: input_text.set_value
@@ -921,6 +1216,81 @@ action:
New State: MANUAL New State: MANUAL
Trigger: {{ trigger_id }} Trigger: {{ trigger_id }}
# ----- Default: No sub-case matched -----
# This handles the case where a light_state_changed trigger fires
# during the grace period (e.g., Zigbee delayed state reports) while
# the disable path was already in progress but got cancelled by
# mode: restart. If disable conditions are met, turn off directly.
default:
- choose:
- conditions:
- condition: template
value_template: "{{ (state_is_enabled or state_is_enabling) and must_be_disabled_preview }}"
sequence:
# Reset state to NONE first (before turn-off triggers another restart)
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Turn OFF or restore lights
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_lights | length > 0 }}"
sequence:
- variables:
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
- choose:
- conditions:
- condition: template
value_template: "{{ last_brightness > 0 }}"
sequence:
- service: light.turn_on
target:
entity_id: "{{ resolved_all_lights }}"
data:
brightness: "{{ last_brightness }}"
transition: "{{ transition_duration }}"
default:
- service: light.turn_off
target:
entity_id: "{{ resolved_all_lights }}"
data:
transition: "{{ transition_duration }}"
# Turn OFF switches
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_switches | length > 0 }}"
sequence:
- service: switch.turn_off
target:
entity_id: "{{ resolved_all_switches }}"
# Execute disable callback
- choose:
- conditions:
- condition: template
value_template: "{{ disable_action != [] }}"
sequence: !input disable_action
# Debug notification
- choose:
- conditions: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Motion Light Debug"
message: >
Action: DISABLE (restart recovery)
Time: {{ now().strftime('%H:%M:%S') }}
Trigger: {{ trigger_id }}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# CASE 2: Enable Path (Motion Detected, Should Turn On) # CASE 2: Enable Path (Motion Detected, Should Turn On)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -948,6 +1318,21 @@ action:
{{ state_attr(reference_light, 'brightness') | int(0) }} {{ state_attr(reference_light, 'brightness') | int(0) }}
{% endif %} {% endif %}
# Update state to ENABLING BEFORE turning on the light.
# This must happen first because mode: restart may cancel
# subsequent steps if the light state change fires immediately.
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (automation_state | combine({
state_motion_light_state: automation_state_enabling,
state_motion_light_last_action_timestamp: date_time_now,
state_motion_light_last_brightness: last_brightness
})) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Scene activation path # Scene activation path
- choose: - choose:
- conditions: - conditions:
@@ -988,19 +1373,6 @@ action:
target: target:
entity_id: "{{ resolved_all_switches }}" entity_id: "{{ resolved_all_switches }}"
# Update state to ENABLING (waiting for light state change confirmation)
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (automation_state | combine({
state_motion_light_state: automation_state_enabling,
state_motion_light_last_action_timestamp: date_time_now,
state_motion_light_last_brightness: last_brightness
})) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Execute enable callback action # Execute enable callback action
- choose: - choose:
- conditions: - conditions:
@@ -1074,15 +1446,49 @@ action:
- delay: - delay:
seconds: "{{ dim_duration }}" seconds: "{{ dim_duration }}"
# Read last_brightness before resetting state
- variables:
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
# Update state to NONE BEFORE turning off the light.
# This must happen first because mode: restart may cancel
# subsequent steps if the light state change fires during
# turn_off transition, which could falsely trigger manual override.
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Execute disable callback action (before turn-off to avoid restart cancellation)
- choose:
- conditions:
- condition: template
value_template: "{{ disable_action != [] }}"
sequence: !input disable_action
# Debug notification (before turn-off to avoid restart cancellation)
- choose:
- conditions: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Motion Light Debug"
message: >
Action: DISABLE
Time: {{ now().strftime('%H:%M:%S') }}
Timeout: {{ timeout }}s
Min On Duration: {{ min_on_duration }}s
Dim Before Off: {{ enable_dim_before_off }}
# Turn OFF or restore the lights # Turn OFF or restore the lights
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ resolved_all_lights | length > 0 }}" value_template: "{{ resolved_all_lights | length > 0 }}"
sequence: sequence:
- variables:
last_brightness: "{{ automation_state.get(state_motion_light_last_brightness, 0) | int }}"
- choose: - choose:
# Restore previous brightness if it was set # Restore previous brightness if it was set
- conditions: - conditions:
@@ -1113,33 +1519,3 @@ action:
- service: switch.turn_off - service: switch.turn_off
target: target:
entity_id: "{{ resolved_all_switches }}" entity_id: "{{ resolved_all_switches }}"
# Update state to NONE (ready for next motion event)
- service: input_text.set_value
target:
entity_id: "{{ automation_state_entity }}"
data:
value: >
{% set new_automation_state = (automation_state | combine({ state_motion_light_state: automation_state_none })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Execute disable callback action
- choose:
- conditions:
- condition: template
value_template: "{{ disable_action != [] }}"
sequence: !input disable_action
# Debug notification
- choose:
- conditions: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Motion Light Debug"
message: >
Action: DISABLE
Time: {{ now().strftime('%H:%M:%S') }}
Timeout: {{ timeout }}s
Min On Duration: {{ min_on_duration }}s
Dim Before Off: {{ enable_dim_before_off }}

View File

@@ -1,3 +1,3 @@
{ {
"version": "2.0.0" "version": "2.5.2"
} }