Files
haos-blueprints/Common/Motion Light.yaml
alexei.dolgolyov f6679f73e3
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
Add advanced features to Motion Light blueprint
- Multiple lights/switches control with group and area targeting
- Smooth light transitions with configurable duration
- Time-based conditions (only active during specified hours)
- Day/Night mode with separate light settings
- Scene support (activate scenes instead of light parameters)
- Minimum on duration to prevent rapid on/off cycling
- Dim before off for visual warning
- Motion sensor debounce to filter false triggers
- Debug notifications for troubleshooting
2026-01-25 04:41:10 +03:00

1192 lines
45 KiB
YAML

# =============================================================================
# Motion Light Automation Blueprint for Home Assistant
# =============================================================================
# This blueprint creates a smart motion-activated light control system.
# It handles motion detection, luminance-based triggering, and manual override
# detection to provide intelligent lighting automation.
#
# Features:
# - Multiple motion sensor support (triggers on ANY sensor)
# - Condition switches (ALL must be ON for automation to work)
# - Multiple lights and/or switches control
# - Light groups and area-based targeting
# - Configurable timeout delay before turning off
# - Minimum on duration (prevents rapid on/off cycling)
# - Motion sensor debounce (filter false triggers)
# - Smooth light transitions with configurable duration
# - Luminance sensor support (only trigger in dark conditions)
# - Time-based conditions (only active during specified hours)
# - Day/Night mode (different light settings based on time)
# - Scene support (activate scenes instead of light parameters)
# - Dim before off (visual warning before turning off)
# - Manual override detection (stops automation if user changes light)
# - Brightness threshold (only trigger if light is dim)
# - Custom light parameters (brightness, color, etc.)
# - Callback actions for enable/disable/manual events
# - Debug notifications for troubleshooting
#
# State Machine:
# The automation tracks these states via persistent storage:
# - NONE (0): Idle, waiting for motion
# - ENABLING (2): Light turn-on command sent, waiting for state change
# - ENABLED (1): Light is ON and controlled by automation
# - MANUAL (3): User took control, automation paused until light turns off
#
# Behavior Notes:
# - 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
# - MANUAL mode exits when light is turned OFF (by any means)
# - Timeout delay only applies when turning OFF (motion cleared)
# - Time conditions support overnight windows (e.g., 22:00 to 06:00)
# - Day/Night mode uses separate time window from time conditions
#
# Requirements:
# - At least one motion sensor
# - input_text entity for persistent state storage
# - Target light(s), switch(es), group, or area to control
#
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
# =============================================================================
blueprint:
name: "Custom: Motion Light"
description: >
Smart motion sensor automation blueprint.
Note: By default will not run if light was already ON. If light was turned
ON during automation running, the automation will enter manual state and
will not trigger until the light is turned off.
Note: Not tested when motion sensors and state sensors are used at the same time.
domain: automation
# ===========================================================================
# INPUT CONFIGURATION
# ===========================================================================
input:
# -------------------------------------------------------------------------
# Motion & Condition Sensors
# -------------------------------------------------------------------------
controls:
name: "Controls"
collapsed: false
input:
motion_sensors:
name: Motion sensors
description: >
Select one or more motion sensors. Light turns ON if ANY sensor
detects motion (OR logic).
default: []
selector:
entity:
domain:
- binary_sensor
- switch
- group
- light
multiple: true
condition_switches:
name: Condition switches
description: >
Automation will not trigger if ANY of these switches is OFF.
All must be ON for the automation to work (AND logic).
default: []
selector:
entity:
domain:
- input_boolean
- switch
- group
- light
- binary_sensor
multiple: true
# -------------------------------------------------------------------------
# Target Devices
# -------------------------------------------------------------------------
devices_group:
name: "Devices"
collapsed: false
input:
target_lights:
name: Target Lights (optional)
description: One or more lights to control
default: []
selector:
entity:
domain: light
multiple: true
target_switches:
name: Target Switches (optional)
description: One or more switches to control
default: []
selector:
entity:
domain: switch
multiple: true
target_light_group:
name: Target Light Group (optional)
description: A light group entity to control as a single unit
default: null
selector:
entity:
domain: light
target_area:
name: Target Area (optional)
description: >
Area ID (e.g., "living_room"). All lights and switches in the
area will be discovered and controlled.
default: ""
selector:
text:
target_light_data:
name: Light Data Dictionary (optional)
default: ""
description: >
Provide a YAML dictionary of light.turn_on parameters.
If not specified, the light's last settings are preserved.
Example:
brightness: 200
color_temp_kelvin: 4000
rgb_color: [255, 0, 0]
selector:
object: {}
brightness_threshold:
name: Brightness Threshold
description: >
Automation only triggers if the light's current brightness is
below this value. Set to 0 to disable this check.
default: 0
selector:
number:
min: 0
max: 255
step: 1
transition_duration:
name: Transition Duration
description: >
Duration in seconds for smooth light transitions.
Set to 0 for instant changes.
default: 1
selector:
number:
min: 0
max: 10
step: 0.5
unit_of_measurement: "s"
timeout_delay:
name: Timeout Delay (seconds)
description: >
Delay before turning off the light after all motion sensors
clear. Set to 0 for immediate turn off.
default: 120
selector:
number:
min: 0
max: 3600
step: 5
unit_of_measurement: seconds
min_on_duration:
name: Minimum On Duration (seconds)
description: >
Light must stay on for at least this duration even if motion
clears. Prevents rapid on/off cycling.
default: 10
selector:
number:
min: 0
max: 300
step: 5
unit_of_measurement: "seconds"
# -------------------------------------------------------------------------
# Persistent State Configuration
# -------------------------------------------------------------------------
persistent_state:
name: "Persistent State"
collapsed: true
input:
automation_state_entity:
name: Automation state entity
description: >
`input_text` entity that stores the automation state in JSON format.
Required for manual override detection and state tracking.
Doesn't require specific initial state - values can be empty.
Each automation instance must have its own entity.
selector:
entity:
domain: input_text
automation_state_placeholder_key:
name: Automation state placeholder key
description: >
Overrides key for persistent storage if not empty.
By default uses the target light/switch entity ID.
Don't override if you don't understand the meaning.
default: ''
selector:
text:
# -------------------------------------------------------------------------
# Luminance Control
# -------------------------------------------------------------------------
luminance:
name: "Luminance"
collapsed: true
input:
luminance_sensor:
name: Luminance sensor (optional)
description: Sensor reporting ambient light level (lux)
default: null
selector:
entity:
domain: sensor
luminance_threshold:
name: Luminance threshold (optional)
description: >
Light will only turn on if luminance sensor value is below
this threshold (darker than this level).
default: 50
selector:
number:
min: 0
max: 10000
step: 5
unit_of_measurement: "lux"
luminance_enable_switch:
name: Luminance control enable switch (optional)
description: >
Switch or input_boolean to enable/disable luminance-based control.
When OFF, luminance check is skipped.
default: null
selector:
entity:
domain:
- switch
- input_boolean
# -------------------------------------------------------------------------
# Time-Based Control
# -------------------------------------------------------------------------
time_based_control:
name: "Time-Based Control"
collapsed: true
input:
enable_time_condition:
name: Enable Time Condition
description: Only allow automation during specified time window
default: false
selector:
boolean:
time_after:
name: Active After
description: Automation only active after this time
default: "00:00:00"
selector:
time:
time_before:
name: Active Before
description: Automation only active before this time
default: "23:59:59"
selector:
time:
# -------------------------------------------------------------------------
# Day/Night Settings
# -------------------------------------------------------------------------
day_night_settings:
name: "Day/Night Settings"
collapsed: true
input:
enable_day_night_mode:
name: Enable Day/Night Mode
description: Use different light settings based on time of day
default: false
selector:
boolean:
night_mode_after:
name: Night Mode After
description: Switch to night settings after this time
default: "22:00:00"
selector:
time:
night_mode_before:
name: Night Mode Before
description: Switch to day settings after this time
default: "06:00:00"
selector:
time:
day_light_data:
name: Day Light Settings
description: Light parameters during day mode (YAML dictionary)
default: ""
selector:
object: {}
night_light_data:
name: Night Light Settings
description: Light parameters during night mode (YAML dictionary)
default: ""
selector:
object: {}
# -------------------------------------------------------------------------
# Scene Support
# -------------------------------------------------------------------------
scene_support:
name: "Scene Support"
collapsed: true
input:
use_scene_instead:
name: Use Scene Instead of Light Data
description: Activate a scene instead of setting light parameters
default: false
selector:
boolean:
scene_entity:
name: Scene Entity
description: Scene to activate when motion detected
default: null
selector:
entity:
domain: scene
night_scene_entity:
name: Night Scene Entity (optional)
description: Scene to activate during night mode (if day/night enabled)
default: null
selector:
entity:
domain: scene
# -------------------------------------------------------------------------
# Dim Before Off
# -------------------------------------------------------------------------
dim_before_off:
name: "Dim Before Off"
collapsed: true
input:
enable_dim_before_off:
name: Enable Dim Before Off
description: Gradually dim light before turning off completely
default: false
selector:
boolean:
dim_brightness:
name: Dim Brightness Level
description: Brightness to dim to before turning off (1-255)
default: 25
selector:
number:
min: 1
max: 255
step: 5
dim_duration:
name: Dim Duration (seconds)
description: How long to stay dimmed before turning off
default: 5
selector:
number:
min: 1
max: 60
step: 1
unit_of_measurement: "seconds"
# -------------------------------------------------------------------------
# Motion Sensor Settings
# -------------------------------------------------------------------------
motion_settings:
name: "Motion Sensor Settings"
collapsed: true
input:
motion_debounce:
name: Motion Debounce (seconds)
description: >
Motion must be sustained for this duration before triggering.
Helps filter out brief false triggers. Set to 0 to disable.
default: 0
selector:
number:
min: 0
max: 30
step: 1
unit_of_measurement: "seconds"
# -------------------------------------------------------------------------
# Debug
# -------------------------------------------------------------------------
debug:
name: "Debug"
collapsed: true
input:
enable_debug_notifications:
name: Enable Debug Notifications
description: Send persistent notifications for debugging automation behavior
default: false
selector:
boolean:
# -------------------------------------------------------------------------
# Callback Actions
# -------------------------------------------------------------------------
actions:
name: "Actions"
collapsed: true
input:
user_condition:
name: Condition block
description: Optional condition(s) that must pass for actions to run
default: []
selector:
condition: {}
enable_action:
name: Enable callback action (optional)
description: Runs when light is turned ON by this automation
default: []
selector:
action: {}
disable_action:
name: Disable callback action (optional)
description: Runs when light is turned OFF by this automation
default: []
selector:
action: {}
manual_action_runs_disable_action:
name: Manual also runs disable action
description: >
If checked, entering manual mode will also execute the
disable callback action.
default: false
selector:
boolean: {}
manual_action:
name: Manual callback action (optional)
description: >
Runs when user manually changes the light while automation is active.
Requires 'Automation state entity' to be configured.
default: []
selector:
action: {}
# =============================================================================
# AUTOMATION MODE
# =============================================================================
# Restart mode ensures rapid motion events don't queue up
mode: restart
# =============================================================================
# TRIGGERS
# =============================================================================
trigger:
# Motion sensors ON (with debounce)
- platform: state
entity_id: !input motion_sensors
to: "on"
for:
seconds: !input motion_debounce
id: "motion_sensor_on"
# Motion sensors OFF
- platform: state
entity_id: !input motion_sensors
to: "off"
id: "motion_sensor_off"
# Condition switches ON/OFF
- platform: state
entity_id: !input condition_switches
# Light state changed (for manual override detection)
- platform: state
entity_id: !input target_lights
id: "light_state_changed"
# Switch state changed (for manual override detection)
- platform: state
entity_id: !input target_switches
id: "switch_state_changed"
# Luminance sensor value changed
- platform: template
value_template: >
{% if luminance_sensor %}
{{ states(luminance_sensor) not in ['unknown','unavailable'] }}
{% else %}
false
{% endif %}
# Luminance enable switch changed
- platform: template
value_template: >
{% if luminance_enable_switch %}
{{ states(luminance_enable_switch) not in ['unknown','unavailable'] }}
{% else %}
false
{% endif %}
# =============================================================================
# CONDITIONS
# =============================================================================
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
# ---------------------------------------------------------------------------
# These define the possible automation states stored in persistent storage
automation_state_invalid: '-1' # Error state
automation_state_none: '0' # Idle, waiting for motion
automation_state_enabled: '1' # Light is ON and controlled by automation
automation_state_enabling: '2' # Turn-on command sent, awaiting confirmation
automation_state_manual: '3' # User took control, automation paused
# Persistent state JSON keys
state_motion_light_state: 'mls' # Current state machine state
state_motion_light_last_action_timestamp: 'mllat' # Last action timestamp
state_motion_light_last_brightness: 'mllb' # Brightness before automation
# ---------------------------------------------------------------------------
# Trigger Context
# ---------------------------------------------------------------------------
date_time_now: "{{ now() }}"
trigger_id: "{{ trigger.id }}"
# ---------------------------------------------------------------------------
# Input Variables
# ---------------------------------------------------------------------------
sensors: !input motion_sensors
condition_switches: !input condition_switches
timeout: !input timeout_delay
min_on_duration: !input min_on_duration
brightness_threshold: !input brightness_threshold
transition_duration: !input transition_duration
# ---------------------------------------------------------------------------
# Target Device Resolution
# ---------------------------------------------------------------------------
target_lights: !input target_lights
target_switches: !input target_switches
target_light_group: !input target_light_group
target_area: !input target_area
# Resolve all lights from direct selection, groups, and areas
resolved_all_lights: >-
{% set result = [] %}
{% if target_lights | length > 0 %}
{% set result = result + target_lights %}
{% endif %}
{% if target_light_group is not none %}
{% set result = result + [target_light_group] %}
{% endif %}
{% if target_area != '' %}
{% set area_lights = area_entities(target_area) | select('match', '^light\\.') | list %}
{% set result = result + area_lights %}
{% endif %}
{% set seen = namespace(items=[]) %}
{% for item in result %}
{% if item not in seen.items %}
{% set seen.items = seen.items + [item] %}
{% endif %}
{% endfor %}
{{ seen.items }}
# Resolve all switches from direct selection and areas
resolved_all_switches: >-
{% set result = [] %}
{% if target_switches | length > 0 %}
{% set result = result + target_switches %}
{% endif %}
{% if target_area != '' %}
{% set area_switches = area_entities(target_area) | select('match', '^switch\\.') | list %}
{% set result = result + area_switches %}
{% endif %}
{{ result | unique | list }}
# Reference light for state checks (first available)
reference_light: "{{ resolved_all_lights[0] if resolved_all_lights | length > 0 else none }}"
# Check if any device is on
any_device_on: >
{% set lights_on = resolved_all_lights | 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 }}
all_devices_off: "{{ not any_device_on }}"
# Legacy compatibility aliases
light_entity: "{{ reference_light }}"
switch_entity: "{{ resolved_all_switches[0] if resolved_all_switches | length > 0 else none }}"
# ---------------------------------------------------------------------------
# Persistent State Management
# ---------------------------------------------------------------------------
automation_state_entity: !input automation_state_entity
# Parse global state JSON from input_text entity
automation_state_global: >
{% set text = states(automation_state_entity) | string %}
{% if text in ['unknown','unavailable','none',''] %}
{{ dict() }}
{% else %}
{{ text | from_json }}
{% endif %}
automation_state_placeholder_key: !input automation_state_placeholder_key
# Determine the key for this automation's state
automation_state_key: >
{% if automation_state_placeholder_key != '' %}
{{ automation_state_placeholder_key }}
{% elif switch_entity is not none %}
{{ switch_entity }}
{% elif light_entity is not none %}
{{ light_entity }}
{% else %}
{{ 'default_motion_light_placeholder' }}
{% endif %}
# Get this automation's state from global state
# BUG FIX: Changed from 'light_entity != ""' to proper none check
automation_state: "{{ automation_state_global.get(automation_state_key, dict()) if (light_entity is not none or switch_entity is not none) else dict() }}"
# Current state machine state
motion_light_state: "{{ automation_state.get(state_motion_light_state, automation_state_none) }}"
# Track last action timestamp
motion_light_last_action_timestamp: >
{% if trigger_id == 'state_motion' %}
{{ date_time_now }}
{% else %}
{{ automation_state.get(state_motion_light_last_action_timestamp, none) }}
{% endif %}
# State machine state checks (for readability)
state_is_none: "{{ (motion_light_state | string) == automation_state_none }}"
state_is_enabled: "{{ (motion_light_state | string) == automation_state_enabled }}"
state_is_enabling: "{{ (motion_light_state | string) == automation_state_enabling }}"
state_is_manual: "{{ (motion_light_state | string) == automation_state_manual }}"
# ---------------------------------------------------------------------------
# Callback Actions
# ---------------------------------------------------------------------------
manual_action: !input manual_action
disable_action: !input disable_action
enable_action: !input enable_action
manual_action_runs_disable_action: !input manual_action_runs_disable_action
light_data: !input target_light_data
# ---------------------------------------------------------------------------
# Luminance Configuration
# ---------------------------------------------------------------------------
luminance_sensor: !input luminance_sensor
luminance_threshold: !input luminance_threshold
luminance_enable_switch: !input luminance_enable_switch
# Check if luminance conditions allow triggering
luminance_ok: >
{% if luminance_sensor is not none and luminance_threshold is not none %}
{% set val = states(luminance_sensor) | float(0) %}
{% set enabled = true %}
{% if luminance_enable_switch %}
{% set enabled = is_state(luminance_enable_switch, 'on') %}
{% endif %}
{{ enabled and val < luminance_threshold }}
{% else %}
{{ true }}
{% endif %}
# ---------------------------------------------------------------------------
# Time-Based Control
# ---------------------------------------------------------------------------
enable_time_condition: !input enable_time_condition
time_after: !input time_after
time_before: !input time_before
time_condition_ok: >
{% if not enable_time_condition %}
{{ true }}
{% else %}
{% set now_time = now().strftime('%H:%M:%S') %}
{% set after = time_after | string %}
{% set before = time_before | string %}
{% if after <= before %}
{{ after <= now_time <= before }}
{% else %}
{# Spans midnight (e.g., 22:00 to 06:00) #}
{{ now_time >= after or now_time <= before }}
{% endif %}
{% endif %}
# ---------------------------------------------------------------------------
# Day/Night Mode
# ---------------------------------------------------------------------------
enable_day_night_mode: !input enable_day_night_mode
night_mode_after: !input night_mode_after
night_mode_before: !input night_mode_before
day_light_data: !input day_light_data
night_light_data: !input night_light_data
is_night_mode: >
{% if not enable_day_night_mode %}
{{ false }}
{% else %}
{% set now_time = now().strftime('%H:%M:%S') %}
{% set after = night_mode_after | string %}
{% set before = night_mode_before | string %}
{% if after <= before %}
{{ after <= now_time <= before }}
{% else %}
{{ now_time >= after or now_time <= before }}
{% endif %}
{% endif %}
effective_light_data: >
{% if enable_day_night_mode %}
{{ night_light_data if is_night_mode else day_light_data }}
{% else %}
{{ light_data }}
{% endif %}
# ---------------------------------------------------------------------------
# Scene Support
# ---------------------------------------------------------------------------
use_scene_instead: !input use_scene_instead
scene_entity: !input scene_entity
night_scene_entity: !input night_scene_entity
effective_scene: >
{% if use_scene_instead %}
{% if enable_day_night_mode and is_night_mode and night_scene_entity is not none %}
{{ night_scene_entity }}
{% else %}
{{ scene_entity }}
{% endif %}
{% else %}
{{ none }}
{% endif %}
# ---------------------------------------------------------------------------
# Dim Before Off
# ---------------------------------------------------------------------------
enable_dim_before_off: !input enable_dim_before_off
dim_brightness: !input dim_brightness
dim_duration: !input dim_duration
# ---------------------------------------------------------------------------
# Debug
# ---------------------------------------------------------------------------
enable_debug_notifications: !input enable_debug_notifications
# ---------------------------------------------------------------------------
# Trigger Evaluation
# ---------------------------------------------------------------------------
# Check if ALL condition switches are ON (AND logic)
all_of_condition_switches_on: >
{% set e = condition_switches if condition_switches is iterable else [condition_switches] %}
{% if e | length == 0 %}
{{ true }}
{% else %}
{{ (e | select('is_state', 'on') | list | length) == (e | length) }}
{% endif %}
# Count how many motion sensors are currently detecting motion
count_of_enabled_sensor: >
{% set e = sensors if sensors is iterable else [sensors] %}
{{ e | select('is_state', 'on') | list | length }}
# Motion state checks
motion_on: "{{ count_of_enabled_sensor > 0 }}"
motion_all_off: "{{ count_of_enabled_sensor == 0 }}"
# ---------------------------------------------------------------------------
# Enable/Disable Decision Logic
# ---------------------------------------------------------------------------
# Should we enable the light? (All conditions must be met)
must_be_enabled_preview: >
{{ (all_of_condition_switches_on and luminance_ok and motion_on and time_condition_ok) | bool }}
must_be_enabled_guard: "{{ state_is_none }}"
must_be_enabled: >
{{ must_be_enabled_preview and must_be_enabled_guard }}
# Should we disable the light? (Motion cleared OR condition switch turned off)
must_be_disabled_preview: >
{{ ((not all_of_condition_switches_on) or motion_all_off) | bool }}
must_be_disabled_guard: "{{ state_is_enabled }}"
must_be_disabled: >
{{ must_be_disabled_preview and must_be_disabled_guard }}
# =============================================================================
# ACTIONS
# =============================================================================
action:
# ---------------------------------------------------------------------------
# DEBUG: Log basic info (enable by setting is_base_debug: true)
# ---------------------------------------------------------------------------
- choose:
- conditions:
- condition: template
value_template: "{{ is_base_debug }}"
sequence:
- service: persistent_notification.create
data:
title: "Debug Info - Motion Light"
message: >
must_be_enabled_preview: {{ must_be_enabled_preview }},
must_be_disabled_preview: {{ must_be_disabled_preview }},
must_be_disabled: {{ must_be_disabled }},
must_be_disabled_guard: {{ must_be_disabled_guard }},
trigger_id: {{ trigger.id }}
# ===========================================================================
# MAIN STATE MACHINE
# ===========================================================================
- choose:
# -----------------------------------------------------------------------
# CASE 1: Light/Switch State Changed (Manual Override Detection)
# -----------------------------------------------------------------------
# Handles state changes from the light/switch itself to detect
# when automation turned it on vs when user manually changed it
- conditions:
- condition: template
value_template: "{{ trigger_id == 'light_state_changed' or trigger_id == 'switch_state_changed' }}"
sequence:
- choose:
# ----- Sub-case: Light/Switch turned OFF -----
# Reset to NONE state so automation can work again
- conditions:
- condition: template
value_template: >
{# BUG FIX: Changed from 'res = false' to 'res = true' for AND logic #}
{% set res = true %}
{% if light_entity is not none %}
{% set brightness = state_attr(light_entity, 'brightness') | int(0) %}
{% set light_off = is_state(light_entity, 'off') or brightness < brightness_threshold %}
{% set res = res and light_off %}
{% endif %}
{% if switch_entity is not none %}
{% set res = res and is_state(switch_entity, 'off') %}
{% endif %}
{# Only true if we have at least one device and all are off #}
{{ res and (light_entity is not none or switch_entity is not none) }}
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 }}
# ----- Sub-case: Automation just turned on the light -----
# Transition from ENABLING to ENABLED
- conditions:
- condition: template
value_template: "{{ state_is_enabling }}"
sequence:
- 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 -----
# Transition from ENABLED to MANUAL (user took control)
- conditions:
- condition: template
value_template: "{{ state_is_enabled }}"
sequence:
# BUG FIX: Fixed YAML structure - was 'data: >' instead of 'data:' with 'value: >'
- 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_manual })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
# Call disable action if configured
- choose:
- conditions: "{{ manual_action_runs_disable_action and disable_action != [] }}"
sequence: !input disable_action
# Call manual action callback
- choose:
- conditions: "{{ manual_action != [] }}"
sequence: !input manual_action
# Debug notification
- choose:
- conditions: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Motion Light Debug"
message: >
Action: MANUAL OVERRIDE
Time: {{ now().strftime('%H:%M:%S') }}
Previous State: ENABLED
New State: MANUAL
Trigger: {{ trigger_id }}
# -----------------------------------------------------------------------
# CASE 2: Enable Path (Motion Detected, Should Turn On)
# -----------------------------------------------------------------------
- conditions:
- condition: template
value_template: "{{ must_be_enabled }}"
sequence:
- choose:
# Guard: Stop if any device is already ON
# (Don't hijack user-controlled lights)
- conditions:
- condition: template
value_template: "{{ any_device_on }}"
sequence:
- stop: "Light is already ON when sensors were triggered"
# Enable the light/switch
default:
# Store current brightness (to restore later if configured)
- variables:
last_brightness: >
{% if reference_light is none or is_state(reference_light, 'off') %}
{{ 0 }}
{% else %}
{{ state_attr(reference_light, 'brightness') | int(0) }}
{% endif %}
# Scene activation path
- 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/switches
default:
# Turn ON the 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 the switches
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_switches | length > 0 }}"
sequence:
- service: switch.turn_on
target:
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
- 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: ENABLE
Time: {{ now().strftime('%H:%M:%S') }}
Lights: {{ resolved_all_lights }}
Switches: {{ resolved_all_switches }}
Scene: {{ effective_scene if use_scene_instead else 'N/A' }}
Night Mode: {{ is_night_mode }}
# -----------------------------------------------------------------------
# CASE 3: Disable Path (Motion Cleared, Should Turn Off)
# -----------------------------------------------------------------------
- conditions:
- condition: template
value_template: "{{ must_be_disabled }}"
sequence:
# Calculate minimum on duration remaining
- variables:
time_since_enabled: >
{% set last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) %}
{% if last_ts is none %}
{{ 9999 }}
{% else %}
{% set parsed = last_ts | as_datetime %}
{% if parsed is none %}
{{ 9999 }}
{% else %}
{{ (now() - parsed).total_seconds() | int }}
{% endif %}
{% endif %}
remaining_min_on: "{{ [0, min_on_duration - time_since_enabled] | max }}"
# Wait remaining minimum on duration if needed
- choose:
- conditions:
- condition: template
value_template: "{{ remaining_min_on > 0 }}"
sequence:
- delay:
seconds: "{{ remaining_min_on }}"
# Wait for timeout before turning off
- delay:
seconds: "{{ timeout }}"
# Dim before off (if enabled)
- choose:
- conditions:
- condition: template
value_template: "{{ enable_dim_before_off and resolved_all_lights | length > 0 }}"
sequence:
- service: light.turn_on
target:
entity_id: "{{ resolved_all_lights }}"
data:
brightness: "{{ dim_brightness }}"
transition: "{{ transition_duration }}"
- delay:
seconds: "{{ dim_duration }}"
# Turn OFF or restore the 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:
# Restore previous brightness if it was set
- 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 }}"
# Otherwise turn off completely
default:
- service: light.turn_off
target:
entity_id: "{{ resolved_all_lights }}"
data:
transition: "{{ transition_duration }}"
# Turn OFF the switches
- choose:
- conditions:
- condition: template
value_template: "{{ resolved_all_switches | length > 0 }}"
sequence:
- service: switch.turn_off
target:
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 }}