DEV Community

Kevin Lu
Kevin Lu

Posted on

I Built a Bathroom Occupancy Notifier with Home Assistant

As a backend dev who runs his own network, NAS, and home lab, I kept getting interrupted mid-game and during meetings to check if the bathroom was free. Walking over to check is the whole problem, especially during moments when I can't afford to take time to step away. So I built a notifier in Home Assistant.

Now, anyone in the house opens the Home Assistant app, sees the live occupancy status, and taps one button to get pinged the moment the bathroom is free. Whoever is inside also gets alerted when someone is waiting, which was the original motivation.

Bathroom available screen

What You Need

  • A door contact sensor paired to Home Assistant (Zigbee, Z-Wave, or WiFi, anything that exposes a binary_sensor)
  • input_boolean.guest_bathroom_queue created in Settings β†’ Device & Services β†’ Helpers β†’ Toggle
  • Home Assistant Companion app installed on family devices for push notifications
  • A separate guest VLAN if you want the auto-login flow (optional)

How It Works

A door contact sensor (binary_sensor.contact_sensor_bathroom_door_intrusion) drives everything. "off" = door closed = occupied. "on" = door open = available.

Note: This can vary depending on the sensor model. Please verify and adjust the provided YAML accordingly.

One input_boolean.guest_bathroom_queue tracks whether anyone is waiting. I deliberately kept this as a shared boolean rather than tracking individual users. It doesn't matter who is waiting, making state management much simpler.

Three automations handle the core logic.

1. Someone joins the queue

Triggers 3 seconds after guest_bathroom_queue flips on. The delay filters out accidental taps.

alias: Notify users when guest is waiting for bathroom
triggers:
  - trigger: state
    entity_id: input_boolean.guest_bathroom_queue
    from: "off"
    to: "on"
    for:
      seconds: 3
actions:
  - action: notify.family_devices
    data:
      title: Bathroom
      message: someone is waiting for the bathroom
mode: single
Enter fullscreen mode Exit fullscreen mode

2. Bathroom becomes free while someone is waiting

Fires when the door opens and guest_bathroom_queue is still on. Turns off the queue, then notifies.

alias: Notify Guest When Bathroom Free
triggers:
  - trigger: state
    entity_id: binary_sensor.contact_sensor_bathroom_door_intrusion
    from: "off"
    to: "on"
conditions:
  - condition: state
    entity_id: input_boolean.guest_bathroom_queue
    state: "on"
actions:
  - action: input_boolean.turn_off
    target:
      entity_id: input_boolean.guest_bathroom_queue
  - action: notify.family_devices
    data:
      title: Bathroom
      message: Bathroom is free 🚻
mode: single
Enter fullscreen mode Exit fullscreen mode

3. Someone cancels their request

Triggers when guest_bathroom_queue flips back off while the door is still closed, meaning they cancelled manually, not because the bathroom freed up.

alias: Cancel Bathroom Request
triggers:
  - trigger: state
    entity_id: input_boolean.guest_bathroom_queue
    from: "on"
    to: "off"
conditions:
  - condition: state
    entity_id: binary_sensor.contact_sensor_bathroom_door_intrusion
    state: "off"
actions:
  - action: notify.family_devices
    data:
      title: request cancelled
      message: user canceled their bathroom request
mode: single
Enter fullscreen mode Exit fullscreen mode

The Dashboard Card

The card has two states driven by binary_sensor.contact_sensor_bathroom_door_intrusion.

Door open β†’ green icons, shows time since it opened.

Door closed β†’ door icons, shows time since it closed, plus a queue button.

I used conditional cards to handle the two states rather than a custom component. It's more YAML, but everything stays in the dashboard config.

The button toggles input_boolean.guest_bathroom_queue. When the queue is active, the icon turns orange, indicating someone is waiting. Tapping it again cancels the request.

Bathroom occupied without queue

Bathroom occupied in queue

Note: The card_mod blocks are optional. It just handles button sizing and color. If you don't have the card-mod HACS frontend integration installed, remove those blocks, and everything still works.

