Sure. So the main one causing me issues is my presence lights automation. Here’s a rundown.
I have two key sensors per room, an aggregate person_detected
sensor which looks like this:
template:
- binary_sensor:
- name: Person detected bedroom
unique_id: 7585346108f14c3194e94a3396c1d96b_detector_bedroom
device_class: occupancy
state: >-
{% set presence_entities = [
"binary_sensor.bedroom_motion_sensor_occupancy",
"binary_sensor.bedroom_tv_in_use",
"switch.bedroom_closet_light",
] %}
{{ expand(presence_entities) | selectattr('state', 'eq', 'on') | list | count > 0 }}
Basically I built a list per room of all the things that when in use mean the room is occupied.
The second is a brightness sensor which looks like this:
template:
- binary_sensor:
- name: Bright bedroom
unique_id: "cdd8d74ab466499fb0d45fab796bf552"
device_class: light
state: >-
{% set lux = states('sensor.bedroom_illuminance_lux_stats') | float(-1.0) %}
{% set daytime = is_state('binary_sensor.daytime', 'on') %}
{% if daytime and lux >= 0 %}
{% set threshold = state_attr('light.bedroom', 'brightness') | int(0) / 5 + 75 %}
{{ lux >= threshold }}
{% else %}
{{ is_state('binary_sensor.bright_inside', 'on') }}
{% endif %}
Which essentially looks at the value of the illuminance sensor for that room and turns on when the light is over a certain level (so I know I don’t have to turn on the lights to make it bright). The threshold
also attempts to account for the brightness of the light (so they generally don’t turn on and immediately turn off). The bright_inside
piece in the else
is because the sensors are a little flaky sometimes so essentially what happens is if I haven’t heard from one in a while I ignore it and use the average of the others.
In addition to these two I have an input_select
per room (ex. input_select.presence_lights_bedroom
) with one of the following values: on
, off
, or override
. A room will only be adjusted by this automation when its input_select
has value on
.
All this feeds into this automation:
- id: a6ff604da4c843d98102bbcf0240e890_automation_presence_lights
alias: Set lights based on presence
description: Turn on and off rooms based on presence, light level and current overrides
mode: parallel
trace:
stored_traces: 15
variables: !include_dir_named /config/common/room_presence
trigger:
- id: input_change
platform: state
entity_id: !include_dir_merge_list /config/common/room_presence/inputs
to:
- platform: event
event_type: !include /config/common/reload_events.yaml
action:
- variables:
rooms: "{{ [area_name(trigger.entity_id)] if trigger.id == 'input_change' else rooms }}"
- alias: Loop through rooms
repeat:
count: "{{ rooms | count }}"
sequence:
- variables:
room: "{{ rooms[repeat.index - 1] | slugify }}"
- variables: &presence-lights-room-variables
controller: "{{ controllers | select('search', room) | first }}"
detector : "{{ detectors | select('search', room) | first }}"
light_sensor: "{{ light_sensors | select('search', room) | first }}"
off_scene: "{{ off_scenes | select('search', room) | first }}"
on_script: "{{ on_scripts | select('search', room) | first }}"
- alias: Stop if room controller isn't on
condition: "{{ is_state(controller, 'on') }}"
- choose:
- alias: Room is occupied
conditions: "{{ is_state(detector, 'on') }}"
sequence:
- service: "{{ on_script }}"
data:
skip_lights: "{{ is_state(light_sensor, 'on') }}"
brighter_only: true
default:
- alias: Room is unoccupied
service: scene.turn_on
data:
entity_id: "{{ off_scene }}"
transition: 2.5
!include_dir_named /config/common/room_presence
becomes:
controllers: ['input_select.presence_lights_bedroom', ...]
detectors: ['binary_sensor.person_detected_bedroom', ...]
light_sensors: ['binary_sensor.bright_bedroom', ...]
off_scenes: ['scene.bedroom_off', ...]
on_scripts: ['script.bedroom_on', ...]
rooms: ['Bedroom', 'Kitchen', ...]
!include_dir_merge_list /config/common/room_presence/inputs
becomes:
- input_select.presence_lights_bedroom
- binary_sensor.person_detected_bedroom
- binary_sensor.bright_bedroom
- ...repeat those 3 per room
!include /config/common/reload_events.yaml
becomes:
- automation_reloaded
- script_reloaded
- scene_reloaded
Note that the idea was to intentionally not rely on trigger
so that no matter how the automation ran it always just set all the rooms to the state they should be in. I realize there is one reference to trigger
in the first step but its optional. I put it in so that if someone tripped a motion sensor it tries to do that one first to optimize performance a bit. But it won’t break if trigger
is missing or whatever.
I considered that one option is probably to rewrite this to be dependent on trigger so one room being restored doesn’t fire the lights in the others. Perhaps that would be safer although I think there would still be a race condition between the brightness sensor and the person detected sensor in each room.
EDIT: I’m going to try tweaking it so if trigger.id == 'input_change'
then it only processes that room and not all the others to see how that works. That also means I have to change it to mode: parallel
but I don’t think that will be an issue since all runs will be seeing the same sensors and making the same decisions. It will still process all rooms for the other triggers but we’ll see how that impacts startup.
Also changing it because I got annoyed walking into a room and it took half a second to turn the lights on. Turns out two rooms triggered at the same time and the other one stopped mine (mode: restart
) so my room had to wait its turn at the end of the list. Can’t have that lol. Adjusted the automation above accordingly.