Last active
June 18, 2024 09:04
-
-
Save AndySymons/368eb93915603fd9bb676f22499959f2 to your computer and use it in GitHub Desktop.
Night Walk. Fades lights up and down in a dementia-friendly way, when motion detected at night
This file contains 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
### ------------------------------------------------------------------------------------ | |
### | |
### NIGHT WALK | |
### ----- ---- | |
### | |
### Controls one or more lights from one or more motion and illuminance sensors | |
### ... but delays first, in case user operates lights him/her self | |
### ... and fades up slowly to avoid confusion to dementia sufferer | |
### | |
### 17-Jun-24 | Andy Symons | First complete version | |
### 18-Jun-24 | Andy Symons | (a) Added stagger times (b) reversed order for turning off | |
### | |
### ------------------------------------------------------------------------------------ | |
blueprint: | |
name: Night Walk | |
description: | |
Fade up lights slowly when motion detected, but only if dark and if not | |
already turned on manually or by voice assistant. | |
Converse for turning off and only after a minimum on time. | |
domain: automation | |
input: | |
motion_sensors: | |
name: Motion sensor(s) | |
selector: | |
entity: | |
filter: | |
domain: binary_sensor | |
device_class: motion | |
multiple: true | |
dimmable_lights: | |
name: Dimmable light(s) | |
description: Light(s) to be faded up and down | |
selector: | |
entity: | |
filter: | |
domain: light | |
multiple: true | |
default: [] | |
non_dimmable_lights: | |
name: Non-dimmable light(s) | |
description: Light(s) to be simply turned on and off | |
selector: | |
entity: | |
filter: | |
- domain: light | |
multiple: true | |
default: [] | |
test_mode_switch: | |
name: Test mode switch | |
description: Switch or sensor that can be turned on to override the Night time / darkness conditions | |
selector: | |
entity: | |
filter: | |
- domain: | |
- binary_sensor | |
- input_boolean | |
- switch | |
multiple: false | |
default: "" | |
night_sensor: | |
name: Night sensor | |
description: Sensor that is 'true' for night time (the hours of darkness or low sun elevation) | |
selector: | |
entity: | |
filter: | |
- domain: | |
- binary_sensor | |
- switch | |
multiple: false | |
default: "" | |
illuminance_sensors: | |
name: Luminance sensor(s) | |
selector: | |
entity: | |
filter: | |
domain: sensor | |
device_class: illuminance | |
multiple: true | |
default: [] | |
stagger_time: | |
name: Stagger time | |
description: Gap between fade or switch starting times for of each light | |
selector: | |
number: | |
min: 0 | |
max: 30 | |
unit_of_measurement: seconds | |
default: 3 | |
fade_up_time: | |
name: Fade up time | |
description: Time to fade up from 0 to 100% | |
selector: | |
number: | |
min: 0 | |
max: 30 | |
unit_of_measurement: seconds | |
default: 20 | |
fade_down_time: | |
name: Fade down time | |
description: Time to fade down from 100 to 0% | |
selector: | |
number: | |
min: 0 | |
max: 30 | |
unit_of_measurement: seconds | |
default: 15 | |
on_delay: | |
name: On delay | |
description: Wait time before acting on detected motion | |
selector: | |
number: | |
min: 0 | |
max: 120 | |
unit_of_measurement: seconds | |
default: 3 | |
off_delay: | |
name: Off delay | |
description: Time to leave the lights on after last motion is detected. | |
selector: | |
number: | |
min: 0 | |
max: 120 | |
unit_of_measurement: seconds | |
default: 3 | |
on_timer: | |
name: On timer | |
description: The timer to be used to time the on-time | |
selector: | |
entity: | |
filter: | |
- domain: | |
- timer | |
multiple: false | |
minimum_on_time: | |
name: Minimum on time | |
description: Wait time before automatically turning off | |
selector: | |
number: | |
min: 0 | |
max: 60 | |
unit_of_measurement: minutes | |
default: 5 | |
logging_service_name: | |
name: LOGGING - Specify the service for logging | |
description: Enter service name (not filename) as a string | |
default: "" | |
mode: queued | |
max_exceeded: silent | |
trigger: | |
# motion detected | |
- platform: state | |
id: motion_detected | |
entity_id: !input motion_sensors | |
to: "on" | |
for: !input on_delay | |
# motion clear | |
- platform: state | |
id: motion_clear | |
entity_id: !input motion_sensors | |
to: "off" | |
for: !input off_delay | |
# On timer end | |
- platform: state | |
id: timer_ended | |
entity_id: !input on_timer | |
to: idle | |
variables: | |
local_dimmable_lights: !input dimmable_lights | |
local_illuminance_sensors: !input illuminance_sensors | |
local_night_sensor: !input night_sensor | |
local_non_dimmable_lights: !input non_dimmable_lights | |
local_test_mode_switch: !input test_mode_switch | |
# Handy intermediate variables | |
# identifies the source of the log messages | |
log_message_preamble: "{{ this.attributes.friendly_name }}" | |
there_are_dimmable_lights: "{{ local_dimmable_lights | list | count > 0 }}" | |
there_are_non_dimmable_lights: "{{ local_non_dimmable_lights | list | count > 0 }}" | |
# | |
# Identify the lights to change | |
# | |
# 1 Lights to fade up = dimmable lights in listed order that are currently off | |
lights_to_fade_up: > | |
{% set input_list = local_dimmable_lights %} | |
{% set ns = namespace(output_list=[]) %} | |
{% for light in input_list %} | |
{% if states(light) == 'off' %} | |
{% set ns.output_list = ns.output_list + [light] %} | |
{% endif %} | |
{% endfor %} | |
{{ ns.output_list }} | |
# 2 Lights to turn on = non-dimmable lights in listed order that are currently off | |
lights_to_turn_on: > | |
{% set input_list = local_non_dimmable_lights %} | |
{% set ns = namespace(output_list=[]) %} | |
{% for light in input_list %} | |
{% if states(light) == 'off' %} | |
{% set ns.output_list = ns.output_list + [light] %} | |
{% endif %} | |
{% endfor %} | |
{{ ns.output_list }} | |
# 3 Lights to fade down = dimmable lights in reverse of listed order that are currently on | |
lights_to_fade_down: > | |
{% set input_list = local_dimmable_lights %} | |
{% set ns = namespace(output_list=[]) %} | |
{% for light in input_list | reverse %} | |
{% if states(light) == 'on' %} | |
{% set ns.output_list = ns.output_list + [light] %} | |
{% endif %} | |
{% endfor %} | |
{{ ns.output_list }} | |
# 4 Lights to turn off = non-dimmable lights in reverse of listed order that are currently on | |
lights_to_turn_off: > | |
{% set input_list = local_non_dimmable_lights %} | |
{% set ns = namespace(output_list=[]) %} | |
{% for light in input_list | reverse %} | |
{% if states(light) == 'on' %} | |
{% set ns.output_list = ns.output_list + [light] %} | |
{% endif %} | |
{% endfor %} | |
{{ ns.output_list }} | |
action: | |
## ------------------------------------------------------------------------------------------------- | |
## ACTION[0,1] -- LOG THE TRIGGER | |
## ------------------------------------------------------------------------------------------------- | |
# First a blank line to start the new entry | |
- service: script.logfile_entry | |
data: | |
notification_service: !input logging_service_name | |
logfile_title: Night Walk | |
message_preamble: "{{ log_message_preamble }}" | |
message_body: --- | |
# Then the trigger details line | |
- service: script.logfile_entry | |
data: | |
notification_service: !input logging_service_name | |
logfile_title: Night Walk | |
message_preamble: "{{ log_message_preamble }}" | |
message_body: > | |
{% if trigger.id is undefined %} | |
{{ "Run manually." }} | |
{% elif trigger.entity_id is undefined %} | |
{{ "Triggered by: " + trigger.id | string + " (no entity id)" }} | |
{% else %} | |
{{ "Triggered by: " + trigger.id | string + " from '" + state_attr(trigger.entity_id, 'friendly_name') + "', state = " + states(trigger.entity_id) }} | |
{% endif %} | |
## ------------------------------------------------------------------------------------------------- | |
## ACTION[2] -- ACT ON THE TRIGGER | |
## ------------------------------------------------------------------------------------------------- | |
- choose: | |
# | |
# ACTION[2]CHOOSE[0] Motion detected and it is dark (or in test mode) | |
# | |
- conditions: | |
- alias: "Motion detected" | |
condition: trigger | |
id: motion_detected | |
- condition: or | |
conditions: | |
# (a) Test mode (there is a test mode switch and it is 'on') | |
- alias: "Test mode on" | |
condition: template | |
value_template: > | |
{{ states(local_test_mode_switch) is defined and states(local_test_mode_switch) == 'on' }} | |
# (b) It is night time (there is a night sensor and it says it is night) | |
- alias: "Night time" | |
condition: template | |
value_template: > | |
{{ states(local_night_sensor) is defined and states(local_night_sensor) == 'on' }} | |
# (c) Darkness is sensed (there is at least one luminance sensor that says it is dark) | |
- alias: "Low luminance" | |
condition: template | |
value_template: > | |
{% set ns = namespace(is_dark = false) %} | |
{% for sensor in local_illuminance_sensors %} | |
{% if states(sensor) is defined %} | |
{% if states(sensor) < 50 %} | |
{% set ns.is_dark = true %} | |
{% endif %} | |
{% endif %} | |
{% endfor %} | |
{{ ns.is_dark}} | |
sequence: | |
# ACTION[2]CHOOSE[0]SEQUENCE[0] Fade up dimmable lights | |
- if: | |
- condition: template | |
value_template: "{{ lights_to_fade_up | count > 0 }}" | |
then: | |
- repeat: | |
for_each: "{{ lights_to_fade_up }}" | |
sequence: | |
- service: light.turn_on | |
data: | |
brightness_pct: 100 | |
transition: !input fade_up_time | |
target: | |
entity_id: "{{ repeat.item }}" | |
# timer started only if a light is actually turned on | |
- service: timer.start | |
metadata: {} | |
data: | |
duration: | |
minutes: !input minimum_on_time | |
target: | |
entity_id: !input on_timer | |
# stagger the start times | |
- delay: | |
seconds: !input stagger_time | |
# ACTION[2]CHOOSE[0]SEQUENCE[1] Turn on any non-dimmable lights | |
- if: | |
- condition: template | |
value_template: "{{ lights_to_turn_on | count > 0 }}" | |
then: | |
- repeat: | |
for_each: "{{ lights_to_turn_on }}" | |
sequence: | |
- service: light.turn_on | |
data: {} | |
target: | |
entity_id: "{{ repeat.item }}" | |
# timer started only if a light is actually turned on | |
- service: timer.start | |
metadata: {} | |
data: | |
duration: | |
minutes: !input minimum_on_time | |
target: | |
entity_id: !input on_timer | |
# stagger the start times | |
- delay: | |
seconds: !input stagger_time | |
# | |
# ACTION[2]CHOOSE[1] Motion clear | |
# | |
- conditions: | |
- alias: "Motion clear" | |
condition: trigger | |
id: motion_clear | |
- alias: "The on timer is not still running" | |
condition: state | |
entity_id: !input on_timer | |
state: "idle" | |
sequence: | |
# Turn off non-dimmable lights | |
- if: | |
- condition: template | |
value_template: "{{ lights_to_turn_off | count > 0 }}" | |
then: | |
- repeat: | |
for_each: "{{ lights_to_turn_off }}" | |
sequence: | |
- service: light.turn_off | |
data: {} | |
target: | |
entity_id: "{{ repeat.item }}" | |
# stagger the turn-off times | |
- delay: | |
seconds: !input stagger_time | |
# Fade down dimmable lights | |
- if: | |
- condition: template | |
value_template: "{{ lights_to_fade_down | count > 0 }}" | |
then: | |
- repeat: | |
for_each: "{{ lights_to_fade_down }}" | |
sequence: | |
- service: light.turn_on | |
data: | |
brightness_pct: 0 | |
transition: !input fade_down_time | |
target: | |
entity_id: "{{ repeat.item }}" | |
# stagger the fade-down start times | |
- delay: | |
seconds: !input stagger_time | |
# | |
# ACTION[2]CHOOSE[2] Timer ended | |
# | |
- conditions: | |
- alias: " timer ended" | |
condition: trigger | |
id: timer_ended | |
- alias: "All motion sensors clear" | |
condition: state | |
entity_id: !input motion_sensors | |
state: "off" | |
sequence: | |
# Turn off non-dimmable lights | |
- if: | |
- condition: template | |
value_template: "{{ lights_to_turn_off | count > 0 }}" | |
then: | |
- repeat: | |
for_each: "{{ lights_to_turn_off }}" | |
sequence: | |
- service: light.turn_off | |
data: {} | |
target: | |
entity_id: "{{ repeat.item }}" | |
# stagger the turn-off times | |
- delay: | |
seconds: !input stagger_time | |
# Fade down dimmable lights | |
- if: | |
- condition: template | |
value_template: "{{ lights_to_fade_down | count > 0 }}" | |
then: | |
- repeat: | |
for_each: "{{ lights_to_fade_down }}" | |
sequence: | |
- service: light.turn_on | |
data: | |
brightness_pct: 0 | |
transition: !input fade_down_time | |
target: | |
entity_id: "{{ repeat.item }}" | |
# stagger the fade-down start times | |
- delay: | |
seconds: !input stagger_time |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment