Last active
June 25, 2025 02:30
Motion-Activated Light with Light Sensor and Mode Support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
blueprint: | |
name: Motion-Activated Light with Optional Modes and Light Sensor | |
description: > | |
Turns on lights with optional mode-based day/night logic. | |
Defensive template usage avoids undefined input errors. | |
Wait times are in minutes. Modes are CSV strings. Supports multiple lights and optional max-on-time. | |
domain: automation | |
author: Chris Benard (assisted by ChatGPT) | |
input: | |
motion_sensor: | |
name: Motion or Occupancy Sensor (Required) | |
selector: | |
entity: | |
domain: binary_sensor | |
device_class: | |
- motion | |
- occupancy | |
light_target: | |
name: Lights to Control (Required) | |
selector: | |
target: | |
entity: | |
domain: light | |
daytime_wait_minutes: | |
name: Day Timeout (minutes) (Required) | |
default: 5 | |
selector: | |
number: | |
min: 0.5 | |
max: 180 | |
step: 0.1 | |
unit_of_measurement: min | |
enable_max_on_time: | |
name: Enforce Max On Time if No Motion? (Optional) | |
default: false | |
selector: | |
boolean: {} | |
max_on_minutes: | |
name: Max On Time (minutes) (Optional, used if above enabled) | |
default: 30 | |
selector: | |
number: | |
min: 0.5 | |
max: 180 | |
step: 0.1 | |
unit_of_measurement: min | |
mode_select: | |
name: Mode Selector (input_select) (Optional) | |
default: null | |
selector: | |
entity: | |
domain: input_select | |
day_modes: | |
name: Day Modes (CSV) (Optional) | |
default: "" | |
selector: | |
text: | |
night_modes: | |
name: Night Modes (CSV) (Optional) | |
default: "" | |
selector: | |
text: | |
use_light_sensor: | |
name: Use Light Sensor? (Optional) | |
default: false | |
selector: | |
boolean: {} | |
light_sensor: | |
name: Light Sensor (Optional) | |
default: null | |
selector: | |
entity: | |
domain: sensor | |
device_class: illuminance | |
light_threshold: | |
name: Light Level Threshold (Optional, lx) | |
default: 50 | |
selector: | |
number: | |
min: 0 | |
max: 1000 | |
unit_of_measurement: lx | |
nighttime_wait_minutes: | |
name: Night Timeout (minutes) (Optional) | |
default: null | |
selector: | |
number: | |
min: 0.5 | |
max: 180 | |
step: 0.1 | |
unit_of_measurement: min | |
daytime_brightness: | |
name: Day Brightness (%) (Optional) | |
default: null | |
selector: | |
number: | |
min: 1 | |
max: 100 | |
unit_of_measurement: percent | |
nighttime_brightness: | |
name: Night Brightness (%) (Optional) | |
default: null | |
selector: | |
number: | |
min: 1 | |
max: 100 | |
unit_of_measurement: percent | |
mode: restart | |
trigger: | |
- platform: state | |
entity_id: !input motion_sensor | |
to: "on" | |
id: motion_detected | |
- platform: state | |
entity_id: !input motion_sensor | |
to: "off" | |
id: motion_cleared | |
variables: | |
motion_sensor: !input motion_sensor | |
light_target: !input light_target | |
daytime_wait_minutes: !input daytime_wait_minutes | |
nighttime_wait_minutes: !input nighttime_wait_minutes | |
enable_max_on_time: !input enable_max_on_time | |
max_on_minutes: !input max_on_minutes | |
mode_entity_id: !input mode_select | |
day_modes_str: !input day_modes | |
night_modes_str: !input night_modes | |
use_sensor: !input use_light_sensor | |
sensor_entity_id: !input light_sensor | |
threshold: !input light_threshold | |
daytime_brightness: !input daytime_brightness | |
nighttime_brightness: !input nighttime_brightness | |
light_entities: "{{ expand(light_target.entity_id) | map(attribute='entity_id') | list }}" | |
mode_enabled: >- | |
{{ | |
mode_entity_id is not none and | |
day_modes_str is defined and day_modes_str|trim != '' and | |
night_modes_str is defined and night_modes_str|trim != '' | |
}} | |
day_modes: >- | |
{% if day_modes_str is defined and day_modes_str|trim != '' %} | |
{{ day_modes_str.split(',') | map('trim') | list }} | |
{% else %} | |
[] | |
{% endif %} | |
night_modes: >- | |
{% if night_modes_str is defined and night_modes_str|trim != '' %} | |
{{ night_modes_str.split(',') | map('trim') | list }} | |
{% else %} | |
[] | |
{% endif %} | |
current_mode: >- | |
{{ | |
mode_enabled | |
and mode_entity_id is not none | |
and states[mode_entity_id] is not none | |
and states[mode_entity_id].state or '' | |
}} | |
is_day_mode: >- | |
{{ | |
mode_enabled | |
and current_mode in day_modes | |
or not mode_enabled | |
}} | |
is_night_mode: >- | |
{{ | |
mode_enabled | |
and current_mode in night_modes | |
or false | |
}} | |
day_wait: "{{ (daytime_wait_minutes | float) * 60 }}" | |
night_wait: >- | |
{{ | |
((nighttime_wait_minutes if nighttime_wait_minutes is not none else daytime_wait_minutes) | float) * 60 | |
}} | |
wait_time: >- | |
{{ | |
is_night_mode and mode_enabled | |
and night_wait | |
or day_wait | |
}} | |
brightness: >- | |
{{ nighttime_brightness if is_night_mode and mode_enabled and nighttime_brightness is not none | |
else daytime_brightness if is_day_mode and daytime_brightness is not none | |
else 'none' }} | |
max_time: "{{ (max_on_minutes | float(30)) * 60 }}" | |
action: | |
- if: | |
- condition: template | |
value_template: "{{ trigger.id == 'motion_detected' }}" | |
then: | |
- if: | |
- condition: template | |
value_template: > | |
{{ | |
not use_sensor | |
or | |
( | |
sensor_entity_id is not none | |
and | |
states[sensor_entity_id] is not none | |
and | |
states[sensor_entity_id] | float(0) <= threshold | float(0) | |
) | |
}} | |
then: | |
- if: | |
- condition: template | |
value_template: "{{ brightness != 'none' }}" | |
then: | |
- service: light.turn_on | |
data: | |
brightness_pct: "{{ brightness | float }}" | |
target: !input light_target | |
else: | |
- service: light.turn_on | |
target: !input light_target | |
- if: | |
- condition: template | |
value_template: "{{ trigger.id == 'motion_cleared' }}" | |
then: | |
- wait_for_trigger: | |
- platform: state | |
entity_id: !input motion_sensor | |
to: "on" | |
timeout: "{{ wait_time | float }}" | |
continue_on_timeout: true | |
- if: | |
- condition: template | |
value_template: > | |
{{ | |
is_state(motion_sensor, 'off') | |
and | |
expand(light_target.entity_id) | selectattr('state', 'eq', 'on') | list | count > 0 | |
}} | |
then: | |
- service: light.turn_off | |
target: !input light_target | |
- if: | |
- condition: template | |
value_template: "{{ enable_max_on_time }}" | |
then: | |
- wait_for_trigger: | |
- platform: state | |
entity_id: !input motion_sensor | |
to: "on" | |
timeout: "{{ max_time | float }}" | |
continue_on_timeout: true | |
- if: | |
- condition: template | |
value_template: > | |
{{ | |
is_state(motion_sensor, 'off') | |
and | |
expand(light_target.entity_id) | selectattr('state', 'eq', 'on') | list | count > 0 | |
}} | |
then: | |
- service: light.turn_off | |
target: !input light_target |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment