Skip to content

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_home in 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_home is 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:

  1. Circadian ambiance — a native-scene day arc (Sunrise → … → Night) on weekends and weekday off-hours.
  2. 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.
  3. 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.)
  4. 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/scenes104 named scenes for the H607C.
  • POST /router/api/v1/device/control with {"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 overrideweather.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_manual on. 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_house always 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_pct to be a real value and sensor.sofi_quote to 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 the corner_lamp_gradient helper (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_mode stays (it is an all-off scene, not a lamp scene), and corner_lamp_focus/_rainbow are deleted as scripts but were never exposed. CLAUDE.md Alexa index drops 20 → 12. Verify the exact set against live .storage/homeassistant.exposed_entities at implementation time.
  • Replace script.wake_house's current raw "turn lamp on to last state" block with "clear corner_lamp_manual, then call script.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 the rest_command scene 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; add input_boolean.corner_lamp_manual to Key Entities; update the Govee integration note; fix the scripts.yaml header 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.