Skip to content

Detecting Room Occupation

Sean edited this page Sep 15, 2020 · 13 revisions

Updated 9/15/2020 - Tweaked the automations to be less confusing and more uniform. The changes yield the same results.


Ever since Google decided to restrict their API and locked everyone out from using the Bluetooth from the Home Minis for Presence Detection, I've had to completely rethink how I want Home Assistant to detect which rooms my wife or I are in.

Without being able to rely on a constant stream of constantly updating data like Bluetooth RSSI, I had to completely rethink my methods of detection and design a kind of "Room Occupation Logic" that could not only detect when a room was occupied, but also maintain that occupation when variables would change such as walking room-to-room and accounting for multiple people in the home.

Please note that this is a constant work in progress and I am continually making tweaks to the system to have it more accurate and representative of our lifestyle. Your home and lifestyle are probably wildly different than mine and you should adjust as appropriate.


First, my detection primarily is dependent on motion sensors which also controls our lighting. The automations are heavily designed around the triggering of our motion sensors. If you don't have any, there are ways to trigger presence, but these are probably the best method for the initial detection. The Wyze Motion Sensors are the cheapest I have found so far and can integrate with Home Assistant with this Custom Component

Second, here's my way of thinking when it comes to triggering and then maintaining room occupancy just to give you an idea how and why I set the automations up as I have.

Upon motion trigger, the lights will come on in the room as well as trigger an input boolean for that room. From there, a timer begins in the event motion is no longer detected for X minutes. If no more motion is detected, then the lights and input boolean simply shut off until the next motion event. However, if a device, such as the TV, is currently in use then the timer will repeat itself until it sees that device is no longer in use.

Pretty simple so far although things get a little more complicated when you add more people and rooms to the mix which makes finding a harmony a bit more challenging.

In our home, I detect occupation for three rooms: Living Room, Office, and Bedroom. I did try adding bathrooms as well but those caused way more problems than I wanted to deal with. Something that helped me was determining what our "Master Room" would be, which is the room we spend a majority of our time in. For us, this is our Living Room and the system will assume we are in there upon occupation and will maintain it longer than the other rooms by default.

Input Booleans

To begin this process we will need to first create our Input Booleans. These are what the system will look to see are triggered or not to determine the next steps it should take. I only have three, but you can add more as necessary.

  office_occupied:
    name: Office Occupied
    initial: false

  living_room_occupied:
    name: Living Room Occupied
    initial: false

  bedroom_occupied:
    name: Bedroom Occupied
    initial: false

Binary Sensors

I recently discovered just how powerful Binary Sensors can be when using them in a template. I have created three binary sensors, one for each room. With each sensor, they each have several other sensors that all must be in a certain state before the binary sensor ever triggers. This is great for when you don't want to mess around with a lot of different conditions in your YAML and you only have to worry about a single trigger.

You'll notice that the "and" condition is used for every additional sensor that I want monitored. This is a must because I want to be absolutely sure that the room is unoccupied before the automations begin. No one likes to be in the dark. When creating your own, just substitute your own entities and states in the YAML below.

  - platform: template
    sensors:
      office_unoccupied:
        friendly_name: "Office Unoccupied"
        value_template: >-
          {{ is_state('binary_sensor.wyzesense_779b1988', 'off')
             and is_state('binary_sensor.office_motion_motion_sensor', 'off')
             and not is_state('sensor.steam_76561197976250572', 'online')
             and not is_state('sensor.steam_76561198154412871', 'online') }}

  - platform: template
    sensors:
      living_room_unoccupied:
        friendly_name: "Living Room Unoccupied"
        value_template: >-
          {{ is_state('binary_sensor.wyzesense_779b29d3', 'off')
             and is_state('binary_sensor.motion_sensor_158d00031b4c8a', 'off')
             and is_state('binary_sensor.motion_sensor_158d0002047afe', 'off')
             and not is_state('media_player.plex_living_room_tv', 'paused')
             and not is_state('media_player.plex_living_room_tv', 'playing')
             and not is_state('media_player.living_room_tv', 'playing') }}

  - platform: template
    sensors:
      bedroom_unoccupied:
        friendly_name: "Bedroom Unoccupied"
        value_template: >-
          {{ is_state('binary_sensor.wyzesense_778c2557', 'off')
             and is_state('binary_sensor.motion_sensor_158d000237a64a', 'off')
             and not is_state('media_player.bedroom_tv', 'paused')
             and not is_state('media_player.bedroom_tv', 'playing')
             and not is_state('media_player.bedroom_speaker', 'playing')
             and is_state('input_boolean.bedtime_emily', 'off')
             and is_state('input_boolean.bedtime_sean', 'off') }}

Automations

The Living Room

Let's get to the automations and begin with our Master Room. Here's what I use for determining if our Living Room is occupied.

  - alias: Living Room occupied
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id: group.living_room_motion
        to: 'on'
      - platform: template
        value_template: "{{ not is_state('media_player.living_room_tv', 'unavailable') }}" # NOT condition
      - platform: template
        value_template: "{{ not is_state('media_player.plex_living_room_tv', 'unavailable') }}" # NOT condition
    condition:
      - condition: state
        entity_id: group.family
        state: 'home'
    action:
      - service: input_boolean.turn_on
        data:
          entity_id: input_boolean.living_room_occupied

The Triggers

  1. The first trigger is a motion trigger. I use several motion sensors per room and then group them. The group allows for only one sensor, rather than all, to be triggered to start the whole automation. This also makes for cleaner YAML and it's much easier to maintain a group rather than add more entities to every automation you want to change.
  2. Triggers two and three are for our Shield TV. Basically whenever the TV is on a certain channel or something playing on Plex, the system knows we are obviously in the Living Room. They are using "Not Conditions" so whenever their state is something other than unavailable (essentially whenever the TV is on), the automation triggers.