card:
  type: vertical-stack
  cards:
    - type: conditional
      conditions:
        - condition: state
          entity: binary_sensor.contact_sensor_bathroom_door_intrusion
          state: "on"
      card:
        type: tile
        entity: binary_sensor.contact_sensor_bathroom_door_intrusion
        name: βœ… Bathroom is Available
        icon: mdi:door-open
        color: green
        hide_state: false
        state_content:
          - last_changed
        vertical: true
        tap_action:
          action: none
        icon_tap_action:
          action: none
        features_position: bottom
    - type: conditional
      conditions:
        - condition: state
          entity: binary_sensor.contact_sensor_bathroom_door_intrusion
          state: "off"
      card:
        type: vertical-stack
        cards:
          - type: tile
            entity: binary_sensor.contact_sensor_bathroom_door_intrusion
            name: πŸšͺ Bathroom is Occupied
            icon: mdi:door-closed
            color: red
            vertical: true
            hide_state: false
            tap_action:
              action: none
            icon_tap_action:
              action: none
            state_content:
              - last_changed
          - type: conditional
            conditions:
              - condition: state
                entity: input_boolean.guest_bathroom_queue
                state: "off"
            card:
              type: button
              entity: input_boolean.guest_bathroom_queue
              icon: mdi:account-outline
              name: Wait for Bathroom
              tap_action:
                action: toggle
              card_mod:
                style: |
                  ha-card {
                    margin: 0 auto;
                    width: 200px;
                    text-align: center;
                  }
          - type: conditional
            conditions:
              - condition: state
                entity: input_boolean.guest_bathroom_queue
                state: "on"
            card:
              type: vertical-stack
              cards:
                - type: button
                  entity: input_boolean.guest_bathroom_queue
                  icon: mdi:human-male
                  name: Waiting for Bathroom
                  tap_action:
                    action: toggle
                  card_mod:
                    style: |
                      ha-card {
                        margin: 0 auto;
                        width: 200px;
                        text-align: center;
                        color: orange;
                      }
                - type: markdown
                  content: >
                    <div style="text-align:center; font-size:16px; color:
                    orange;">
                      πŸ•’ You're in the queue! We'll notify you when it's your turn.

                    </div>

Enter fullscreen mode Exit fullscreen mode

Notifying All Devices Without Repeating Yourself

You can call individual device services directly in automations:

actions:
  - action: notify.mobile_app_mom_big_tablet
    data:
      message: Bathroom is free 🚻
  - action: notify.mobile_app_kevin_f6
    data:
      message: Bathroom is free 🚻
Enter fullscreen mode Exit fullscreen mode

That works. The problem is every automation that needs to notify everyone repeats the same device list. Add a new phone, edit every automation.

Instead, define a notify group once in configuration.yaml:

notify:
  - platform: group
    name: family_devices
    services:
      - service: mobile_app_device_1
      - service: mobile_app_device_2
      - service: mobile_app_device_3
      - service: mobile_app_device_4
Enter fullscreen mode Exit fullscreen mode

Now every automation calls notify.family_devices. Add or remove a device in one place.

Triggered when someone joins the queue
Bathroom occupied notif

Bathroom occupied push alert

Triggered when the bathroom becomes free
Bathroom free with notif

Bathroom free push alert

Guest Access via Trusted Networks

Guests connect to a dedicated VLAN (192.168.10.0/24). A guest WiFi password alone isn't enough here. Anyone with the password could browse to Home Assistant and see the full dashboard. The VLAN lets me scope exactly what they can access at the network level, and Home Assistant auto-logs them in as a guest user when they're on that subnet, bypassing the password prompt.

In configuration.yaml:

homeassistant:
  auth_providers:
    - type: trusted_networks
      trusted_networks:
        - 192.168.10.0/24
      allow_bypass_login: true
      trusted_users:
        192.168.10.0/24: <your_guest_user_id>
    - type: homeassistant
Enter fullscreen mode Exit fullscreen mode

Navigate to http://<your-ha-ip>:8123/config/users and click the guest user. The ID is shown explicitly in the first row.

Guests who don't have the Home Assistant app get a QR code to the bathroom dashboard. They land on the card, see live status, and can join the queue, but they can't access anything else.

The guest dashboard itself is restricted to two user IDs via the visible field, so the isolation is enforced at the Home Assistant layer.

Top comments (0)