Making a Scene: Bathroom Occupancy Indicator in Home Assistant
Posted on November 24, 2025 • 5 minutes • 959 words
For the last few months, my bathroom lighting automation has been rock solid. It is a simple setup: motion is detected, the lights turn on. Motion stops, the lights turn off. It works perfectly, and I haven’t had to touch a light switch in ages.
But recently, I realized I was missing a piece of the puzzle.
I found that I would walk all the way there only to find the door closed with the lights on. I needed a way to know if the room was occupied before getting up.
Naturally, I decided to hijack one of the living room ceiling lights to act as an occupancy indicator.
The Goal
The logic seemed simple enough on paper:
- When the bathroom is occupied (trigger), turn the living room notify light Red.
- When the bathroom is vacant, turn the living room light back to… well, whatever it was before.
And that is where the trouble started.
The “Restore” Problem
The first step was basic. I adjusted my existing automation to change my chosen indicator light red at the same time as the bathroom light turns on.
The next step, however, gave me pause: what do I do once the bathroom light turns off? I could hard code the light to my preferred shade of white, but what if someone happened to have changed the lights to a different shade? Or had dimmed them? Or had turned them off entirely?
I considered attempting to copy the state of another light in the room, but then I would have to account for numerous scenarios as setting the state of the light is different if it’s white, coloured, or even off.
I needed a time machine. I needed a way to snapshot the light’s state exactly as it was, change it for the notification, and then restore it later.
Enter: The Dynamic Scene
Home Assistant has an action called scene.create. It takes a snapshot of an entity’s current attributes (color, brightness, or off-state).
- action: scene.create
data:
scene_id: before_bathroom_notification
snapshot_entities:
- light.living_room_notify_light
When you’re ready to return the light to the previous state, you can then apply the saved snapshot.
- action: scene.turn_on
target:
entity_id: scene.before_bathroom_notification
This solved the restoration problem. Whether the light was Blue, Dim White, or Off, the scene captured it.
The “Double-Dip” Loop
However, motion sensors are chatty. They trigger, then clear, then trigger again while you are still in the room.
I ran into an issue where the automation would restart while the light was already Red. The snapshot action would run again, take a picture of the Red light, and save that as the “restore point.”
When I left the bathroom, the automation would dutifully restore the light to… Red.
To fix this, I had to wrap the snapshot in a condition. We only want to take a picture if one doesn’t already exist.
- if:
- condition: template
value_template: "{{ states('scene.before_bathroom_notification') == 'unknown' }}"
then:
- action: scene.create
data:
scene_id: before_bathroom_notification
snapshot_entities:
- light.living_room_notify_light
Cleaning Up (Hygiene Matters)
For the logic above to work, we have to delete the scene when we are done.
If we don’t delete it, the next time the bathroom is used (say, 4 hours later), the system will see the old scene still floating in memory. It will skip the new snapshot and eventually restore the lighting state from 4 hours ago.
We use scene.delete to ensure the slate is wiped clean for the next run.
The Final Solution
Here is the complete automation, tweaked a little from my real implementation for the sake of demonstration. It handles the snapshot, the notification, the re-trigger protection, and the cleanup. I also added a timeout safety net: if the sensor gets stuck, the light resets after an hour so the living room doesn’t stay Red forever.
mode: restart
triggers:
- entity_id:
- binary_sensor.bathroom_motion
from: "off"
to: "on"
trigger: state
conditions: []
actions:
- alias: Turn on the bathroom lights
action: light.turn_on
data:
kelvin: 3000
brightness_pct: "{{ (states('input_number.nightlightbrightness') | float) + 10 }}"
target:
area_id: bathroom
- alias: Capture scene if not already captured
if:
- condition: template
value_template: "{{ states('scene.before_bathroom_notification') == 'unknown' }}"
alias: Check for existing scene
then:
- alias: Capture current state of Living Room Notify
action: scene.create
data:
scene_id: before_bathroom_notification
snapshot_entities:
- light.living_room_notify_light
- alias: Set Living Room Notify Light to Red
action: light.turn_on
metadata: {}
data:
rgb_color:
- 255
- 0
- 0
brightness_pct: "{{ (states('input_number.nightlightbrightness') | float) + 10 }}"
target:
entity_id: light.living_room_notify_light
- alias: Wait for motion to no longer be detected
wait_for_trigger:
- entity_id:
- binary_sensor.bathroom_motion
from: "on"
to: "off"
trigger: state
timeout:
hours: 1
minutes: 0
seconds: 0
milliseconds: 0
continue_on_timeout: true
- alias: Wait for 30 seconds of no motion
delay:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
- alias: Turn off the bathroom lights
data: {}
action: light.turn_off
target:
area_id: bathroom
- alias: Restore state of Living Room Notify Light
action: scene.turn_on
target:
entity_id: scene.before_bathroom_notification
- alias: Cleanup temporary light state
action: scene.delete
target:
entity_id: scene.before_bathroom_notification
One thing you’ll note here is that I’m setting the notify light to the same brightness as the bathroom light. This uses a helper that adjusts the target brightness based on the time of day and it is shared across a number of my Home Assistant automations. It’s very helpful in preventing flash banging yourself at 3am.
Wrap Up
It took a few iterations to get from a basic red light to a robust notification system, but the result is seamless. The living room vibe is preserved, and I no longer have to walk down the hall just to find a locked door.
Now, if you’ll excuse me, I have some other automations to shed some light on.