The Conditions

  1. We have two cats and we don't want to accidentally have our system triggered when neither of us are home. This condition prevents that.

The Action

  1. Simply enough, this triggers the Living Room input boolean on.

And now here's where things will get a little confusing and will require a bit of planning. Here's what I have for determining when the Living Room is unoccupied.

  - alias: Living Room unoccupied
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id: binary_sensor.living_room_unoccupied
        to: 'on'
        for:
          minutes: 3
    condition:
      condition: and
      conditions:
        - condition: state
          entity_id: input_boolean.living_room_occupied
          state: 'on'
        - condition: or
          conditions:
            - condition: state
              entity_id: input_boolean.office_occupied
              state: 'on'
              for:
                minutes: '5'
            - condition: state
              entity_id: input_boolean.bedroom_occupied
              state: 'on'
              for:
                minutes: '5'
    action:
      - service: input_boolean.turn_off
        data:
          entity_id: input_boolean.living_room_occupied

The Triggers

  1. Once the Binary Sensor sees that all monitored entities are in their correct states for three minutes, the automation begins.

The Conditions

  1. The first condition just prevents the automation from firing every unless the input boolean is already on. Not totally necessary, but I prefer to have it in there.
  2. These two conditions are important. If either the Office or Bedroom have been occupied for X minutes, the system assumes that those rooms are now occupied rather than the Living Room.

The Action

  1. If all the conditions are met, the Living Room input boolean will be turned off allowing for other automations to begin turning off lights.

The Office

Let's get to the other rooms now. I only have two, but you may have more you want to detect and you will have to adjust accordingly. The Office is next.

  - alias: Office occupied
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id: group.office_motion
        to: 'on'
      - platform: state
        entity_id: sensor.steam_XXXXXXXXXXXXXX
        to: 'online'
    condition:
      - condition: state
        entity_id: group.family
        state: 'home'
    action:
      - service: input_boolean.turn_on
        data:
          entity_id: input_boolean.office_occupied

The Triggers

  1. Self explanatory, any motion will trigger the automation.
  2. I use the Steam Sensor component to monitor if I'm currently on my computer or not. If I go AFK for five minutes, the status goes from online to away.

The Conditions

  1. Once again, cats.

The Action

  1. The Office input boolean is turned on. This also begins the timer to determine if we are in the Living Room still or not.

And now here's what I use to determine if the Office becomes unoccupied. A lot of these will look familiar.

  - alias: Office unoccupied
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id: binary_sensor.office_unoccupied
        to: 'on'
        for:
          minutes: 3
    action:
      - service: input_boolean.turn_off
        data:
          entity_id: input_boolean.office_occupied

The Trigger

  1. Once the Binary Sensor sees that all monitored entities are in their correct states for three minutes, the automation begins.

The Action

  1. Once all conditions have been met, the Office input boolean will be switched off. This in turn will affect whether or not the Living Room will remain occupied.

The Bedroom

Finally the final room, the Bedroom. This will be very similar in practice to the Office so you can probably gloss over if you feel like you get the gist of this.

  - alias: Bedroom occupied
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id: group.bedroom_motion
        to: 'on'
      - platform: state
        entity_id: input_boolean.sean_bedtime
        to: 'on'
      - platform: state
        entity_id: input_boolean.emily_bedtime
        to: 'on'
      - platform: template
        value_template: "{{ not is_state('media_player.bedroom_tv', 'unavailable','idle') }}" # NOT condition
    condition:
      - condition: state
        entity_id: group.family
        state: 'home'
    action:
      - service: input_boolean.turn_on
        data:
          entity_id: input_boolean.bedroom_occupied

The Actions

  1. Motion trigger
  2. Actions two and three look to see if we are in bed or not to sleep. Upon plugging in our phones to charge at night, a script is triggered that performs various tasks including turning on a sleeping input boolean.

The Condition

  1. Cats.

The Action

  1. Turns on the Bedroom input boolean which also begins the timer to determine if the Living Room is occupied or not.

Here's how the system determines if the Bedroom is unoccupied.

  - alias: Bedroom unoccupied
    initial_state: 'on'
    trigger:
      - platform: state
        entity_id: binary_sensor.bedroom_unoccupied
        to: 'on'
        for:
          minutes: 3
    action:
      - service: input_boolean.turn_off
        data:
          entity_id: input_boolean.bedroom_occupied

The Trigger

  1. Once the Binary Sensor sees that all monitored entities are in their correct states for three minutes, the automation begins.

The Action

  1. If all conditions are met, the Bedroom input boolean is turned off. This can affect whether or not the Living Room is occupied or not.

Usage in Other Automations

And that's really about it. Once you have your Master Room determined, all you really have to do is figure out what kind of parameters you want for each of the other rooms to determine their occupancy. You can use a wide variety of devices to accomplish this, but I have tried to keep this as simple as possible for our needs.

I'm still constantly tweaking as time goes but as of today it has been 100% accurate with the both of us at home. The Occupied Input Booleans are used in various automations such as the one I use to turn off the lights in our Living Room.

The system checks every five minutes to see if the Living Room is occupied or not and goes from there. While I only use this for lighting, you can get more creative with your automations such as turning off devices, changing the thermostat, or whatever.

- alias: Living Room Lights off
  initial_state: 'on'
  trigger:
    platform: time_pattern
    minutes: '/5'
  condition:
    condition: and
    conditions:
      - condition: state
        entity_id: input_boolean.living_room_occupied
        state: 'off'
      - condition: template
        value_template: "{{ not is_state('media_player.plex_living_room_tv', 'paused') }}" # NOT condition
  action:
    - service: light.turn_off
      data:
        entity_id: light.living_room
        transition: 2