Corner Lamp Priority Renderer — Design¶
Status: design approved 2026-07-10; built and tuned live — see "As-built" note below. Fixture: Govee RGBIC Corner Lamp (H607C, 16 addressable segments), Louis's office. Related:
projects/govee_floor_lamp.md(integration),projects/smart_lighting_scenes.md(lighting backlog), automations.yaml §19 (Sengled SOFI ticker — see Open Items).
As-built (tuned during implementation, #74)¶
This design was implemented largely as drafted, but several scene choices in §4/§6 were tuned live against the physical lamp after preview-firing and superseded the original draft below:
- Alert scenes renamed. The provisional Strike (alarm) / Thunderstorm (leak) picks in §4 (flagged "provisional, must preview-fire" in §13) didn't read as distinct alerts on the physical lamp. As-built: Breathe (9690/16565) for the alarm, Dripping (9695/16570) for the leak — both fired with
force: true. - New §4b — severe weather, not in the original draft:
weather.forecast_homein a severe condition (lightning / lightning-rainy / pouring / hail / exceptional) now shows Thunderstorm (9705/16580) — the name freed up by renaming the leak alert off it. This branch sits below away/asleep in priority (so it never lights an empty or sleeping room) but above the ticker (so it punches through even during market hours). - Circadian arc reshaped. The original 6-block Sunrise/Morning/Afternoon/Sunset/Starry Sky/Candlelight arc (§6) is retired in favor of a 5-block Morning/Afternoon/Twilight/ Night/Starry Sky arc, plus a mild-weather override: rainy/snowy(-rainy)/fog swap in a weather-themed scene (Raining/Snowing/Dense Fog) in place of the time-of-day pick. Severe weather is handled entirely by §4b, not here.
weather.forecast_homeis now one of the render triggers (§7).
§4 and §6 below are updated inline to the as-built tables; the rest of the document (§1–3, §5, §7–13) is left as originally drafted for historical context except where noted.
1. Goal¶
Turn the Corner Lamp from a bag of manually-invoked scene scripts into a single priority renderer — the same choose:-stack pattern as script.night_lights_render, script.string_lights_render, and script.master_bedroom_sitting_lamp_render — that composes three behaviors and yields cleanly to manual control:
- Circadian ambiance — a native-scene day arc (Sunrise → … → Night) on weekends and weekday off-hours.
- SOFI stock ticker — during market hours, a scene chosen by SOFI's % change: greens for up, reds for down, blue when flat, intensity encoding the size of the move.
- Alert modes — dramatic, high-motion, non-red scenes for alarm and leak (red is reserved for "stock down", so alerts must not collide with it). (As-built: Breathe/ Dripping — see "As-built" note above.)
- Severe-weather override (added post-draft, see "As-built" above) — a Thunderstorm scene that punches through the ticker on lightning/hail/pouring/exceptional conditions.
Everything the lamp shows is an animated Govee scene (never a solid color) — the on-device motion is the whole point. The only non-scene states are off (away / asleep) and yield (manual override).
2. Key enabling discovery (2026-07-10)¶
Native animated scenes are reachable on the Govee Platform API key alone — no account login required. This corrects the prior belief (recorded in projects/govee_floor_lamp.md) that native scenes were blocked by govee2mqtt issue #649. That issue only blocks real-time push and tap-to-run scenes; the documented dynamic_scene / lightScene capability works with just the API key:
POST /router/api/v1/device/scenes→ 104 named scenes for the H607C.POST /router/api/v1/device/controlwith{"id": <id>, "paramId": <paramId>}→code: 200, msg: "success", one call, no 429s (verified by firing 5 scenes 10 s apart).
This is why the renderer can fire a scene in one cloud call instead of the ~32-call, 30-second throttled segment paint the old corner_lamp_gradient scripts needed.
3. Architecture¶
One renderer script owns the lamp; every governing signal calls it. Scene control goes direct to the Govee Platform API via rest_command (bypassing govee2mqtt, which stays responsible for on/off power and segment state). The two control paths do not conflict — a scene is not a state govee2mqtt models, and the renderer tracks its own last-render token.
Components¶
| Component | Location | Role |
|---|---|---|
govee_api_key, govee_corner_lamp_device | secrets.yaml (box; gitignored) + stubs in tests/fixtures/ci_secrets.yaml | Credentials / device id for the REST calls |
rest_command.govee_corner_lamp_scene (scene_id, param_id) | configuration.yaml | The only device-control call — sets an animated scene |
input_boolean.corner_lamp_manual | configuration.yaml | Single source of truth; renderer yields when on |
input_text.corner_lamp_last_render | configuration.yaml | Dedupe token — skip re-sending an unchanged scene |
script.corner_lamp_render | scripts.yaml | The priority choose: stack |
automation.corner_lamp_render_triggers | automations.yaml | Re-render on every governing signal |
automation.corner_lamp_manual_webhook_on / _off | automations.yaml | Two webhooks flip the manual flag |
script.wake_house (edit) | scripts.yaml | Morning reset: clear corner_lamp_manual, then render |
Power is set via govee2mqtt's existing switch.corner_lamp_power_switch (one call). There is no solid-color rest_command — by design, everything visible is a scene.
4. Priority stack (script.corner_lamp_render) — AS-BUILT¶
Top match wins (highest priority first). Branches 1, 2, 4b, 5, 6 turn the power switch on before sending a scene; branches 3, 4 turn it off.
| # | Condition | Result |
|---|---|---|
| 0 | input_boolean.corner_lamp_manual is on | yield (stop) — user owns the lamp via the Govee app; stamps last-render token "manual" |
| 1 | alarm_control_panel.blairmont_alarm is triggered | Scene Breathe (9690/16565), force: true |
| 2 | binary_sensor.any_leak_wet is on | Scene Dripping (9695/16570), force: true |
| 3 | zone.home count == 0 (away) | off |
| 4 | input_boolean.sleeping is on | off |
| 4b | weather.forecast_home state in lightning, lightning-rainy, pouring, hail, exceptional (severe weather) | Scene Thunderstorm (9705/16580) — shown even during market hours (punches through the ticker); sits below away/asleep so it never lights an empty/sleeping room |
| 5 | Market hours (Mon–Fri 09:30–16:00 ET) and SOFI data fresh | ticker scene (§5) |
| 6 | default (awake, home, off-hours, weekends, and no mild-weather override) | weather-aware circadian scene (§6) |
Alarm and leak deliberately outrank away — an intrusion or leak while nobody is home is exactly when the beacon matters.
As-built (superseded the provisional picks below): the original Strike (alarm) / Thunderstorm (leak) picks were preview-fired and didn't read as distinct alerts on the physical lamp. Final: Breathe (alarm) / Dripping (leak) — see the "As-built" note at the top of this document. Thunderstorm was repurposed as the new §4b severe-weather scene rather than dropped. The original provisional-scene text is preserved below for historical context:
Alert scenes are provisional. Strike and Thunderstorm are best reads from the catalog names; they MUST be preview-fired on the physical lamp before locking. Backups if underwhelming: Fireworks (9665/16540), Dance Party (15766/25596).
5. SOFI ticker scene-map (branch 5)¶
sensor.sofi_change_pct magnitude picks the scene; intensity encodes the move size.
| Move | Scene | id / paramId |
|---|---|---|
| down > 4% | Lava | 9645 / 16520 |
| down 2–4% | Fire | 9647 / 16522 |
| down 0.5–2% | Sunset Glow | 9649 / 16524 |
| flat < 0.5% | Ocean (neutral blue) | 9635 / 16510 |
| up 0.5–2% | Rustling leaves | 9656 / 16531 |
| up 2–4% | Forest | 9634 / 16509 |
| up > 4% | Green Reign | 29882 / 41597 |
Thresholds (0.5 / 2 / 4 %) are tunable after watching real moves. A within-tier rotation for extra variety is a possible later enhancement (not v1).
6. Weather-aware circadian arc (branch 6) — AS-BUILT¶
As-built (superseded the 6-block arc below): the original Sunrise/Morning/Afternoon/ Sunset/Starry Sky/Candlelight arc was reshaped during tuning into a 5-block arc, and a mild-weather override was added — neither existed in the original draft. See the "As-built" note at the top of this document.
Native scenes provide a ready-made day arc. This shows on weekends and weekday pre-open / post-close (market hours are covered by the ticker), except when severe weather (§4b) or mild weather (below) overrides it.
Time-of-day block (ET):
| Block (ET) | Scene | id / paramId |
|---|---|---|
| 05–12 | Morning | 9668 / 16543 |
| 12–17 | Afternoon | 9669 / 16544 |
| 17–20 | Twilight | 9670 / 16545 |
| 20–23 | Night | 9709 / 16584 |
| 23–05 | Starry Sky | 9707 / 16582 |
Mild-weather override — weather.forecast_home in one of these states replaces the time-of-day pick above (severe weather is handled by §4b, not here):
| Weather state | Scene | id / paramId |
|---|---|---|
rainy | Raining | 9706 / 16581 |
snowy, snowy-rainy | Snowing | 9636 / 16511 |
fog | Dense Fog | 9651 / 16526 |
Original draft (superseded — kept for history)
| Block (ET) | Scene | id / paramId | |---|---|---| | 05–08 | **Sunrise** | 9558 / 16433 | | 08–12 | **Morning** | 9668 / 16543 | | 12–17 | **Afternoon** | 9669 / 16544 | | 17–20 | **Sunset** | 9631 / 16506 | | 20–23 | **Starry Sky** | 9707 / 16582 | | 23–05 | **Candlelight** (dim; rare — usually asleep/away → off) | 9666 / 16541 |7. Triggers (automation.corner_lamp_render_triggers)¶
Re-render on: sun.sun above/below horizon; a time trigger at each circadian block boundary; time_pattern every 5 min (market ticker refresh); sensor.sofi_change_pct state; alarm_control_panel.blairmont_alarm state; binary_sensor.any_leak_wet state; input_boolean.sleeping; zone.home; input_boolean.corner_lamp_manual; weather.forecast_home state (as-built addition — needed for §4b severe-weather and the §6 mild-weather override, neither of which existed in the original draft); and homeassistant start.
8. Manual override & reset¶
- Enter manual: dashboard toggle or
POST /api/webhook/<corner_lamp_manual_id>→input_boolean.corner_lamp_manualon. Renderer yields; user free-paints in the Govee app. - Exit manual: dashboard toggle or
POST /api/webhook/<corner_lamp_auto_id>or the morning wake (script.wake_house) → flag off, renderer resumes. - Guaranteed reset:
script.wake_housealways clears the flag, so the lamp cannot be stuck in manual past the next morning wake. - The off/on-power-cycle re-enable idea is dropped — it only existed to work around the polling lag and is unnecessary given explicit HA-side disable + the morning reset.
- Webhook ids must be long/random (the URL is the only credential).
9. Error handling & robustness¶
- Stale/missing SOFI data (guards against the #47 failure where a frozen quote pinned a fake move): the ticker branch requires
sensor.sofi_change_pctto be a real value andsensor.sofi_quoteto have updated within the last 20 min. Otherwise the lamp shows Ocean (neutral) and logs it — never a stale green/red. - Dropped REST call: alert branches (1, 2) re-send on every trigger and every 5-min tick (no dedupe), self-healing within 5 min. Ticker/circadian dedupe via
input_text.corner_lamp_last_render(skip the call if the scene token is unchanged) to avoid restarting the animation every 5 min; a rare dropped update lags one tier until the next change, still showing a valid scene. - Power state: active branches flip the power switch on before sending, so a scene is never sent to a lamp that is off.
10. Teardown¶
- Delete the 10 corner-lamp scripts:
corner_lamp_cozy,_sunset,_fireplace,_ocean,_aurora,_cyberpunk,_lavender,_focus,_rainbow, and thecorner_lamp_gradienthelper (no remaining caller — native scenes replace them, manual looks are picked in the Govee app). - Remove the Alexa-exposed corner-lamp scenes from the allowlist (
.storage/homeassistant.exposed_entities) →cloud/alexa/sync→ re-discover. Per current CLAUDE.md that is 8 scripts (corner_lamp_cozy,gradient,sunset,fireplace,ocean,aurora,cyberpunk,lavender);movie_modestays (it is an all-off scene, not a lamp scene), andcorner_lamp_focus/_rainboware deleted as scripts but were never exposed. CLAUDE.md Alexa index drops 20 → 12. Verify the exact set against live.storage/homeassistant.exposed_entitiesat implementation time. - Replace
script.wake_house's current raw "turn lamp on to last state" block with "clearcorner_lamp_manual, then callscript.corner_lamp_render".
11. Testing & deploy¶
Pre-deploy, manual, in order (bites if skipped): 1. Add govee_api_key + govee_corner_lamp_device to /config/secrets.yaml on the box over SSH before pushing — else the post-restart health check fails on the missing !secret (this is the config's first !secret use). 2. Add matching stub values to tests/fixtures/ci_secrets.yaml and document them in secrets.yaml.template, so check_config (Tier 1) resolves the !secret references.
Suite: - The two new helpers are referenced before they are registered → add to tests/fixtures/entities_allow.txt with a justifying comment. After deploy, make snapshot moves them into entities.txt and drops them from the allowlist. - New automations get unique ids/aliases (Tier 2). - Deleting the 10 scripts is safe once their Alexa/dashboard refs are gone — nothing references them, so no entity-ref (Tier 3) test breaks. (Their now-stale rows in entities.txt are harmless; make snapshot prunes them.)
Deploy: configuration.yaml + scripts.yaml + automations.yaml all change → CI does a full ha core restart.
12. Documentation (same change)¶
projects/govee_floor_lamp.md— correct the false "native scenes impossible / blocked by #649" claim; document the renderer and therest_commandscene path.projects/smart_lighting_scenes.md— rewrite the Corner Lamp section (now a built renderer, not a bag of gradient scripts).CLAUDE.md— new automation-section entry; Alexa index 20 → 12; addinput_boolean.corner_lamp_manualto Key Entities; update the Govee integration note; fix thescripts.yamlheader comment claiming scenes are blocked.docs/lighting.md+docs/automations.md— user-facing behavior + how to take manual control (toggle/webhook + Govee app), and the technical reference.
13. Open items / future¶
- ~~Alert-scene preview: fire Strike and Thunderstorm on the lamp and confirm they read as alerts (and are distinct from each other and from the ticker reds) before locking.~~ Done, as-built differently — see the "As-built" note at the top: the preview fire led to Breathe/Dripping instead, and Thunderstorm was repurposed as the §4b severe-weather scene.
- Sengled SOFI ticker (§19): left running for now; it duplicates the lamp's ticker in the office. Tracked separately for future repurposing (a circadian role on a sitting-room lamp) — see the follow-up ticket.
- Within-tier ticker variety: optional later rotation among a color bucket for visual variety beyond the magnitude tiers.
- Away/presence nuance: v1 uses
zone.home == 0→ off. A finer office-occupancy gate is possible later.