Symptom
At ~5pm on a cloudy day, indoor lights on, the screen drops to effectively zero brightness and looks off. Room is clearly lit but LDR reads in the low range and the curve collapses to ~MIN_BRIGHT_DISPLAY_ON = 4 (i.e. brightness = 4/255).
Root cause (firmware/src/main.cpp::automaticBrightControl)
Three bugs compounding:
- No low-side clamp on the LDR reading.
map(currentValue, ldrMin, ldrMax, 1, slots) is called when currentValue is below ldrMin. map() extrapolates below the input range — produces slot 0 or negative. Cast to uint8_t produces undefined-behavior brightness levels.
- Floor brightness is too low.
MIN_BRIGHT_DISPLAY_ON = 4 (~1.5%) is below the threshold where the panel produces useable light in a normally-lit room. Anywhere in the bottom slot of the curve, the panel looks off.
- No averaging, no hysteresis on the off path. Single transient low LDR reads (a passing shadow, brief cloud) pin the panel at slot 1. The 10-slot quantization makes the curve jagged.
The MIN_BRIGHT_DISPLAY_OFF = 0 branch (full off when LDR < ldrMin) is also wrong as designed: autoBrightMin is configured by the user as "the LDR reading at which output should be minimum brightness" — not "below this, kill the display." A separate threshold should govern actual lights-out.
Fix plan
- Average the last N (~8) LDR samples — absorbs cloud/shadow/headlight transients.
- Clamp the averaged reading into
[ldrMin, ldrMax] before mapping.
- Drop the 10-slot quantization. Map directly to a brightness in
[MIN_VISIBLE_BRIGHT, displayBright] where MIN_VISIBLE_BRIGHT is high enough to remain legible in any lit room (~12/255).
- Replace the
< ldrMin → off shortcut with a separate hard cutoff: display goes fully off only when the averaged LDR reading is below DARK_OFF_THRESHOLD (~5/4095) for ≥60s. Means the room is actually pitch black, not just dim.
- Tighten the apply-only-on-change threshold to ±4 brightness units (continuous, not slot-based) so motion-sensitive displays don't pulse but small sun-cloud movement doesn't drop the panel into the off range.
- Log under
[Bright] prefix at level changes for tunability.
Acceptance
Out of scope
- Exposing the new constants (
MIN_VISIBLE_BRIGHT, DARK_OFF_THRESHOLD, sample window) in the web UI. Hardcoded for now; if the fix lands and feels off, follow-up to add prefs.
- Gamma / log-scale curve on the LDR-to-brightness mapping. Linear with averaging is enough for the stated symptom.
Symptom
At ~5pm on a cloudy day, indoor lights on, the screen drops to effectively zero brightness and looks off. Room is clearly lit but LDR reads in the low range and the curve collapses to ~
MIN_BRIGHT_DISPLAY_ON = 4(i.e. brightness = 4/255).Root cause (
firmware/src/main.cpp::automaticBrightControl)Three bugs compounding:
map(currentValue, ldrMin, ldrMax, 1, slots)is called whencurrentValueis belowldrMin.map()extrapolates below the input range — produces slot 0 or negative. Cast touint8_tproduces undefined-behavior brightness levels.MIN_BRIGHT_DISPLAY_ON = 4(~1.5%) is below the threshold where the panel produces useable light in a normally-lit room. Anywhere in the bottom slot of the curve, the panel looks off.The
MIN_BRIGHT_DISPLAY_OFF = 0branch (full off when LDR <ldrMin) is also wrong as designed:autoBrightMinis configured by the user as "the LDR reading at which output should be minimum brightness" — not "below this, kill the display." A separate threshold should govern actual lights-out.Fix plan
[ldrMin, ldrMax]before mapping.[MIN_VISIBLE_BRIGHT, displayBright]whereMIN_VISIBLE_BRIGHTis high enough to remain legible in any lit room (~12/255).< ldrMin → offshortcut with a separate hard cutoff: display goes fully off only when the averaged LDR reading is belowDARK_OFF_THRESHOLD(~5/4095) for ≥60s. Means the room is actually pitch black, not just dim.[Bright]prefix at level changes for tunability.Acceptance
MIN_VISIBLE_BRIGHTand only goes fully off after ~60s sustained.Out of scope
MIN_VISIBLE_BRIGHT,DARK_OFF_THRESHOLD, sample window) in the web UI. Hardcoded for now; if the fix lands and feels off, follow-up to add prefs.