Compare commits
6 Commits
e98df855d9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 24f89e57b8 | |||
| a6ac4f4257 | |||
| 584c970044 | |||
| 4c03bc849e | |||
| eefc8993e3 | |||
| 9d19dfa8d3 |
@@ -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)
|
||||||
|
|||||||
@@ -275,9 +275,14 @@ variables:
|
|||||||
# CONDITIONS
|
# CONDITIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
condition:
|
condition:
|
||||||
# Only process events from the configured vacuum entity
|
# 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
|
- condition: template
|
||||||
value_template: "{{ event_entity_id == vacuum_entity }}"
|
value_template: >
|
||||||
|
{{ event_entity_id == vacuum_entity
|
||||||
|
or event_entity_id.startswith(vacuum_entity ~ '_') }}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ACTIONS
|
# ACTIONS
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -159,6 +158,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
|
||||||
|
|||||||
@@ -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: >
|
||||||
@@ -489,6 +478,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 +897,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
|
||||||
|
|
||||||
@@ -929,6 +940,8 @@ variables:
|
|||||||
|
|
||||||
# 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 +1046,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 }}
|
||||||
@@ -1249,13 +1258,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 %}
|
||||||
@@ -1384,7 +1395,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 %}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -2512,7 +2523,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 }}
|
||||||
|
|||||||
@@ -101,6 +101,19 @@ blueprint:
|
|||||||
max: 255
|
max: 255
|
||||||
mode: slider
|
mode: slider
|
||||||
|
|
||||||
|
min_brightness:
|
||||||
|
name: Minimum Brightness
|
||||||
|
description: >
|
||||||
|
Minimum brightness for all lights (1–255). 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:
|
none_action:
|
||||||
name: When Sensor is Unavailable
|
name: When Sensor is Unavailable
|
||||||
description: "Action to take when a sensor state is None, unknown, or unavailable."
|
description: "Action to take when a sensor state is None, unknown, or unavailable."
|
||||||
@@ -189,6 +202,7 @@ variables:
|
|||||||
lights: !input lights
|
lights: !input lights
|
||||||
max_fps: !input max_fps
|
max_fps: !input max_fps
|
||||||
brightness_override: !input brightness_override
|
brightness_override: !input brightness_override
|
||||||
|
min_brightness: !input min_brightness
|
||||||
none_action: !input none_action
|
none_action: !input none_action
|
||||||
default_color: !input default_color
|
default_color: !input default_color
|
||||||
enable_debug_notifications: !input enable_debug_notifications
|
enable_debug_notifications: !input enable_debug_notifications
|
||||||
@@ -222,6 +236,7 @@ action:
|
|||||||
**Pairs:** {{ pair_count }}
|
**Pairs:** {{ pair_count }}
|
||||||
**FPS:** {{ max_fps }}
|
**FPS:** {{ max_fps }}
|
||||||
**Brightness Override:** {{ brightness_override }}
|
**Brightness Override:** {{ brightness_override }}
|
||||||
|
**Min Brightness:** {{ min_brightness }}
|
||||||
|
|
||||||
**Sensor States:**
|
**Sensor States:**
|
||||||
{% for i in range(pair_count | int) %}
|
{% for i in range(pair_count | int) %}
|
||||||
@@ -286,7 +301,7 @@ action:
|
|||||||
entity_id: "{{ current_light }}"
|
entity_id: "{{ current_light }}"
|
||||||
data:
|
data:
|
||||||
rgb_color: "{{ default_color }}"
|
rgb_color: "{{ default_color }}"
|
||||||
brightness: "{{ brightness_override | int if brightness_override | int > 0 else 255 }}"
|
brightness: "{{ [brightness_override | int if brightness_override | int > 0 else 255, min_brightness | int] | max }}"
|
||||||
|
|
||||||
# keep_last — do nothing
|
# keep_last — do nothing
|
||||||
|
|
||||||
@@ -295,7 +310,7 @@ action:
|
|||||||
- variables:
|
- variables:
|
||||||
sensor_rgb: "{{ state_attr(current_sensor, 'rgb_color') | default([255, 255, 255]) }}"
|
sensor_rgb: "{{ state_attr(current_sensor, 'rgb_color') | default([255, 255, 255]) }}"
|
||||||
sensor_brightness: "{{ state_attr(current_sensor, 'brightness') | default(255) }}"
|
sensor_brightness: "{{ state_attr(current_sensor, 'brightness') | default(255) }}"
|
||||||
target_brightness: "{{ brightness_override | int if brightness_override | int > 0 else sensor_brightness | int }}"
|
target_brightness: "{{ [brightness_override | int if brightness_override | int > 0 else sensor_brightness | int, min_brightness | int] | max }}"
|
||||||
|
|
||||||
- service: light.turn_on
|
- service: light.turn_on
|
||||||
target:
|
target:
|
||||||
|
|||||||
@@ -141,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
|
||||||
@@ -393,7 +393,7 @@ blueprint:
|
|||||||
Some devices (especially Zigbee) report delayed state updates
|
Some devices (especially Zigbee) report delayed state updates
|
||||||
that can be mistaken for manual control. Increase this value
|
that can be mistaken for manual control. Increase this value
|
||||||
if you see false manual overrides in the debug log.
|
if you see false manual overrides in the debug log.
|
||||||
default: 10
|
default: 2
|
||||||
selector:
|
selector:
|
||||||
number:
|
number:
|
||||||
min: 0
|
min: 0
|
||||||
@@ -527,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
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -826,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 }}
|
||||||
|
|
||||||
@@ -836,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
|
||||||
@@ -867,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 -----
|
||||||
@@ -874,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') %}
|
||||||
@@ -897,12 +951,206 @@ 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:
|
||||||
|
- choose:
|
||||||
|
# If disable conditions are already met (motion cleared
|
||||||
|
# while light was still in ENABLING state), skip ENABLED
|
||||||
|
# and go straight to disable
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
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
|
- service: input_text.set_value
|
||||||
target:
|
target:
|
||||||
entity_id: "{{ automation_state_entity }}"
|
entity_id: "{{ automation_state_entity }}"
|
||||||
@@ -913,20 +1161,27 @@ action:
|
|||||||
|
|
||||||
# ----- 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
|
# Grace period: ignore state changes shortly after the automation
|
||||||
# turns on the light to avoid false manual override detection.
|
# turns on the light to avoid false manual override detection.
|
||||||
# Some devices (especially Zigbee) report delayed state updates.
|
|
||||||
- conditions:
|
- conditions:
|
||||||
- condition: template
|
- condition: template
|
||||||
value_template: >
|
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 last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) %}
|
||||||
{% set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(10)) %}
|
{% set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(2)) %}
|
||||||
{% if state_is_enabled and last_ts is not none %}
|
{% if state_is_enabled and last_ts is not none %}
|
||||||
{% set parsed = last_ts | as_datetime %}
|
{% set parsed = last_ts | as_datetime %}
|
||||||
{{ parsed is none or (now() - parsed).total_seconds() > grace }}
|
{{ parsed is none or (now() - parsed).total_seconds() > grace }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ state_is_enabled }}
|
{{ state_is_enabled }}
|
||||||
{% endif %}
|
{% 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
|
||||||
@@ -961,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)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
@@ -988,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:
|
||||||
@@ -1028,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:
|
||||||
@@ -1114,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:
|
||||||
@@ -1153,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 }}
|
|
||||||
|
|||||||
@@ -242,5 +242,5 @@ action:
|
|||||||
sequence:
|
sequence:
|
||||||
- service: telegram_bot.send_message
|
- service: telegram_bot.send_message
|
||||||
data:
|
data:
|
||||||
target: "{{ chat_id }}"
|
chat_id: "{{ chat_id }}"
|
||||||
message: "{{ reply_message }}"
|
message: "{{ reply_message }}"
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ action:
|
|||||||
sequence:
|
sequence:
|
||||||
- service: telegram_bot.send_message
|
- service: telegram_bot.send_message
|
||||||
data:
|
data:
|
||||||
target: "{{ chat_id }}"
|
chat_id: "{{ chat_id }}"
|
||||||
message: "{{ answers[button_index] }}"
|
message: "{{ answers[button_index] }}"
|
||||||
# Reply to original message unless we're deleting it
|
# Reply to original message unless we're deleting it
|
||||||
reply_to_message_id: >
|
reply_to_message_id: >
|
||||||
@@ -397,7 +397,7 @@ action:
|
|||||||
# Send the message with keyboard
|
# Send the message with keyboard
|
||||||
- service: telegram_bot.send_message
|
- service: telegram_bot.send_message
|
||||||
data:
|
data:
|
||||||
target: "{{ result_chat_ids }}"
|
chat_id: "{{ result_chat_ids }}"
|
||||||
message: "{{ message_text }}"
|
message: "{{ message_text }}"
|
||||||
inline_keyboard: "{{ inline_keyboard }}"
|
inline_keyboard: "{{ inline_keyboard }}"
|
||||||
config_entry_id: >
|
config_entry_id: >
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"version": "2.2.3"
|
"version": "2.5.2"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user