Skip to content

Instantly share code, notes, and snippets.

@AndySymons
Last active June 18, 2024 09:04
Show Gist options
  • Save AndySymons/368eb93915603fd9bb676f22499959f2 to your computer and use it in GitHub Desktop.
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
### ------------------------------------------------------------------------------------
###
### 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