🚷 Automatic Room Occupancy

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 :smile:

:goal_net: 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.

:notebook_with_decorative_cover: 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.

:timer_clock: 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)

:computer: 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) :spiral_notepad:

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 :spiral_notepad:

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

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

  name: "[Occupancy] Auto Room Occupancy"
  description: >
    Automatically turn on and off an ocupancy switch for a single room.

    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)

      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)

      - Use bluetooth timeout input (Help needed)

  domain: automation

      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"
      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"
      name: Media Players (Optional)
      description: "A comma separated list of media_players in room. Include domain and name IE media_player.lounge_echo."
      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"
      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"
      name: Room Presence State (Optional)
      description: "A comma separated list of states that presence entity can match. EG 'lounge, lounge2'."
      default: "none"

      name: Enable Motion Occupancy
      description: "Turn on/off occupancy being set to 'on' via motion."
      default: true
      name: Enable Door Occupancy
      description: "Turn on/off occupancy being set to 'on' when door is shut."
      default: false
      name: Enable Media Occupancy
      description: "Turn on/off occupancy being set to 'on' via media."
      default: false
      name: Enable Presence Occupancy
      description: "Turn on/off occupancy being set to 'on' via presence."
      default: false

      name: Disable Mode (Optional)
      description: "An input_boolean to entirely disable this logic. Where 'on' disables."
      default: "none"
          domain: input_boolean

      name: Household Group (Optional)
      description: "A grouped set of people in your house. On logic will only run if home."
      default: "none"
          domain: group
      name: Guest Mode (Optional)
      description: "An input_boolean to allow logic to work when household is away."
      default: "none"
          domain: input_boolean
    # TARGET
      name: Occupancy Switch
      description: "An input_boolean that switches occupancy state in target room. On = occupied and Off = not_occupied."
          domain: input_boolean

  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


  # DOOR
  - platform: state
    entity_id: !input door_sensor

  - platform: state
    entity_id: !input motion_sensor
    to: 'on'
  - platform: state
    entity_id: !input motion_sensor
    to: 'off'
      minutes: "{{ states('input_number.motion_occupancy_timeout') | int(10) }}"

  - platform: state
    entity_id: !input media_players

  - platform: state
    entity_id: !input presence_entities

  - "{{ not blueprint_disabled }}"

  - choose:

      - conditions:
          - "{{ occupied_on_shut_door_enabled }}"
          - "{{ door_shut }}"
          - "{{ at_home }}"
          - service: input_boolean.turn_on
              entity_id: !input occupancy_switch

      - conditions:
          - "{{ occupied_on_motion_enabled }}"
          - "{{ is_motion }}"
          - "{{ at_home }}"
          - service: input_boolean.turn_on
              entity_id: !input occupancy_switch

      - conditions:
          - "{{ occupied_on_media_enabled }}"
          - "{{ media_is_playing }}"
          - "{{ at_home }}"
          - service: input_boolean.turn_on
              entity_id: !input occupancy_switch

      - conditions:
          - "{{ occupied_on_presence_enabled }}"
          - "{{ in_room }}"
          - "{{ at_home }}"
          - service: input_boolean.turn_on
              entity_id: !input occupancy_switch

      - 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! :)

          - service: input_boolean.turn_off
              entity_id: !input occupancy_switch

Cool blueprint! Minor typo correction:

Yeah, I really enjoy my media payers too. Whenever I walk into a room, they pay some people to play lots of music, and Home Assistant detects the deduction from my bank account.

On a more serious note, you forgot an L:

1 Like

Thanks for the heads up, all sorted.

I was trying to overly complicate the triggering so this has been re-done.
Gone is the nasty time repeated work around.

Next on the list is to add door sensors
Handy for rooms that are always occupied when door is shut EG bathrooms.

Also to allow motion logic for if there is movement in a room since the door was shut.

The automation is working. but when there is no more occupancy my boolean is not going back to False

All characteristics of a room occupied by a person who is either asleep or sitting still, like when reading.

Completely reliable, passive occupancy detection is a challenging goal. You may want to consider employing the Bayesian Binary Sensor to add some infererence into the equation. However, configuring it involves a fair bit of experimentation.

Yes agreed.

I use room-assistant to try and combat this. Its not 100% accurate but I have it pretty dam close for my use case.

In this case when I’m reading (and my phone is next to me) it reports that I’m in the room through Bluetooth.

I’ll look at the Bayesian sensor, thank you!
Do you think that could just replace this blueprint entirely or compliment it?

1 Like

Can you show me your automation config and I’ll take a look?

Thanks for the nice blueprint. Very helpful!

I spotted a small bug in your media player timeout logic:

{% set t = (time_now - media_timeout) * 60 %}

I’m pretty sure that your intention was to subtract the media_timeout value as minutes. The correct code would therefore be:

{% set t = time_now - (media_timeout * 60) %}

The parentheses are redundant in this case but they make it easier to read IMO :slightly_smiling_face:

That sounds like a proper solution for my very first smart home scenario problem (sitting in the office room on the desk, want to have lights and heating on where a motion sensor is not sufficient because of lack of movement). I think a motion sensor + door sensor + this blueprint are a powerful solution. Not sure if room assistant could add something to that.

Thanks for sharing and actively working on / improving it!

You can also leave out the parentheses because operator precedence will perform multiplication before subtraction.

{% set t = time_now - media_timeout * 60 %}

You can easily demonstrate it by pasting this into the Template Editor:

{{ 10 - 2 * 3 }}

The result will be 4 (not 24).

I have an idea: would you take into considerations that there might be a used device (Laptop for e.g. or printer) that is used in one room to mark it as occupied? It would be cool if that could be used.

Hi, I am trying to use your blueprint. It “creates” it, I can see in automations.yaml (multiple times) but it dos not show up in the UI… do you know why?


There is nothing to screen shot because it is not created in the UI (automations), it is in yaml in automations.yaml:

- id: '1617804849577'
  alias: '[Occupancy] Auto Room Occupancy'
  description: ''
    path: gdeboos/automatic-room-occupancy.yaml
      presence_states: none
      motion_sensor: binary_sensor.motion_sonoff_01
      occupancy_switch: input_boolean.occupancy_01
      media_players: none

(it is a test with 1 motion sensor now)

Reboot HA + clear cache?

Hi. Just installed this and I’m having the same issue. I created a few automations with this blueprint but they don’t show up in the UI.

I really like this blueprint. Thanks!
For me it would be usefull to add device_trackers. For example when I’m working from home, I don’t move so much and my office turns to off. Cheers!

Thank you for this blueprint. I am fairly new to HA. installed this weekend and migrated all my devices over. I am now venturing into the automation phase.

You had mention you can add helpers to change motion timeouts. Can I do this per room or its a global helper?

Any tips on how I can do room by room motion occupancy timeout?

I, in general, like Bayesian approaches, but it seems to be implemented strangely in home assistant. It would seen to me that some how it should age the data. Turning on a TV is almost certainly a sign that someone is in the room, but an hour later with no motion in the room would seem to me that the person has moved on. I would think that we would need to calculate the probability for each room. Maybe each minute or so and then compare with the number of people in the house and a number of rooms we’re willing to allow to be occupied (say we have three people in the house we might have a rule that says the that a room need to have a probability of greater than 35%-50% but that only the four highest probability rooms are occupied.
This is a quickly written, and I would enjoy working through some thoughts.