Automatic Room Occupancy
This blueprint will toggle an input_boolean that signifies if a room is occupied or not. Uses multiple inputs to determine this such as motion, media players and room presence sensors EG room-assistant.
This is very much a work in progress.
I have a to-do list (see below) and any help on this would be much appreciated. Also any suggestions are greatly appreciated
Goal
I (like many) have room level automations that rely on knowing if someone is in the room or not. Accuracy is the issue here and I have found that no one solution works well on its own, and it is best to combine multiple layers of logic.
Prerequisites
You need an input_boolean to signify if the room is occupied. You will need a minimum of one of the following for your room:
- Motion Sensor(s) – create a group if multiple
- Door Sensor(s) – create a group if multiple
- Media Players
- Presence Sensors – see room-assistant
This will work best with 2 or all of the above. If you only have a motion sensor this will be overkill and there may be better suited blueprints for you.
Optional Timeout Helpers
These will all default to 10 minutes if not provided. For best results add these helpers so you can tweak things to your liking on your own front end.
- input_number.motion_occupancy_timeout
- input_number.media_occupancy_timeout
- input_number.bluetooth_occupancy_timeout (NOT IN USE YET)
Logic
The room will be occupied when ANY of the following are met:
- Motion (for any amount of time)
- Door is shut (useful for rooms EG bathrooms)
- Media is playing in the room on at least one device
- Presence sensor(s) show at least 1 person in room
Optional: You can have it only trigger when someone is home or guest mode is on.
Note: You do not need all of these for this to work as all are optional. These can also individually be turned off. This is handy if you have an unreliable method for switching occupancy on, but still want it to be a condition of the off state.
The room will not be occupied when ALL of the following conditions are met:
- No motion
- No media playing in room
- Presence sensors do not report anyone in room
Note: Each of these (BT still todo) have a timeout feature. See helpers section above. This will default to 10 minutes if helper does not exist.
To-Do / Wishlist (Help Needed)
Use bluetooth timeout input
I want to use a timeout feature but I cant use last_updated or last_changed. As the state may have changed from one room to another that does not include this room. EG this room is bedroom but they have moved from lounge to kitchen within timeout period.
“Wasp in a box”
Have the room always occupied if the door is shut and there has been any motion since it was shut.
Changelog
2020.12.23: Better error handling if motion or door not provided
2020.12.23: Added door sensor and ability to have room occupied if door is shut
2020.12.22: Trigger on media or presence state changes. Remove repeating trigger.
2020.12.21: Initial Version
Blueprint Code
blueprint:
name: "[Occupancy] Auto Room Occupancy"
description: >
Automatically turn on and off an ocupancy switch for a single room.
------LOGIC------
Occupied when ANY of the following conditions are met (all are optional).
- door is shut
- any motoion
- any media player playing
- door is shut (for rooms eg bathrooms)
- on room presence (eg room-assistant)
Occupancy is clared under ALL of the following conditions being met (all are optional):
- motion off for a set amount of time
- no player in room played for a set amount of time
- no one in room (TODO//: add a timer)
------HELPERS------
Uses the following helpers to allow for front end control of timeouts.
All default to 10 minutes if not provided.
- input_number.motion_occupancy_timeout
- input_number.media_occupancy_timeout
- input_number.bluetooth_occupancy_timeout (NOT IN USE YET)
------TODO------
- Use bluetooth timeout input (Help needed)
domain: automation
input:
# SENSORS USED
motion_sensor:
name: Motion Sensor
description: "A motion sensor or group of motion sensors in room. State must be either 'on' or 'off'."
default: "binary_sensor.none"
selector:
entity:
door_sensor:
name: Door Sensor
description: "A door sensor or group of door sensors in room. State must be either 'on' or 'off'."
default: "binary_sensor.none"
selector:
entity:
media_players:
name: Media Players (Optional)
description: "A comma separated list of media_players in room. Include domain and name IE media_player.lounge_echo."
media_states:
name: Media States to Match (optional)
description: "A comma separated list of states a Media Player can be in when room is occupied. Default is 'playing'."
default: "playing"
presence_entities:
name: Room Presence Entities (Optional)
description: "A comma separated list of room presence entities. Include domain and name IE 'sensor.phone_1_room_presence, sensor.phone_2_room_presence'."
default: "sensor.none"
presence_states:
name: Room Presence State (Optional)
description: "A comma separated list of states that presence entity can match. EG 'lounge, lounge2'."
default: "none"
# ENABLE / DISABLE INDIVIDUAL FEATURES
occupied_on_motion_enabled:
name: Enable Motion Occupancy
description: "Turn on/off occupancy being set to 'on' via motion."
default: true
selector:
boolean:
occupied_on_shut_door:
name: Enable Door Occupancy
description: "Turn on/off occupancy being set to 'on' when door is shut."
default: false
selector:
boolean:
occupied_on_media_enabled:
name: Enable Media Occupancy
description: "Turn on/off occupancy being set to 'on' via media."
default: false
selector:
boolean:
occupied_on_presence_enabled:
name: Enable Presence Occupancy
description: "Turn on/off occupancy being set to 'on' via presence."
default: false
selector:
boolean:
# GLOBAL DISABLE
disabled_entity_id:
name: Disable Mode (Optional)
description: "An input_boolean to entirely disable this logic. Where 'on' disables."
default: "none"
selector:
entity:
domain: input_boolean
# HOME SENSORS
household_group:
name: Household Group (Optional)
description: "A grouped set of people in your house. On logic will only run if home."
default: "none"
selector:
entity:
domain: group
guest_mode_switch:
name: Guest Mode (Optional)
description: "An input_boolean to allow logic to work when household is away."
default: "none"
selector:
entity:
domain: input_boolean
# TARGET
occupancy_switch:
name: Occupancy Switch
description: "An input_boolean that switches occupancy state in target room. On = occupied and Off = not_occupied."
selector:
entity:
domain: input_boolean
variables:
time_now: "{{ as_timestamp(now()) }}"
media_players_str: !input media_players
media_players: "{{ media_players_str.split(',') | map('trim') | list }}"
media_states_str: !input media_states
media_states: "{{ media_states_str.split(',') | map('trim') | list }}"
media_timeout: "{{ states('input_number.media_occupancy_timeout') | int(10) }}"
media_is_playing: "{{ expand(media_players) | selectattr('state','in',media_states) | list | count > 0 }}"
presence_entities_str: !input presence_entities
presence_entities: "{{ presence_entities_str.split(',') | map('trim') | list }}"
presence_states_str: !input presence_states
presence_states: "{{ presence_states_str.split(',') | map('trim') | list }}"
presence_timeout: "{{ states('input_number.bluetooth_occupancy_timeout') | int(10) }}"
in_room: "{{ expand(presence_entities) | selectattr('state','in',presence_states) | list | count > 0 }}"
motion_sensor: !input motion_sensor
motion_timeout: "{{ states('input_number.motion_occupancy_timeout') }}"
motion_last_changed: "{{ states[motion_sensor].last_changed.timestamp() | float(0) }}"
motion_is_timed_out: "{{ (time_now - motion_last_changed) > motion_timeout | float(10) }}"
is_motion: "{{ is_state(motion_sensor, 'on') }}"
door_sensor: !input door_sensor
door_shut: "{{ is_state(door_sensor, 'off') }}"
occupied_on_motion_enabled: !input occupied_on_motion_enabled
occupied_on_shut_door_enabled: !input occupied_on_shut_door
occupied_on_media_enabled: !input occupied_on_media_enabled
occupied_on_presence_enabled: !input occupied_on_presence_enabled
disabled_entity_id: !input disabled_entity_id
blueprint_disabled: "{{ disabled_entity_id != 'none' and is_state(disabled_entity_id, 'on') }}"
household_group: !input household_group
guest_mode_switch: !input guest_mode_switch
house_home: "{{ household_group == 'none' or is_state(household_group, 'home') }}"
guests_home: "{{ guest_mode_switch == 'none' or is_state(guest_mode_switch, 'on') }}"
at_home: "{{ house_home or guests_home }}"
mode: parallel
trigger:
# DOOR
- platform: state
entity_id: !input door_sensor
# MOTION
- platform: state
entity_id: !input motion_sensor
to: 'on'
- platform: state
entity_id: !input motion_sensor
to: 'off'
for:
minutes: "{{ states('input_number.motion_occupancy_timeout') | int(10) }}"
# MEDIA
- platform: state
entity_id: !input media_players
# PRESENCE
- platform: state
entity_id: !input presence_entities
condition:
- "{{ not blueprint_disabled }}"
action:
- choose:
# OCCUPIED IF DOOR SHUT
- conditions:
- "{{ occupied_on_shut_door_enabled }}"
- "{{ door_shut }}"
- "{{ at_home }}"
sequence:
- service: input_boolean.turn_on
data:
entity_id: !input occupancy_switch
# OCCUPIED ON MOTION
- conditions:
- "{{ occupied_on_motion_enabled }}"
- "{{ is_motion }}"
- "{{ at_home }}"
sequence:
- service: input_boolean.turn_on
data:
entity_id: !input occupancy_switch
# OCCUPIED ON MEDIA PLAY
- conditions:
- "{{ occupied_on_media_enabled }}"
- "{{ media_is_playing }}"
- "{{ at_home }}"
sequence:
- service: input_boolean.turn_on
data:
entity_id: !input occupancy_switch
# OCCUPIED ON PRESENCE
- conditions:
- "{{ occupied_on_presence_enabled }}"
- "{{ in_room }}"
- "{{ at_home }}"
sequence:
- service: input_boolean.turn_on
data:
entity_id: !input occupancy_switch
# CLEAR OCCUPANCY
- conditions:
- "{{ not is_motion }}"
- "{{ motion_is_timed_out }}"
# NO MEDIA - none of the players has played in the last x minutes
- >
{% set t = (time_now - media_timeout) * 60 %}
{% set ns = namespace(not_playing=[]) %}
{% for player in media_players %}
{% for state in media_states %}
{% if states[player].last_changed is defined %}
{% set timed_out = states[player].last_changed.timestamp() < t %}
{% else %}
{% set timed_out = true %}
{% endif %}
{% if states(player) != state and timed_out %}
{% set ns.not_playing = ns.not_playing + [ player.entity_id ] %}
{% endif %}
{% endfor %}
{% endfor %}
{{ ns.not_playing | length >= media_players | length }}
# NO PRESENCE - none of the presence entities in room
- "{{ not in_room }}"
# TODO// Need to add timeout IE time since last in room.
# But cant use last_updated or last_changed as may have changed from one room to another that does not include this room
# EG this room is bedroom but they have moved from lounge to kitchen within timeout period.
# HELP NEEDED! :)
sequence:
- service: input_boolean.turn_off
data:
entity_id: !input occupancy_switch