Late-night dim compounds across nights: brightness decays 128 → 51 → 20 → 8 on consecutive no-event days #110
Labels
No labels
area/ai
area/backend
area/frontend
area/infra
area/scheduler
area/wled
good-first-issue
priority/high
priority/low
priority/medium
type/bug
type/chore
type/ci-cd
type/docs
type/feature
type/qa
v1.0.0
v1.1.0
v1.2.0
v2.0.0
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Iris-WLED#110
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while confirming #7's unattended cycles on the dev deployment. The scheduler is working correctly; this is a separate defect it exposed.
Observed
Three consecutive nights on the dev host, with
default_schemeunset so every no-event evening pushes off:brilate_summer_moon_2026_mid(global_brightness: 128){"on": false}{"on": false}Controller now reads
on=false, bri=8. Left alone it continues 8 → 3 → 1.Cause
run_dim_jobreads the live state and dims relative to it, with no check that the lights are on:Two things follow:
late_night_brightness = 40reads as "dim to 40%", but it means "dim by 60% of current".{"on": false}from the no-event path leavesbriuntouched, so nothing resets the baseline and each night's dim stacks on the last.It self-heals the moment a real scheme is pushed, because
scheme_to_wled_statesetsbrifromglobal_brightness— and likewise for thedim_whitedefault, which setsbriexplicitly. The decay window is a run of consecutive no-event days withdefault_schemeunset, which for a holiday-lighting system is most of the year.Why it matters
Nothing is visible while the lights are off, which is why this went unnoticed. The failure mode is a user turning the lights on by hand during a quiet stretch — via the WLED app, or Iris's own quick push, which sets segment brightness but not the global
bri— and finding them at 3% with no explanation. "My lights come on almost black" is a hard thing to trace back to a dim job that ran correctly on an empty night.There is also a smaller reporting problem:
dim_joblogssuccessevery night while dimming an off controller, so the schedule log implies work that had no effect.Suggested fix
Either would break the compounding; they are not exclusive:
ctrl.get_state()is already fetched — acurrent.get("on")check is free, and loggingskippedis more honest thansuccess.global_brightness(or 255 when no scheme is active) makeslate_night_brightnessmean what its name says and makes the operation idempotent.The first is the smaller change and fixes the observed decay. The second fixes the underlying non-idempotence, which would still bite if the dim job ever ran twice in one night — for example after a restart between 23:00 and sunrise re-registering a pending
dim_job.Acceptance criteria
successwhen the controller is offLATE_NIGHT_BRIGHTNESSmeans — a target level, or a reduction factor. The env comment and settings label should agree with the code.Found during #7. Related: #106.
Fixed in #111.
dim_statenow takes an explicit baseline, so it is idempotentsuccesswhen the controller is off — it logsskippeddim_stateapplied twice gives the same answer;run_dim_jobagainst an off controller pushes nothing and logsskippedLATE_NIGHT_BRIGHTNESSdocumented as a percentage of the scheme's brightness, in both.env.exampleand spec §5.2Spec §5.2 already specified the reduction as proportional — "overrides per-scheme brightness values proportionally" — so proportional was correct and the baseline was the defect. Worth checking before changing it, since the alternative reading (40% of full scale) would have been a behaviour change disguised as a bug fix.
One extra case the fix covers, not in the original report: with
default_scheme = dim_whitethe compounding happened within a single night. The ambient default comes on atlate_night_brightness% of 255 (102), anddim_jobthen took 40% of that — 40 — a few hours later. With a stable 255 baseline the dim now lands on the level already showing, so it is a no-op rather than a second reduction.That also explains how the ambiguity survived:
dim_white_statereads the setting as a percentage of 255 whiledim_stateread it as a percentage of current. Two defensible readings of "brightness 40", both live in the same codebase.Closing.