Last active
September 10, 2025 11:11
-
-
Save FrankTub/ba9af2b147a8b6795d9b93299a82aab1 to your computer and use it in GitHub Desktop.
Wasp principle for HA
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: Occupancy | |
description: | | |
This blueprint implements the 'Wasp in a Box' principle, a method for detecting room occupancy based on motion and door sensors. It was inspired by an AppDaemon app with a similar approach. | |
You can find more information about this blueprint on the Home Assistant Community: https://community.home-assistant.io/t/occupancy-blueprint/477772. | |
Original credits go to [Alex Babel](https://community.home-assistant.io/u/alexbabel/summary). | |
Given that you already use a door and motion sensor this blueprint allows some additional configuration for scenario's where you want to deviate from the wasp in a box principle. | |
- (Optional) Clear occupancy on closing the door after a delay period | |
- (Optional) When door was opened and multiple motions have been detected a different turn off delay can be used (Open door mode) | |
Some sample use cases aside from the wasp in a box: | |
- For a toilet door you typically only close the door when entering, the next time you close the door you have left the room and the occupancy can be cleared right away -> Turn on "Clear occupancy on closing door after delay period" | |
- For a laundry room where you might not close the door you might want to have a different turn off delay when the door is open and multiple motions have been detected. For this take a look at ‘Open door mode’. | |
domain: automation | |
source_url: https://gist.github.com/FrankTub/ba9af2b147a8b6795d9b93299a82aab1 | |
input: | |
required_inputs: | |
name: "Required inputs" | |
icon: mdi:list-box-outline | |
collapsed: false | |
input: | |
door_sensor: | |
name: Single Door Sensor or Door Sensor Group | |
selector: | |
entity: | |
filter: | |
- domain: binary_sensor | |
- domain: input_boolean | |
motion_sensor: | |
name: Single Motion Sensor or Motion Sensor Group | |
selector: | |
entity: | |
filter: | |
- domain: binary_sensor | |
- domain: input_boolean | |
occupancy_helper: | |
name: Occupancy Helper (Type input_boolean) | |
description: This helper tracks occupancy status and can be used as a trigger for automations, such as turning lights on or off. | |
selector: | |
entity: | |
domain: input_boolean | |
multiple: false | |
last_motion_helper: | |
name: Last Motion Helper (Type date and time) | |
description: This helper logs the last motion detection time to ensure occupancy clears correctly when the door is closed. | |
selector: | |
entity: | |
domain: input_datetime | |
multiple: false | |
configuration_settings: | |
name: "Configuration settings" | |
icon: mdi:tools | |
collapsed: true | |
input: | |
motion_sensor_turn_off_delay: | |
name: Motion Sensor turn off delay | |
description: "Time after the motion sensor no longer detects motion to set the occupancy to clear. (Default = 5s)" | |
default: 5 | |
selector: | |
number: | |
mode: box | |
min: 0 | |
max: 3600 | |
unit_of_measurement: seconds | |
step: 1.0 | |
door_sensor_turn_off_delay: | |
name: Door Sensor turn off delay | |
description: "In order to automatically set the occupancy to clear when the door was opened and left open. This only clears the occupancy when there was no motion detected between opening the door and the period of this turn off delay period. (Default = 15s)" | |
default: 15 | |
selector: | |
number: | |
mode: box | |
min: 0 | |
max: 3600 | |
unit_of_measurement: seconds | |
step: 1.0 | |
motion_sensor_delay: | |
name: Motion Sensor Delay | |
description: | | |
The time the motion sensor takes before clearing its detected state. (Default=-1 [Disabled]) | |
It is recommended to add a small buffer to prevent false positives. For example, if your motion sensor has a 10-second delay, set this to 12s. | |
default: -1 | |
selector: | |
number: | |
mode: box | |
min: -1 | |
max: 3600 | |
unit_of_measurement: seconds | |
step: 1.0 | |
start_on_opening_door: | |
name: Start occupancy on opening the door | |
description: | | |
Turn on occupancy when the door is opened. | |
default: true | |
selector: | |
boolean: | |
reset_on_closing_door_after_delay_period: | |
name: Clear occupancy on closing door after delay period | |
description: | | |
Turn off occupancy when the door is closed after the greatest delay period (meaning Motion Sensor Delay + Motion Sensor turn off delay OR Door Sensor turn off delay) with a minimum of 20 seconds after the occupancy was turned on. This can be useful for doors that are not used frequently and prevent guests from doubting if they have to manually turn off a light, e.g. a toilet door. Other scenario where this could be usefull is a room where you don't necessarily close the door while using the room and when you leave the room you typically close the door, e.g. a laundry room. | |
default: false | |
selector: | |
boolean: | |
open_door_mode: | |
name: "Open door mode" | |
icon: mdi:walk | |
collapsed: true | |
input: | |
open_door_motion_counter: | |
name: Motion counter with door open (Type input_number) | |
description: (Optional) Create an input_number with min=0, max=100, step=1 and select it here. This counts the number of motion detections while the door is open. Can be usefull for scenario's where someone likes to use the toilet with the door open | |
default: null | |
selector: | |
entity: | |
filter: | |
- domain: input_number | |
multiple: false | |
open_door_turn_off_delay: | |
name: Open door mode turn-off delay | |
description: (Optional) Timeout to clear occupancy once in open door mode. This is only used if you have provided above option (Motion counter with door open) and the door is open and at least 2 times motion has been detected. | |
default: 300 | |
selector: | |
number: | |
mode: box | |
min: 1 | |
max: 3600 | |
unit_of_measurement: seconds | |
step: 1.0 | |
trigger_variables: | |
motion_sensor: !input motion_sensor | |
motion_sensor_delay: !input motion_sensor_delay | |
variables: | |
door_sensor: !input door_sensor | |
last_motion_helper: !input last_motion_helper | |
start_on_opening_door: !input start_on_opening_door | |
reset_on_closing_door_after_delay_period: !input reset_on_closing_door_after_delay_period | |
occupancy_helper: !input occupancy_helper | |
motion_sensor_turn_off_delay: !input motion_sensor_turn_off_delay | |
door_sensor_turn_off_delay: !input door_sensor_turn_off_delay | |
open_door_motion_counter: !input open_door_motion_counter | |
open_door_motion_count_threshold: 2 | |
open_door_turn_off_delay: !input open_door_turn_off_delay | |
open_door_motion_counter_enabled: "{{ open_door_motion_counter is not none }}" | |
trigger: | |
- platform: state | |
entity_id: !input motion_sensor | |
from: "off" | |
to: "on" | |
id: motion | |
- platform: template | |
value_template: "{{ states(motion_sensor) in ['true', 'on'] }}" | |
enabled: "{{ motion_sensor_delay > 0 }}" | |
for: | |
seconds: !input motion_sensor_delay | |
id: motion_still_on | |
- platform: state | |
entity_id: !input door_sensor | |
id: door_opened | |
from: "off" | |
to: "on" | |
- platform: state | |
entity_id: !input door_sensor | |
id: door_closed | |
from: "on" | |
to: "off" | |
- platform: state | |
entity_id: !input motion_sensor | |
from: "on" | |
to: "off" | |
for: !input motion_sensor_turn_off_delay | |
id: no_motion | |
- platform: state | |
entity_id: !input door_sensor | |
to: "on" | |
for: !input door_sensor_turn_off_delay | |
id: door_left_open | |
- platform: state | |
entity_id: !input motion_sensor | |
from: "on" | |
to: "off" | |
for: !input open_door_turn_off_delay | |
id: open_door_mode_no_motion | |
condition: [] | |
action: | |
- choose: | |
############################################################ | |
# ANCHOR: Turning occupancy on | |
############################################################ | |
- conditions: | |
- "{{ trigger.id in ['motion', 'motion_still_on'] }}" | |
sequence: | |
- if: | |
- "{{ trigger.id == 'motion' }}" | |
then: | |
- service: input_datetime.set_datetime | |
data: | |
timestamp: "{{ as_timestamp(now()) }}" | |
entity_id: "{{ last_motion_helper }}" | |
- if: | |
- "{{ trigger.id == 'motion' }}" | |
- "{{ states(occupancy_helper) in ['false', 'off'] }}" | |
then: | |
- service: input_boolean.turn_on | |
data: | |
entity_id: "{{ occupancy_helper }}" | |
- if: | |
- "{{ open_door_motion_counter_enabled }}" | |
- "{{ states(door_sensor) in ['true', 'on'] }}" | |
then: | |
- service: input_number.increment | |
target: | |
entity_id: "{{ open_door_motion_counter }}" | |
- conditions: | |
- "{{ trigger.id == 'door_opened' }}" | |
sequence: | |
- if: | |
- "{{ states(occupancy_helper) in ['false', 'off'] }}" | |
- "{{ start_on_opening_door }}" | |
then: | |
- service: input_boolean.turn_on | |
data: | |
entity_id: "{{ occupancy_helper }}" | |
############################################################ | |
# ANCHOR: Turning occupancy off | |
############################################################ | |
- conditions: | |
- "{{ states(occupancy_helper) in ['true', 'on'] }}" | |
- or: | |
- and: | |
- "{{ trigger.id == 'no_motion' }}" | |
- or: | |
- and: | |
- "{{ states(door_sensor) in ['true', 'on', 'unavailable', 'unknown'] }}" | |
- or: | |
- "{{ open_door_motion_counter_enabled and states(open_door_motion_counter) | int < open_door_motion_count_threshold }}" | |
- "{{ not open_door_motion_counter_enabled }}" | |
- and: | |
- "{{ states(door_sensor) in ['false', 'off'] }}" | |
- "{{ states[door_sensor].last_changed > as_local(as_datetime(states(last_motion_helper))) }}" | |
- "{{ motion_sensor_delay == -1 or (motion_sensor_delay > -1 and states[door_sensor].last_changed >= states[motion_sensor].last_changed - timedelta(seconds=motion_sensor_delay)) }}" | |
- and: | |
- "{{ trigger.id == 'open_door_mode_no_motion' }}" | |
- "{{ open_door_motion_counter_enabled }}" | |
- "{{ states(door_sensor) in ['true', 'on', 'unavailable', 'unknown'] }}" | |
- "{{ now().timestamp() - states[occupancy_helper].last_changed.timestamp() >= open_door_turn_off_delay }}" | |
- and: | |
- or: | |
- "{{ trigger.id == 'door_closed' }}" | |
- and: | |
- "{{ trigger.id == 'door_left_open' }}" | |
- or: | |
- "{{ open_door_motion_counter_enabled and states(open_door_motion_counter) | int < open_door_motion_count_threshold }}" | |
- "{{ not open_door_motion_counter_enabled }}" | |
- "{{ states(motion_sensor) in ['false', 'off'] }}" | |
- "{{ states[door_sensor].last_changed > as_local(as_datetime(states(last_motion_helper))) }}" | |
- and: | |
- "{{ trigger.id == 'door_closed' }}" | |
- "{{ reset_on_closing_door_after_delay_period }}" | |
- "{{ now().timestamp() - states[occupancy_helper].last_changed.timestamp() > max(door_sensor_turn_off_delay, max(0, motion_sensor_delay) + motion_sensor_turn_off_delay, 20) }}" | |
sequence: | |
- service: input_boolean.turn_off | |
data: | |
entity_id: "{{ occupancy_helper }}" | |
default: [] | |
############################################################ | |
# ANCHOR: Resetting number of motions if necessary | |
############################################################ | |
- if: | |
- "{{ open_door_motion_counter_enabled }}" | |
- or: | |
- and: | |
- "{{ states(occupancy_helper) in ['false','off'] }}" | |
- or: | |
- "{{ states(door_sensor) in ['false','off'] }}" | |
- "{{ states(open_door_motion_counter) | int >= open_door_motion_count_threshold }}" | |
- "{{ trigger.id in ['door_closed', 'door_opened'] }}" | |
then: | |
- service: input_number.set_value | |
target: | |
entity_id: "{{ open_door_motion_counter }}" | |
data: | |
value: 0 | |
mode: queued | |
max: 10 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment