Corner Lamp Priority Renderer Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make light.corner_lamp (Govee H607C, office) a single priority renderer that composes a circadian native-scene arc, a SOFI stock ticker, and non-red animated alert scenes — all via one Govee Platform API call — with a manual-override escape hatch.
Architecture: One choose:-stack script (script.corner_lamp_render) owns the lamp, mirroring script.string_lights_render. It delegates the send to two primitive scripts (corner_lamp_apply_scene, corner_lamp_off) that dedupe against input_text.corner_lamp_last_render and set power via govee2mqtt's switch.corner_lamp_power_switch. Scenes are fired by a rest_command posting {id, paramId} straight to /router/api/v1/device/control. A trigger automation re-renders on every governing signal; two webhooks + a wake_house edit drive the manual flag.
Tech Stack: Home Assistant YAML (configuration.yaml, scripts.yaml, automations.yaml), rest_command, Govee Platform API, the repo's tiered test suite (make lint / make check-config / make pytest).
Global Constraints¶
- Everything visible is an animated Govee scene — never a solid color. Only non-scene states are
off(away/asleep) and yield (manual). - Clock is ET (
America/New_York); market hours = Mon–Fri 09:30–16:00. - One secret:
govee_api_keyinsecrets.yaml(first!secretthis config uses). The H607C device id is inlined inconfiguration.yaml(non-credential identifier), not a secret. - Deploy gate:
configuration.yaml,scripts.yaml,automations.yamlall change → CI does a fullha core restart. Realgovee_api_keymust be in/config/secrets.yamlon the box before the deploy or the health check fails. - Verified scene ids (H607C, 2026-07-10),
id / paramId: Strike9693/16568, Thunderstorm9705/16580, Lava9645/16520, Fire9647/16522, Sunset Glow9649/16524, Ocean9635/16510, Rustling leaves9656/16531, Forest9634/16509, Green Reign29882/41597, Sunrise9558/16433, Morning9668/16543, Afternoon9669/16544, Sunset9631/16506, Starry Sky9707/16582, Candlelight9666/16541. - Branch:
feat/corner-lamp-renderer(spec already committed). Tracking issue #74 — move it to In Progress on the Blairmont Smart Home board when execution starts. - TDD note for YAML: there is no unit-test harness for HA config; each task's "test" is
make lint(yamllint + structural + entity-ref pytest) and, where the file is container-validatable,make check-config(needs Docker). Runtime behavior is verified live at deploy (Task 8).
File Structure¶
configuration.yaml— addrest_command.govee_corner_lamp_scene,input_boolean.corner_lamp_manual,input_text.corner_lamp_last_render.scripts.yaml— addcorner_lamp_apply_scene,corner_lamp_off,corner_lamp_render; editwake_house; delete 10 oldcorner_lamp_*scripts.automations.yaml— addcorner_lamp_render_triggers,corner_lamp_manual_webhook_on,corner_lamp_manual_webhook_off.secrets.yaml.template,tests/fixtures/ci_secrets.yaml— addgovee_api_key.tests/fixtures/entities_allow.txt— allowlist the 2 new helpers (pre-snapshot).- Docs:
projects/govee_floor_lamp.md,projects/smart_lighting_scenes.md,CLAUDE.md,docs/lighting.md,docs/automations.md.
Task 1: Secrets plumbing + config scaffolding (rest_command + helpers)¶
Files: - Modify: tests/fixtures/ci_secrets.yaml - Modify: secrets.yaml.template - Modify: configuration.yaml (add rest_command:, extend input_boolean:, extend input_text:) - Modify: tests/fixtures/entities_allow.txt
Interfaces: - Produces: rest_command.govee_corner_lamp_scene (data vars: scene_id, param_id); input_boolean.corner_lamp_manual; input_text.corner_lamp_last_render.
- [ ] Step 1: Fetch the real H607C device id to inline
Run (prints only the device id — safe, it is not a credential):
ssh haos 'KEY=$(ha addons info b9845f46_govee2mqtt --raw-json | jq -r .data.options.govee_api_key); \
curl -s -H "Govee-API-Key: $KEY" https://openapi.api.govee.com/router/api/v1/user/devices \
| jq -r ".data[] | select(.sku==\"H607C\") | .device"'
AB:CD:12:34:...:H607C device id. Note it — call it <DEVICE_ID> below. - [ ] Step 2: Add the API-key stub to the CI secrets fixture
Append to tests/fixtures/ci_secrets.yaml:
- [ ] Step 3: Document the secret in the template
Append to secrets.yaml.template:
# Govee Platform API key (Govee Home app → Settings → Apply for API Key).
# Used by rest_command.govee_corner_lamp_scene to fire native H607C scenes.
govee_api_key: "YOUR_GOVEE_API_KEY"
- [ ] Step 4: Add the rest_command to
configuration.yaml
Add a new top-level block (place near other integration blocks). Replace <DEVICE_ID> with the value from Step 1:
# Fire a native Govee scene on the Corner Lamp (H607C) in ONE Platform-API call.
# Native scenes are reachable on the API key alone (not blocked by govee2mqtt #649,
# which only gates real-time push / tap-to-run). The device id is a non-secret
# identifier (useless without the key); only the key is a !secret. See
# docs/superpowers/specs/2026-07-10-corner-lamp-renderer-design.md.
rest_command:
govee_corner_lamp_scene:
url: https://openapi.api.govee.com/router/api/v1/device/control
method: POST
content_type: "application/json"
headers:
Govee-API-Key: !secret govee_api_key
payload: >-
{"requestId":"ha-corner-lamp","payload":{"sku":"H607C","device":"<DEVICE_ID>","capability":{"type":"devices.capabilities.dynamic_scene","instance":"lightScene","value":{"id":{{ scene_id }},"paramId":{{ param_id }}}}}}
- [ ] Step 5: Add the two helpers
Under the existing input_boolean: block in configuration.yaml, add:
# Manual override for the Corner Lamp renderer. On = script.corner_lamp_render
# yields (stop) so you can free-paint the lamp in the Govee app. Cleared by the
# dashboard toggle, the corner_lamp_auto webhook, or the morning wake (wake_house).
corner_lamp_manual:
name: "Corner Lamp Manual"
icon: mdi:floor-lamp-torchiere
input_text: block, add: # Dedupe token for the Corner Lamp renderer — the last render it applied
# (e.g. "circadian:morning", "ticker:lava", "alert:strike", "off:away"). The
# renderer skips re-sending an unchanged scene so the on-device animation
# isn't restarted every 5-minute re-eval. Alerts bypass this (force).
corner_lamp_last_render:
name: "Corner Lamp Last Render"
icon: mdi:history
max: 32
- [ ] Step 6: Allowlist the two helpers (pre-snapshot)
Append to tests/fixtures/entities_allow.txt:
# Corner Lamp renderer helpers — defined in configuration.yaml, registered after
# the next config deploy. Move to entities.txt via `make snapshot` post-deploy (#74).
input_boolean.corner_lamp_manual
input_text.corner_lamp_last_render
- [ ] Step 7: Validate
Run: make lint Expected: PASS (yamllint clean; structural + entity-ref pytest pass — the two helpers resolve via the allowlist).
Run: make check-config Expected: PASS — Testing configuration at /config → no errors. (!secret govee_api_key resolves from the stub; the rest_command schema validates.)
- [ ] Step 8: Commit
git add configuration.yaml secrets.yaml.template tests/fixtures/ci_secrets.yaml tests/fixtures/entities_allow.txt
git commit -m "feat(lighting): corner lamp scene rest_command + manual/dedupe helpers (#74)"
Task 2: Scene-apply + off primitive scripts¶
Files: - Modify: scripts.yaml (add two scripts)
Interfaces: - Consumes: rest_command.govee_corner_lamp_scene, input_text.corner_lamp_last_render, switch.corner_lamp_power_switch. - Produces: script.corner_lamp_apply_scene (fields: scene = "id,paramId" csv, token string, force bool default false); script.corner_lamp_off (field: token string).
- [ ] Step 1: Add the primitives to
scripts.yaml
# Corner Lamp render primitives (#74). apply_scene = dedupe → power on → fire ONE
# Govee scene → store token. off = dedupe → power off → store token. The renderer
# (script.corner_lamp_render) is the only intended caller. Dedupe skips a no-op
# re-send (avoids restarting the on-device animation every 5-min re-eval); alerts
# pass force:true to always re-fire. See the design spec.
corner_lamp_apply_scene:
alias: "Corner Lamp — apply scene"
icon: mdi:floor-lamp-torchiere
mode: queued
max: 10
fields:
scene:
description: "Scene as 'id,paramId' (e.g. '9633,16508')."
example: "9633,16508"
token:
description: "Dedupe token stored after a successful apply."
example: "ticker:forest"
force:
description: "Bypass dedupe (alerts always re-fire)."
default: false
sequence:
- if: "{{ force or states('input_text.corner_lamp_last_render') != token }}"
then:
- action: switch.turn_on
target:
entity_id: switch.corner_lamp_power_switch
continue_on_error: true
- action: rest_command.govee_corner_lamp_scene
data:
scene_id: "{{ scene.split(',')[0] | trim }}"
param_id: "{{ scene.split(',')[1] | trim }}"
- action: input_text.set_value
target:
entity_id: input_text.corner_lamp_last_render
data:
value: "{{ token }}"
corner_lamp_off:
alias: "Corner Lamp — off"
icon: mdi:floor-lamp-torchiere
mode: queued
max: 10
fields:
token:
description: "Dedupe token stored after turning off."
example: "off:away"
sequence:
- if: "{{ states('input_text.corner_lamp_last_render') != token }}"
then:
- action: switch.turn_off
target:
entity_id: switch.corner_lamp_power_switch
continue_on_error: true
- action: input_text.set_value
target:
entity_id: input_text.corner_lamp_last_render
data:
value: "{{ token }}"
- [ ] Step 2: Validate
Run: make lint Expected: PASS.
Run: make check-config Expected: PASS (scripts parse; corner_lamp_apply_scene/corner_lamp_off register).
- [ ] Step 3: Commit
git add scripts.yaml
git commit -m "feat(lighting): corner lamp apply-scene + off render primitives (#74)"
Task 3: The priority-stack renderer¶
Files: - Modify: scripts.yaml (add corner_lamp_render)
Interfaces: - Consumes: script.corner_lamp_apply_scene, script.corner_lamp_off, input_boolean.corner_lamp_manual, alarm_control_panel.blairmont_alarm, binary_sensor.any_leak_wet, zone.home, input_boolean.sleeping, sensor.sofi_change_pct, sensor.sofi_quote. - Produces: script.corner_lamp_render (no fields).
- [ ] Step 1: Add the renderer to
scripts.yaml
# Corner Lamp priority renderer (#74) — owns light.corner_lamp via native Govee
# scenes. Same choose-stack pattern as script.night_lights_render. Priority (top
# wins): manual→yield · alarm→Strike · leak→Thunderstorm · away→off · asleep→off ·
# market+fresh SOFI→ticker · else→circadian. Everything is an ANIMATED scene (never
# a solid color) except off/yield. Re-render triggers: automation
# corner_lamp_render_triggers. Full design + scene-id table: the design spec.
corner_lamp_render:
alias: "Corner Lamp — render"
icon: mdi:floor-lamp-torchiere
mode: restart
sequence:
- choose:
# 0. Manual override → yield. You own the lamp via the Govee app until the
# flag clears (dashboard toggle / corner_lamp_auto webhook / morning wake).
- conditions: "{{ is_state('input_boolean.corner_lamp_manual','on') }}"
sequence:
- stop: "manual override — renderer yields"
# 1. Alarm triggered → Strike (electric lightning). force: always re-fire.
- conditions: "{{ is_state('alarm_control_panel.blairmont_alarm','triggered') }}"
sequence:
- action: script.corner_lamp_apply_scene
data:
scene: "9693,16568"
token: "alert:strike"
force: true
# 2. Leak wet → Thunderstorm (water-themed, distinct from alarm). force.
- conditions: "{{ is_state('binary_sensor.any_leak_wet','on') }}"
sequence:
- action: script.corner_lamp_apply_scene
data:
scene: "9705,16580"
token: "alert:thunderstorm"
force: true
# 3. Away (nobody home) → off. Below alerts on purpose.
- conditions: "{{ states('zone.home') | int(0) == 0 }}"
sequence:
- action: script.corner_lamp_off
data:
token: "off:away"
# 4. Asleep → off.
- conditions: "{{ is_state('input_boolean.sleeping','on') }}"
sequence:
- action: script.corner_lamp_off
data:
token: "off:sleep"
# 5. Market hours (Mon–Fri 09:30–16:00 ET) → SOFI ticker. Freshness-gated:
# stale/missing quote → neutral Ocean (guards the #47 stuck-value failure).
- conditions: >-
{{ now().weekday() < 5
and '09:30' <= now().strftime('%H:%M') < '16:00' }}
sequence:
- variables:
fresh: >-
{{ has_value('sensor.sofi_change_pct')
and states.sensor.sofi_quote is not none
and (now() - states.sensor.sofi_quote.last_updated).total_seconds() < 1200 }}
pct: "{{ states('sensor.sofi_change_pct') | float(0) }}"
- choose:
- conditions: "{{ not fresh }}"
sequence:
- action: logbook.log
data:
name: "Corner Lamp"
message: "SOFI quote stale/missing — showing neutral Ocean."
- action: script.corner_lamp_apply_scene
data:
scene: "9635,16510"
token: "ticker:stale"
default:
- variables:
tier: >-
{% set a = pct | abs %}
{% if pct <= -4 %}lava
{% elif pct <= -2 %}fire
{% elif pct <= -0.5 %}sunset_glow
{% elif a < 0.5 %}ocean
{% elif pct < 2 %}rustling_leaves
{% elif pct < 4 %}forest
{% else %}green_reign{% endif %}
tmap:
lava: "9645,16520"
fire: "9647,16522"
sunset_glow: "9649,16524"
ocean: "9635,16510"
rustling_leaves: "9656,16531"
forest: "9634,16509"
green_reign: "29882,41597"
- action: script.corner_lamp_apply_scene
data:
scene: "{{ tmap[tier | trim] }}"
token: "ticker:{{ tier | trim }}"
# 6. default → circadian scene by ET time block (weekends + weekday off-hours).
default:
- variables:
block: >-
{% set h = now().hour %}
{% if 5 <= h < 8 %}sunrise
{% elif 8 <= h < 12 %}morning
{% elif 12 <= h < 17 %}afternoon
{% elif 17 <= h < 20 %}sunset
{% elif 20 <= h < 23 %}starry_sky
{% else %}candlelight{% endif %}
cmap:
sunrise: "9558,16433"
morning: "9668,16543"
afternoon: "9669,16544"
sunset: "9631,16506"
starry_sky: "9707,16582"
candlelight: "9666,16541"
- action: script.corner_lamp_apply_scene
data:
scene: "{{ cmap[block | trim] }}"
token: "circadian:{{ block | trim }}"
- [ ] Step 2: Validate
Run: make lint Expected: PASS (entity-ref: input_boolean.corner_lamp_manual resolves via allowlist; sensor.sofi_change_pct, alarm/leak/zone/sleeping already in the snapshot).
Run: make check-config Expected: PASS.
- [ ] Step 3: Commit
git add scripts.yaml
git commit -m "feat(lighting): corner lamp priority renderer — circadian + SOFI ticker + alerts (#74)"
Task 4: Trigger automation + manual webhooks¶
Files: - Modify: automations.yaml (add three automations, in a new numbered section)
Interfaces: - Consumes: script.corner_lamp_render, input_boolean.corner_lamp_manual. - Produces: automations corner_lamp_render_triggers, corner_lamp_manual_webhook_on, corner_lamp_manual_webhook_off.
- [ ] Step 1: Generate two random webhook ids
Run:
python3 -c "import secrets; print('manual', secrets.token_hex(16)); print('auto', secrets.token_hex(16))"
<MANUAL_WEBHOOK_ID> and <AUTO_WEBHOOK_ID>. - [ ] Step 2: Add the automations to
automations.yaml
Append near the other renderer trigger automations (after the string-lights section). Replace the two webhook-id placeholders:
##############################################################################
# 24. Corner Lamp Renderer (#74)
##############################################################################
# Re-render the Corner Lamp on every governing signal. script.corner_lamp_render
# owns the priority stack; this just calls it. /5-min tick refreshes the market
# ticker; time triggers mark the circadian block boundaries.
- alias: "Corner Lamp — Render Triggers"
id: corner_lamp_render_triggers
triggers:
- trigger: state
entity_id:
- input_boolean.corner_lamp_manual
- input_boolean.sleeping
- binary_sensor.any_leak_wet
- alarm_control_panel.blairmont_alarm
- zone.home
- sensor.sofi_change_pct
- trigger: sun
event: sunset
- trigger: sun
event: sunrise
- trigger: time
at:
- "05:00:00"
- "08:00:00"
- "12:00:00"
- "17:00:00"
- "20:00:00"
- "23:00:00"
- "09:30:00"
- "16:00:00"
- trigger: time_pattern
minutes: "/5"
- trigger: homeassistant
event: start
actions:
- action: script.corner_lamp_render
mode: single
# Manual override ON — renderer yields so you can free-paint in the Govee app.
- alias: "Corner Lamp — Manual On (webhook)"
id: corner_lamp_manual_webhook_on
triggers:
- trigger: webhook
webhook_id: <MANUAL_WEBHOOK_ID>
allowed_methods:
- POST
- GET
local_only: false
actions:
- action: input_boolean.turn_on
target:
entity_id: input_boolean.corner_lamp_manual
mode: single
# Manual override OFF — hand the lamp back to the renderer.
- alias: "Corner Lamp — Manual Off / Auto (webhook)"
id: corner_lamp_manual_webhook_off
triggers:
- trigger: webhook
webhook_id: <AUTO_WEBHOOK_ID>
allowed_methods:
- POST
- GET
local_only: false
actions:
- action: input_boolean.turn_off
target:
entity_id: input_boolean.corner_lamp_manual
- action: script.corner_lamp_render
mode: single
- [ ] Step 3: Validate
Run: make lint Expected: PASS (Tier-2 unique automation id/alias check passes; the three ids are new).
Run: make check-config Expected: PASS.
- [ ] Step 4: Record the webhook URLs
Note for later wiring (Alexa routine / Home / NFC): https://<nabu-casa-or-local>/api/webhook/<MANUAL_WEBHOOK_ID> (manual on), .../api/webhook/<AUTO_WEBHOOK_ID> (auto/off). These go in docs/lighting.md in Task 7.
- [ ] Step 5: Commit
git add automations.yaml
git commit -m "feat(lighting): corner lamp render triggers + manual on/off webhooks (#74)"
Task 5: Morning reset in wake_house¶
Files: - Modify: scripts.yaml (the wake_house corner-lamp block, ~lines 187–205)
Interfaces: - Consumes: input_boolean.corner_lamp_manual, script.corner_lamp_render.
- [ ] Step 1: Replace the raw corner-lamp turn-on block
In script.wake_house, replace the existing step that does the repeat: count: 3 over switch.corner_lamp_power_switch + light.corner_lamp (the block whose comment mentions "#65 — that was the bug") with:
# 3. Corner Lamp — hand it to the renderer for the day. Clear any manual override
# (guaranteed daily reset), then render (→ circadian/ticker; turns the power
# switch on itself). Replaces the old raw 3× power+segment turn-on (#65, #74).
- action: input_boolean.turn_off
target:
entity_id: input_boolean.corner_lamp_manual
- action: script.corner_lamp_render
wake_house (TOU bulb, sleeping/error clear, alarm disarm) unchanged. - [ ] Step 2: Validate
Run: make lint Expected: PASS.
Run: make check-config Expected: PASS.
- [ ] Step 3: Commit
git add scripts.yaml
git commit -m "refactor(lighting): wake_house clears corner_lamp_manual + renders (was raw turn-on) (#74)"
Task 6: Teardown — delete the 10 old scene scripts¶
Files: - Modify: scripts.yaml (delete 10 scripts)
- [ ] Step 1: Confirm nothing outside scripts.yaml/docs references them
Run:
grep -rn "corner_lamp_cozy\|corner_lamp_sunset\|corner_lamp_fireplace\|corner_lamp_ocean\|corner_lamp_aurora\|corner_lamp_cyberpunk\|corner_lamp_lavender\|corner_lamp_focus\|corner_lamp_rainbow\|corner_lamp_gradient" \
automations.yaml lovelace/ *.yaml 2>/dev/null
scripts.yaml definitions + docs, which Task 7 rewrites). If a dashboard match appears, remove that tile in this task too. - [ ] Step 2: Delete the scripts
Remove these top-level keys and their bodies from scripts.yaml: corner_lamp_cozy, corner_lamp_sunset, corner_lamp_fireplace, corner_lamp_ocean, corner_lamp_aurora, corner_lamp_cyberpunk, corner_lamp_lavender, corner_lamp_focus, corner_lamp_rainbow, corner_lamp_gradient. Keep movie_mode. Also remove the block comment header that introduced the Govee gradient scenes.
- [ ] Step 3: Validate
Run: make lint Expected: PASS (nothing references the removed scripts via captured patterns).
Run: make check-config Expected: PASS.
- [ ] Step 4: Commit
git add scripts.yaml
git commit -m "refactor(lighting): remove 10 corner_lamp gradient scripts — native scenes replace them (#74)"
Task 7: Documentation¶
Files: - Modify: projects/govee_floor_lamp.md, projects/smart_lighting_scenes.md, CLAUDE.md, docs/lighting.md, docs/automations.md
- [ ] Step 1: Correct
projects/govee_floor_lamp.md
Fix the false claim that native scenes are impossible/blocked by #649. Replace the "Not done — and not doable by us" paragraph and the §B1 stopgap note with: native dynamic_scene/lightScene scenes ARE reachable on the API key alone (one /device/control call, verified 2026-07-10); #649 only blocks real-time push + tap-to-run. Note the renderer (script.corner_lamp_render) now drives the lamp via rest_command.govee_corner_lamp_scene, and the 10 throttled gradient scripts are retired. Cross-link the design spec and issue #74.
- [ ] Step 2: Rewrite the Corner Lamp section of
projects/smart_lighting_scenes.md
Replace the "Corner Lamp (built)" scene-scripts description with: a priority renderer (circadian arc / SOFI ticker / non-red alerts) via native scenes; manual override (input_boolean.corner_lamp_manual + two webhooks + morning reset); dedupe via input_text.corner_lamp_last_render. Move the old gradient-scene notes to a "superseded" line.
-
[ ] Step 3: Update
CLAUDE.md -
Architecture/automations: add a §24 "Corner Lamp Renderer" entry describing the priority stack and the single-call scene REST path.
- Alexa Voice Exposure: drop the 8 corner-lamp scenes from the table and change the count 20 → 12 (
movie_modestays; the exposed set removed:corner_lamp_cozy/gradient/sunset/fireplace/ocean/aurora/cyberpunk/lavender). - Key Entities: add
input_boolean.corner_lamp_manual. - Govee integration note: native scenes now used via
rest_command; correct the "blocked" language. -
Fix the
scripts.yaml-referenced claim that animated scenes are blocked (in the govee note). -
[ ] Step 4: Update user manual
docs/lighting.md
Add a Corner Lamp section: what it shows through the day (circadian), during market hours (SOFI ticker — green up / red down / blue flat), and on alerts (dramatic non-red). How to take manual control (dashboard toggle or the manual webhook) then pick colors in the Govee app, and that it returns to auto via the auto webhook or automatically at the morning wake. Include the two webhook URLs from Task 4 Step 4.
- [ ] Step 5: Update
docs/automations.md
Add a technical section for the §24 Corner Lamp renderer: the priority stack table, the ticker tiers, the circadian blocks, the trigger list, and the manual/webhook flow.
- [ ] Step 6: Validate docs build
Run: mkdocs build --strict Expected: PASS — no broken internal links / missing-nav warnings (warnings are fatal under --strict). Fix any cross-page anchor that doesn't resolve.
- [ ] Step 7: Commit
git add projects/govee_floor_lamp.md projects/smart_lighting_scenes.md CLAUDE.md docs/lighting.md docs/automations.md
git commit -m "docs(lighting): document corner lamp renderer; correct native-scenes-blocked claim (#74)"
Task 8: Deploy + post-deploy verification¶
Files: - Modify (post-deploy): tests/fixtures/entities.txt, tests/fixtures/entities_allow.txt
- [ ] Step 1: Put the real API key on the box (pre-deploy, required)
Run (paste the real key when prompted; do not echo it into history):
ssh haos 'grep -q "^govee_api_key:" /config/secrets.yaml || echo "govee_api_key: \"REPLACE_ME\"" >> /config/secrets.yaml; \
grep "^govee_api_key:" /config/secrets.yaml'
/config/secrets.yaml on the box to the real key (e.g. ssh haos vi /config/secrets.yaml). Verify it matches the add-on key: ssh haos 'diff <(ha addons info b9845f46_govee2mqtt --raw-json | jq -r .data.options.govee_api_key) <(grep "^govee_api_key:" /config/secrets.yaml | sed -E "s/.*\"(.*)\".*/\1/") && echo MATCH'
MATCH. - [ ] Step 2: Merge to
mainand push (triggers CI)
git checkout main && git merge --no-ff feat/corner-lamp-renderer -m "feat(lighting): corner lamp priority renderer (#74)"
git push
gh run list --workflow=ci.yml --limit 1
test job passes, then deploy-ha runs a full ha core restart. Confirm green: gh run watch $(gh run list --workflow=ci.yml --limit 1 --json databaseId -q '.[0].databaseId')
ssh haos "ha core logs --lines 20"
- [ ] Step 3: Preview-test the alert scenes on the real lamp
With the lamp reachable, fire each alert scene and eyeball it (reuse the tonight pattern):
ssh haos 'KEY=$(ha addons info b9845f46_govee2mqtt --raw-json | jq -r .data.options.govee_api_key); \
DEV=$(curl -s -H "Govee-API-Key: $KEY" https://openapi.api.govee.com/router/api/v1/user/devices | jq -r ".data[]|select(.sku==\"H607C\").device"); \
for V in "9693,16568" "9705,16580"; do \
curl -s -X POST -H "Govee-API-Key: $KEY" -H "Content-Type: application/json" \
-d "{\"requestId\":\"t\",\"payload\":{\"sku\":\"H607C\",\"device\":\"$DEV\",\"capability\":{\"type\":\"devices.capabilities.dynamic_scene\",\"instance\":\"lightScene\",\"value\":{\"id\":${V%,*},\"paramId\":${V#*,}}}}}" \
https://openapi.api.govee.com/router/api/v1/device/control | jq -r .msg; sleep 10; done'
success twice; Strike reads as an alarm (electric, dramatic, non-red) and Thunderstorm as a distinct water/storm look. If either underwhelms, swap the id/paramId in script.corner_lamp_render (backups: Fireworks 9665/16540, Dance Party 15766/25596) and re-deploy that one line. - [ ] Step 4: Trim Alexa exposure
Remove the 8 corner-lamp scenes from the allowlist and re-sync (per CLAUDE.md "Alexa Voice Exposure" — stop HA, jq-edit /config/.storage/homeassistant.exposed_entities to set each script.corner_lamp_* should_expose:false, start, then WS cloud/alexa/sync), then re-discover in the Alexa app. Verify:
ssh haos 'jq "[.data.exposed_entities|to_entries[]|select(.value.assistants[\"cloud.alexa\"].should_expose==true)|.key]|length" /config/.storage/homeassistant.exposed_entities'
- [ ] Step 5: Refresh the entity snapshot
Now that the helpers are registered and the old scripts are gone:
Then remove the two helper lines (and their comment) fromtests/fixtures/entities_allow.txt, since make snapshot now lists them in entities.txt. - [ ] Step 6: Validate + commit the snapshot
Run: make lint Expected: PASS (helpers now resolve from entities.txt; the 10 deleted scripts are pruned from the snapshot).
git add tests/fixtures/entities.txt tests/fixtures/entities_allow.txt
git commit -m "chore(snapshot): corner lamp helpers in, retired gradient scripts out (#74)"
git push
-
[ ] Step 7: Live behavior spot-check
-
Toggle
input_boolean.corner_lamp_manualon → confirm the renderer stops touching the lamp; off → confirm it re-renders. - Call the manual/auto webhook URLs (Task 4) → confirm the flag flips.
- Force a render outside market hours → confirm a circadian scene for the current block.
- (Optional, market hours) watch a
/5tick pick the right ticker tier for the livesensor.sofi_change_pct. - Close issue #74 with a note linking the spec + plan.
Self-Review¶
Spec coverage: circadian arc (Task 3 default branch) ✓ · SOFI ticker tiers + freshness gate (Task 3 branch 5) ✓ · non-red alert scenes (Task 3 branches 1–2) ✓ · manual override + webhooks + morning reset (Tasks 1, 4, 5) ✓ · dedupe/robustness (Task 2) ✓ · one rest_command, scenes-only (Tasks 1–2) ✓ · teardown + Alexa trim (Tasks 6, 8) ✓ · secrets/CI stub (Tasks 1, 8) ✓ · snapshot/allowlist handling (Tasks 1, 8) ✓ · docs incl. corrected #649 claim (Task 7) ✓ · alert-scene preview (Task 8 Step 3) ✓. Spec's second secret (govee_corner_lamp_device) intentionally dropped — device id inlined as a non-credential; noted in Global Constraints.
Placeholder scan: the only <...> tokens (<DEVICE_ID>, <MANUAL_WEBHOOK_ID>, <AUTO_WEBHOOK_ID>) are values the implementer fetches/generates via the exact command given in the same task — not deferred work.
Type/name consistency: corner_lamp_apply_scene(scene, token, force) and corner_lamp_off(token) are defined in Task 2 and called with those exact field names in Tasks 3 and 5; rest_command.govee_corner_lamp_scene data vars scene_id/param_id match between Task 1 (definition) and Task 2 (call); input_text.corner_lamp_last_render and input_boolean.corner_lamp_manual names consistent across Tasks 1–5.