lights,web: per-light override with last-command-wins arbitration - #462
lights,web: per-light override with last-command-wins arbitration#462bennotk wants to merge 2 commits into
Conversation
|
CI is red, but not because of this diff: every job in all three workflows (including Nothing to fix in the branch — please check the account's Actions billing/spending limit if this persists. I'll re-run the workflows on my next check-in and report back once they actually execute. Generated by Claude Code |
Per-light override slot (full LightConfig snapshot, replace never merge) with baseSeq arbitration: group re-broadcasts with an unchanged seq keep the override, any real group change (seq bump or reassignment) takes the light back, an optional durationMs auto-reverts receiver-side, and a reboot always comes back on the group (volatile slot). The standing brightness adjustment is bypassed while an override is active; the hardware clamp applies to everything. REST set/clear on the owning device, override state in /api/lights and the peers push, dashboard indicator (following group / overridden) with a clear control, mock parity incl. a seeded override and displacement on group edits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QCqsYkCrhRHLoWZFuDTWjh
1334ece to
7e4aeca
Compare
PreviewURL: https://batterylight-pr-462.blackpond-ef672c92.germanywestcentral.azurecontainerapps.io Hosted on Azure Container Apps — accessible from the internet. |
Two firmware-build failures the CI outage had hidden. GCC 8.4 rejects an in-class array initializer whose element type carries default member initializers, and four static Slots — each holding a full LightConfig — overflowed esp32dev's DRAM segment by 136 bytes at link time. Hold the slots in a function-local static pointer table (the idiom SceneManager already uses) and allocate a Slot only while an override is actually set, freeing it on clear. Static cost drops to the pointer table; an override costs its snapshot on the heap for as long as it lives, and a failed allocation leaves the light following its group. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QCqsYkCrhRHLoWZFuDTWjh
cccbaca to
54fe618
Compare
|
CI is green after rebasing onto the renamed project's The firmware build had never actually run during yesterday's runner outage, and it surfaced two real failures in this branch, both now fixed in
The slots now live in a function-local static pointer table (the idiom Worth noting separately: the DRAM segment had only ~400 bytes of headroom before this change, which is unrelated to this PR but likely to bite the next feature that adds static state. Generated by Claude Code |
Summary
src/lights/LightOverrides.h: volatile per-light override slots — a direct command stores a fullLightConfigsnapshot (replace, never merge) plus the group'sseqat capture time (baseSeq), the light's group membership, and an optionaldurationMsdeadline. RAM-only by design: a reboot always comes back rendering the groupapplyEffectiveLight()funnel inmain.cppthat every apply path goes through — group edits (web/MQTT/buttons), meshLightConfigrelay,GroupSyncadoption, group reassignment, group deletion:seq == baseSeq(periodic self-heal) keep the overrideseq > baseSeq, or moving the light to another group, drops it — the light rejoins its groupdurationMsexpiry reverts receiver-side via the 50 ms slow tickbrightnessOverrideEnabled, config: allow brightness override per light, not just per group #299) is bypassed while an override is active and applies again on rejoin; the hardware clamp (config,led: per-light hardware brightness limit and scale #456) applies to overrides like everything else. Phase-sync snaps skip overridden lightsPOST /api/lights/override({index, durationMs?, <LightConfig fields>}, unspecified fields fall back to the current group state) andPOST /api/lights/override/clear. Override state exposed asoverriddeninGET /api/lightsand inself.lightsof the peers payload, so it rides the existing WebSocket peers push on set/clear/displacement/expiry/api/groups/updatenow bumpsseqon light-field edits), Patio starts overridden so the indicator/clear flow is exercisable immediately; 8 new mock-server testsCloses #457
Test plan
npm run lintandnpm test(44/44) passclang-format --dry-run --Werrorclean on all changedsrc/files/validate-uion the dashboard flow: seeded "Overridden" indicator + Clear button on exactly one light, Clear click flips the row to "Following group", a REST-set override shows the indicator, a real group change displaces itpr-OTA build: set an override via REST → exactly that light changes while group siblings keep the scene; edit the group → the light rejoins; wait out adurationMsoverride → it reverts on its own; reboot mid-override → the light comes back on the group🤖 Generated with Claude Code
https://claude.ai/code/session_01QCqsYkCrhRHLoWZFuDTWjh
Generated by Claude Code