fafcf116be
- Presence Scene Controller: per-room presence-aware time-of-day scenes with vacant/sleep scenes and Motion Light coexistence - Time Of Day Selector: event-driven state machine that sets an input_select when a configured time entity fires - List both blueprints in the root README - Bump version to 2.13.0
159 lines
10 KiB
Markdown
159 lines
10 KiB
Markdown
# Presence Scene Controller Blueprint
|
||
|
||
A per-room, presence-aware time-of-day scene controller. Maps scenes to time-of-day options by index — like the [Day Scene Controller](../Day%20Scene%20Controller/README.md) — but adds presence gating, vacant/sleep scenes, and explicit coexistence with the [Motion Light](../Motion%20Light/README.md) blueprint.
|
||
|
||
Designed to take over the **per-room** responsibilities that have outgrown a single house-wide scene per time slot. Day Scene Controller can be kept alongside this blueprint for genuinely house-wide scenes (Away, Goodnight, All-Off).
|
||
|
||
## Why this exists
|
||
|
||
`Day Scene Controller` triggers one global scene per time-of-day slot. As a home grows, those scenes balloon to dozens of entities and start fighting the user — empty bedrooms light up at sunset, scenes overwrite whatever is happening in the room you're actually in, and a single light requires editing four monolithic scenes.
|
||
|
||
This blueprint splits the problem per room. Each room gets its own small scenes (3–6 entities), its own presence sensors, its own vacancy behavior, and a clean handoff with motion-driven automations.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
Time of Day Selector (input_select, 1 instance)
|
||
│
|
||
├──► Day Scene Controller (house-wide, kept for Away/Goodnight)
|
||
└──► Presence Scene Controller × N (one instance per room)
|
||
```
|
||
|
||
Per room the user provides:
|
||
- A small set of scenes (Morning/Day/Evening/Night, plus a Vacant scene).
|
||
- Optional presence sensors.
|
||
- Optional sleep-mode switch + sleep scene.
|
||
- Optional Motion Light state entity for coexistence.
|
||
|
||
## Migration path
|
||
|
||
The blueprint is built so you can adopt it gradually:
|
||
|
||
1. **Day 1.** Instantiate the blueprint per room with **no presence sensors** and small per-room scenes. With no sensors the room is treated as always-occupied and behaves as a per-room TOD mapper — you immediately gain smaller scenes and per-room control without buying any new hardware.
|
||
2. **Day N.** Add presence sensors to one room (start with the bedroom — biggest UX win). Append the sensor entity to the input list. Vacancy gating, the sleep-person fix, and TOD-defer all activate for that room. No other config changes.
|
||
3. Repeat per room as you wire more sensors. Day Scene Controller can stay around for the house-wide scenes that still make sense, or be retired completely.
|
||
|
||
## How it works
|
||
|
||
### Triggers
|
||
|
||
The automation re-evaluates on any of:
|
||
|
||
- Time-of-day state change.
|
||
- Any presence sensor reporting `on`.
|
||
- All presence sensors reporting `off` for the configured timeout.
|
||
- Room enable switch toggled.
|
||
- Sleep mode switch toggled.
|
||
- Motion Light state entity changed (used as an abort signal).
|
||
|
||
### Decision flow
|
||
|
||
On every run, in order:
|
||
|
||
1. **Yield to Motion Light.** If the configured Motion Light state entity reports the same room is in `ENABLING`/`ENABLED`/`MANUAL`, stop immediately.
|
||
2. **Motion-Light-state-change abort.** If this run was triggered by a Motion Light state change and Motion Light is now released, stop without re-applying anything (avoids fighting a freshly-disabled Motion Light).
|
||
3. **Validate TOD.** Stop if the time-of-day state is not in the options list, or if the scenes list is shorter than the options.
|
||
4. **Apply TOD-while-occupied policy** (only when the trigger was a TOD flip and the room is currently occupied):
|
||
- `defer` — stop, wait for the next vacancy cycle.
|
||
- `apply_if_lights_off` — stop unless every light/switch in the target scene is currently off.
|
||
- `apply` — continue.
|
||
5. **Skip if same scene.** If a `last_applied_state_entity` is configured and the target scene matches what was last applied, stop.
|
||
6. **Apply the target scene.**
|
||
7. **Persist last-applied scene** (if a state entity is configured).
|
||
8. **Post-apply re-check.** After 300 ms re-read Motion Light state; if Motion Light has since claimed the room, stop without running callbacks.
|
||
9. **Run callback** — `vacant_scene_applied_callback` or `scene_applied_callback` depending on which scene was applied.
|
||
|
||
### Target-scene resolution
|
||
|
||
Computed at action time (not trigger time), so a TOD flip that fires while a vacancy timeout is still counting down resolves to the new TOD's scene at apply time:
|
||
|
||
| Condition | Target scene |
|
||
|---|---|
|
||
| Sleep mode switch ON (and sleep scene configured) | sleep_scene |
|
||
| Room enable switch OFF | vacant_scene |
|
||
| Occupied and TOD index valid | scenes[tod_index] |
|
||
| Vacant | vacant_scene |
|
||
|
||
### "TOD change while occupied" — the headline rule
|
||
|
||
Three modes for the toggle, default `apply_if_lights_off`:
|
||
|
||
| Mode | What it does | Best for |
|
||
|---|---|---|
|
||
| `apply_if_lights_off` | Re-apply only if every light/switch in the target scene is currently off | **Default** — handles "sleeping at 06:00 in a dark room" and "kitchen is dark, refresh it" with a single rule. Doesn't fight a user who is already reading with the lamp on. |
|
||
| `defer` | Never re-apply on TOD flip; wait for the next vacancy cycle | Bedrooms with mmWave that always reports occupancy. |
|
||
| `apply` | Always re-apply on TOD flip | Kitchen, hallway, transit areas. |
|
||
|
||
The deferred TOD scene is delivered automatically the next time the room transitions vacant→occupied — when you walk back in, you see the new TOD's scene.
|
||
|
||
### Vacancy
|
||
|
||
When all presence sensors have been off for `presence_off_timeout`, the `vacant_scene` is applied. Typical choices:
|
||
|
||
- An "All Off" scene for hallways and bathrooms.
|
||
- A dim night-light scene for the bedroom.
|
||
- An empty-room scene that turns off only the discretionary lights.
|
||
|
||
If no presence sensors are configured, vacancy is never reached and `vacant_scene` is effectively unused (still required as input, but ignored).
|
||
|
||
### Sleep mode
|
||
|
||
When `sleep_mode_switch` is ON (and `sleep_scene` is configured), every run resolves to `sleep_scene` regardless of TOD or presence. Use it for:
|
||
|
||
- Shift workers sleeping during the day.
|
||
- Naps where TOD says "Afternoon" but you want the room dark.
|
||
- A partner entering the bedroom without lighting it up.
|
||
|
||
The switch can be wired to a dashboard button, an NFC tag, a voice command, an alarm-clock automation that flips it off in the morning, or a sleep tracker.
|
||
|
||
### Motion Light coexistence
|
||
|
||
If both Motion Light and Presence Scene Controller target the same lights, point Presence Scene Controller at Motion Light's state `input_text` plus the matching key (Motion Light's `automation_state_placeholder_key`, or — by default — the room enable switch entity ID).
|
||
|
||
Coexistence is enforced two ways:
|
||
|
||
- **Yield gate.** Every evaluation reads Motion Light's state JSON. If it reports `ENABLING/ENABLED/MANUAL` for this room, we stop.
|
||
- **mode: restart abort.** A change on the Motion Light state entity is one of the triggers, so a transition into ENABLING during a scene application cancels the in-flight run via Home Assistant's restart mode.
|
||
- **Post-apply re-check.** A 300 ms delay after `scene.turn_on` is followed by a fresh read of Motion Light state. If Motion Light claimed the room during the apply, we yield without calling the success callback.
|
||
|
||
This is advisory rather than a hard lock — Home Assistant doesn't expose CAS-style locking — but it eliminates the common race in practice.
|
||
|
||
### Presence semantics
|
||
|
||
- ANY-on logic: the room is occupied if any presence sensor reports on.
|
||
- Sensors reporting `unavailable` or `unknown` are treated as **occupied** — safer than the alternative (briefly plunging a real-occupied room into the vacant scene during a Zigbee hiccup).
|
||
- `presence_off_timeout` debounces motion drop-outs and short absences.
|
||
|
||
## Configuration
|
||
|
||
| Group | Input | Notes |
|
||
|-------|-------|-------|
|
||
| **States** | Time of Day State Selector | Required. The shared `input_select`. |
|
||
| | Room Enable Switch | Optional kill switch. OFF → apply vacant scene. |
|
||
| **Presence** | Presence Sensors | Optional list. Empty = always occupied. |
|
||
| | Presence Off Timeout | Seconds (default 120). |
|
||
| **Scenes** | Time-of-Day Scenes | Required ordered list, one per TOD option. |
|
||
| | Vacant Scene | Required. Applied on vacancy / room disabled. |
|
||
| | Sleep Scene | Optional. Required for sleep mode to do anything. |
|
||
| **Behavior** | TOD Change Behavior While Occupied | `apply_if_lights_off` (default) / `defer` / `apply`. |
|
||
| | Sleep Mode Switch | Optional. |
|
||
| **Motion Light Coexistence** | Motion Light State Entity | Optional `input_text` from a Motion Light instance. |
|
||
| | Motion Light State Key | Optional override of the JSON key. |
|
||
| **Persistence** | Last Applied Scene Entity | Optional `input_text` for skip-if-same-scene. |
|
||
| **Actions** | Scene Applied Callback | Runs after a TOD/sleep scene is applied. |
|
||
| | Vacant Scene Applied Callback | Runs after the vacant scene is applied. |
|
||
| **Debug** | Enable Debug Notifications | Posts a persistent notification per evaluation. |
|
||
|
||
## Behavior notes & known limitations
|
||
|
||
- **HA restart while occupied.** After a restart, the first vacancy cycle applies the *current* TOD's scene to the room. If TOD already advanced past several boundaries during the restart, intermediate scenes are not retroactively applied.
|
||
- **Identical vacant scene and TOD scene.** If you accidentally set them to the same entity, the skip-if-same-scene guard suppresses redundant work — but only if you've configured a `last_applied_state_entity`.
|
||
- **Motion Light coexistence is advisory.** A worst-case race between a scene apply and Motion Light's enable path can still produce a brief flicker before the post-apply re-check yields. If you need stricter mutual exclusion, wire a per-room "scene controller idle" `input_boolean` into Motion Light's `condition_switches`.
|
||
- **Settled occupancy isn't modeled.** A user who rolls out of bed but stays in the room never triggers a vacancy cycle, so the deferred TOD scene won't apply until they leave and re-enter. Acceptable in practice; the first bathroom trip resets it.
|
||
- **One blueprint instance per room.** If you need separate behavior for two distinct light sets in the same room (e.g., bedroom main lights vs. bedroom fan + reading lamp), instantiate the blueprint twice with non-overlapping scenes.
|
||
- **Optional state-trigger entities.** Triggers reference `room_enable_switch`, `sleep_mode_switch`, and `motion_light_state_entity`. Leaving them empty is supported by modern Home Assistant; on older versions you may see a non-fatal warning in the log. The automation continues to work either way.
|
||
|
||
## Author
|
||
|
||
Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
|