The problem
Light brightness as state
A trick for giving a light “memory” of how it was turned on, by encoding a flag in the brightness value.
The problem
A hallway or passage light usually has two trigger sources:
- A motion sensor (PIR) — the light should come on, but if nobody is staying, it should turn itself off after a fixed delay.
- A physical button (e.g. an RF wall switch) — the light should stay on until someone deliberately turns it off.
The delayed auto-off has to distinguish “the light I switched on 15 minutes
ago, nobody has touched it since” from “the light someone deliberately left on
with the button”. Lights only have on/off plus brightness — so the
brightest, least visible place to stash a flag is the brightness itself.
The 252 / 255 contract
| Trigger | Brightness set | Later behaviour |
|---|---|---|
| PIR / motion | 252 (≈ 99 %) | Auto-turns off after a fixed delay |
| Physical button | 255 (100 %) | Stays on until switched off |
252 and 255 are imperceptibly different to the eye, but the automation can
read the difference back with state_attr(light.x, 'brightness'):
- PIR automation turns the light on at brightness 252, then waits the fixed delay. At the end of the delay it turns the light off only if the brightness is still < 255 — i.e. it is still the light it switched on and nobody has since pressed the button.
- Button automation toggles using the same flag in reverse: a light at 252 counts as “on” for the user, so the button press turns it off and clears the flag. A light at 255 (button-turned-on) or fully off toggles in the normal way, setting 255 when it turns on.
The contract is:
- brightness 252 = “on by motion, may be auto-offed”
- brightness 255 = “on by a human, leave alone”
- anything below = normal dimming, no flag meaning
When editing these automations, keep the threshold intact. The on-automation, the delayed-off, and the matching button automation all depend on the exact same value — change one without the others and the light either never auto-offs or auto-offs while someone is in the room.
Sketch
# PIR: on at 252, then delayed conditional off
trigger:
- trigger: state
entity_id: binary_sensor.passage_pir
from: 'off'
to: 'on'
action:
- action: light.turn_on
target:
entity_id: light.passage_lights
data:
brightness: 252
- delay: "15:00"
- if:
- condition: template
value_template: >-
{{ state_attr('light.passage_lights', 'brightness') is not none
and state_attr('light.passage_lights', 'brightness') < 255 }}
then:
- action: light.turn_off
target:
entity_id: light.passage_lights
Why not a helper?
You could add an input_boolean or template sensor per light to track the
source. That works, but it doubles the entities, adds a second thing to keep in
sync with the light, and fails quietly if the helper and the light drift apart
(a scene or an assistant voice command can change the light without touching
the helper). Storing the flag in the light’s own state means the source of
truth is always the light itself — whatever turns it on, the flag travels with
it.
The same trick generalises: any light attribute (brightness, colour temperature, hue) can carry a small amount of state that other automations read back, as long as you document the contract.
Related
Source Disclaimer
- AI Generated
- Human Generated
- AI Edited
- Human Edited