All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
- 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
1192 lines
45 KiB
YAML
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 }}
|