Skip to content

Fix: prevent LED flash on cold boot when a boot preset is configured#5676

Draft
leuenbert wants to merge 2 commits into
wled:mainfrom
leuenbert:main
Draft

Fix: prevent LED flash on cold boot when a boot preset is configured#5676
leuenbert wants to merge 2 commits into
wled:mainfrom
leuenbert:main

Conversation

@leuenbert

@leuenbert leuenbert commented Jun 10, 2026

Copy link
Copy Markdown

Description

When WLED is powered by a "dumb" switch that cuts power to the whole circuit (PSU + ESP together), the LEDs briefly flash their previously-restored state at full/restored brightness during boot, before the configured boot preset (e.g. a playlist) takes over. This PR removes that flash.

Root Cause

The boot preset is queued in WLED::beginStrip() and applied later in setup() with a transition. That transition crossfades from the current (restored) segment buffer. On cold boot the segment buffer still holds the restored colors at the restored brightness, so the crossfade's source frame is bright — that's what flashes for the first fraction of a second.

Testing performed

  • ✅ Compiles.
  • ✅ Tested on actual hardware (ESP32 + WS2812B, single-switch PSU+ESP setup, FCOB SPI CCT WS2811 IC LED Strip) with a boot playlist. Normal cold boot (power off ≥3 s, then on) is now clean — no flash.
  • ⚠️ Known limitation: a rapid off/on within <2 s can still show a brief flash. This is a hardware effect, not firmware — the PSU's bulk capacitors keep the WS2812s powered through the dip, so the
    strip holds its last latched frame until the ESP reboots and sends a black frame. Waiting a few seconds (or adding a bleeder resistor across the 5 V rail) avoids it. Firmware sends black as early as
    it can (after config load + finalizeInit).

Summary by CodeRabbit

  • Bug Fixes
    • Improved boot preset initialization to properly manage LED transitions during startup. When a boot preset is applied, the LED strip now initializes to a clean black state with transitions appropriately disabled, then re-enables transitions after the preset is fully applied. This ensures smoother and more predictable LED behavior during device startup.

prevent flickering when using a "dumb switch" that turns on / off the whole LED circuit (PSU and ESP). Due to boot duration LED flicker when turning on or using initial playlist
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ffd85d18-1135-47ac-b9a3-30562652b6ad

📥 Commits

Reviewing files that changed from the base of the PR and between d8e3578 and fa1317e.

📒 Files selected for processing (1)
  • wled00/wled.cpp

Walkthrough

Boot-time LED flash prevention is implemented by coordinating transitions and preset application in WLED::setup() and WLED::beginStrip(). When a boot preset is active, the strip is blanked to BLACK with transitions disabled, then transitions are restored after the preset sequence completes.

Changes

Boot preset flash prevention

Layer / File(s) Summary
Boot preset initialization and transition control
wled00/wled.cpp
WLED::beginStrip() blanks segments to BLACK with transitions disabled, resets color and brightness state, and returns early without re-enabling transitions when a boot preset is active. WLED::setup() restores transitions to transitionDelayDefault after the boot preset sequence completes, deferring transition activation until preset application finishes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • wled/WLED#5468: The boot preset timing changes and transition control directly address the boot-time LED flash and flash timing behavior described in this issue.

Possibly related PRs

  • wled/WLED#5573: Both PRs adjust the boot-preset startup flow in wled00/wled.cpp by changing how and when boot presets are applied during initialization.

Suggested reviewers

  • softhack007
  • willmmiles
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: preventing LED flash on cold boot with a boot preset configured. It directly reflects the primary objective and fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@softhack007

softhack007 commented Jun 10, 2026

Copy link
Copy Markdown
Member

That transition crossfades from the current (restored) segment buffer. On cold boot the segment buffer still holds the restored colors at the restored brightness,

@leuenbert what kind of segment buffer do you mean?
When WLED is power-cut, the MCU is off. Software is off, RAM is off. There is no memory buffer that will "hold restored colors" at the next boot.

@softhack007 softhack007 added the AI Partly generated by an AI. Make sure that the contributor fully understands the code! label Jun 10, 2026
@softhack007

Copy link
Copy Markdown
Member

I think this PR is largely "snake oil".
@coderabbitai full review.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR diff
gh pr view 5676 --repo wled/WLED --json title,body,files
echo "---DIFF---"
gh pr diff 5676 --repo wled/WLED

Length of output: 5593


🏁 Script executed:

#!/bin/bash
# Get broader context around beginStrip() and setup() in the PR branch
gh api repos/wled/WLED/contents/wled00/wled.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | head -800 | tail -400
echo "=== LINES 600-700 ==="
gh api repos/wled/WLED/contents/wled00/wled.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | sed -n '600,700p'

# Also check the base (main) branch version for comparison
echo "=== BASE beginStrip ==="
gh api repos/wled/WLED/contents/wled00/wled.cpp?ref=main \
  --jq '.content' | base64 -d | sed -n '610,680p'

# Check what transitionDelayDefault is and when it's set
gh api repos/wled/WLED/contents/wled00/wled.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n "transitionDelay\|transitionDef" | head -30

Length of output: 23452



@softhack007 Here is the full review.


Review of PR #5676

On the "snake oil" concern

The concern is partially valid — the root-cause description in the PR is misleading in two ways:

  1. "Cold boot" is a misnomer. This flash would occur on any boot (warm reset, watchdog reboot, software restart) whenever a boot preset with a non-zero transitionDelayDefault is configured. It is not limited to cold/power-cut scenarios. The flash doesn't require the "dumb switch cuts both PSU and ESP" setup at all.

  2. "Segment buffer holds restored colors in RAM" is technically wrong. After a power-cut, RAM is indeed cleared. The segment state is restored by deserializeConfig() (reading from LittleFS/flash) before beginStrip() is called. The flash-loaded state is what populates the segment buffers, not any RAM persistence.

Is the underlying bug real?

Yes. The actual root cause is a sequencing problem:

Old code flow:

beginStrip()
  → strip.setTransition(0)        // transitions off
  → [flash-restored state in segs]
  → colorUpdated(CALL_MODE_INIT)
  → applyPreset(bootPreset)       // only QUEUES the preset
  → strip.setTransition(transitionDelayDefault) ← transitions RE-ENABLED here
setup()
  → handlePresets()               // fires with transitions ENABLED → crossfade from restored state → FLASH

The boot preset was queued in beginStrip() but processed by handlePresets() in setup() — by which time transitionDelayDefault had already been restored, so the preset applied as a crossfade from the flash-loaded colors. That crossfade is visible as a flash.

Is the fix sound?

Mostly yes. The new flow correctly defers strip.setTransition(transitionDelayDefault) until after handlePresets() completes:

beginStrip()
  → blackout segments, bri=0
  → colorUpdated(CALL_MODE_INIT)
  → applyPreset(bootPreset)       // queued
  → return early (NO transition re-enable)
setup()
  → handlePresets()               // fires with transition=0 → instant, no crossfade
  → handlePlaylist()
  → handlePresets()
  → strip.setTransition(transitionDelayDefault) ← restored only now

Issues identified

1. Partial segment blackout (incomplete fix)
Only seg.colors[0] is cleared per segment. colors[1] (secondary color) and colors[2] (tertiary color) are untouched. Effects that sample secondary/tertiary colors during the brief window between colorUpdated() and the boot preset application could still render non-black briefly. The old code had the same omission.

// clears only primary color:
if (seg.isActive()) seg.colors[0] = BLACK;
// colors[1] and colors[2] are NOT cleared

2. Undocumented behavioral change: turnOnAtBoot=true + bootPreset>0
The PR moves the bootPreset > 0 check above turnOnAtBoot, so now bootPreset unconditionally overrides turnOnAtBoot. The old code honored turnOnAtBoot (set brightness, no blackout) when both were set. The new code always blacks out first. This is arguably more correct (the old combination also caused a flash and the preset sets its own brightness), but it is an undocumented behavioral change that could surprise users.

3. Root-cause description is wrong
As noted above — the description attributes the bug to cold-boot RAM retention, which is incorrect. The bug is reproducible on any boot with a non-zero transitionDelayDefault. The PR description and code comments propagate this misdiagnosis, which will confuse future readers.

4. strip.show() asymmetry with relay
When rlyPin >= 0, no black frame is pushed (strip.show() is skipped). This is consistent with pre-existing behavior in the else branch and handleOnOff(true) presumably keeps the strip off via the relay. Low risk, but worth noting that the relay path relies entirely on the relay holding the strip dark.

5. Transitions remain disabled during userSetup() / UsermodManager::setup()
After the early return in beginStrip(), setup() calls userSetup() and UsermodManager::setup() before handlePresets() restores transitions. Any usermod that triggers LED updates during its setup will do so with transitionDelay=0. This is likely harmless but is an unintended side effect.

Summary

The fix addresses a real, pre-existing bug (not snake oil), but the root-cause description is incorrect and misleads about the scope of the problem. The mechanism of the fix is sound. The main behavioral concern is the silent override of turnOnAtBoot when a boot preset is configured, and the partial segment blackout. The PR could benefit from correcting the root-cause description and clearing all three colors[] entries per segment.

@DedeHai

DedeHai commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

This can be fixed, in fact I did this but never PR'd. Similar but the code here is only partially correct

@leuenbert

Copy link
Copy Markdown
Author

That transition crossfades from the current (restored) segment buffer. On cold boot the segment buffer still holds the restored colors at the restored brightness,

@leuenbert what kind of segment buffer do you mean? When WLED is power-cut, the MCU is off. Software is off, RAM is off. There is no memory buffer that will "hold restored colors" at the next boot.

Basically what i've tested is using a "dumb switch" so PSU and MCU are connected to an Outlet that is controlled by a normal light switch.

In the current version when turning the whole thing off and on, and you have a Boot Playlist/Transition it will: flash -> turn black -> play transition/playlist

But when you turn the brightness to 0/color to BLACK right before switching off the whole thing with the "dumb switch", wait 3s and switch it on again the flash doesn't happen

Maybe my wording was wrong, I didn't mean a buffer that persists through the power loss.

I mean the buffer after WLED reloads the saved brightness and colors from flash during boot. The boot preset is applied with a transition, so the crossfade starts from whatever is already in that buffer. That's why the strip lights up bright for a split second before the preset kicks in.

The fix forces a black starting frame (and applies the boot preset with transitions off) so the crossfade has nothing bright to start from.

As @DedeHai stated, there might be a prettier solution for that though

@willmmiles

Copy link
Copy Markdown
Member

I believe the AI analysis is wrong. @coderabbitai, can you point to where in the code it "restores flash state to segs"? To the best of my knowledge this is not a thing -- the initial segment state is determined by only three config inputs:

  • "turn on at boot"
  • "create one segment per output"
  • the global brightness setting, if set in the config

The initial segments are always initialized to mode solid, either orange or black, depending on the state of "turn on at at
boot". color[1] and color[2] are always initialized to black by the Segment constructor.


Applying the boot preset should execute the transition configured in the boot preset, or the default transition from the config.

If "turn on at boot" is disabled, then the correct behaviour is to execute the boot preset with transition from an initial state of all segments black.

If "turn on at boot" is set, then the current behaviour is for the boot preset to transition from the default initial state (ie. one segment at strip length set to solid orange). It's not very useful but it's the natural consequence of the interaction of those features. I could accept an argument that we should ignore the "turn on at boot" flag if a boot preset is set, but it would be a breaking change. I believe it's been discussed before.

It is definitely not correct to disable the transition to the boot preset. (The fade-in behaviour by default is quite nice IMO.)

As far as I can find, this is what the current code does: the first frame written to the LEDs is always either full black or full yellow, depending on the "turn on at boot" setting. At no point in the firmware is any color data remembered from a previous boot, and the transition correctly proceeds from the initial segment setting. All pixel buffers are fully initialized with a clean frame render before show() is called.


I haven't been able to reproduce the issue. I always get exactly as described above, an initial frame of either orange or black, followed by a transition to the boot preset, as expected. On my test bench I'm using a switch in the USB 5V line for the micro and a WS2812-compatible LED strip.

I think the implication is that the issue is hardware-ish -- I'm aware some LEDs can hold their control state for a period of time after power-down; and we've already seen that 16.0 has a different power-on timing than previous versions (cf. all the newly reported issues with poor power supply regulation needing an additional capacitor). I note that one of the changes in this patch is that it moves the first show() to earlier in the setup path when a boot preset is configured and "turn on at boot" is set; I am suspecting that's the functional change. We might want to consider an "early LED output frame" as a mitigation for hardware weirdnessess at power on, though I fear that will have even further knock-on effects on power supply regulation.

@leuenbert Have you already tried disabling "turn on at boot"? Did that change the behaviour?

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the Segment constructor to see how colors[] are initialized
gh api repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n "Segment::" | head -5
echo "=== Segment constructor ==="
gh api repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n -A 40 "Segment::Segment(" | head -60

# Check if deserializeConfig restores segment colors
echo "=== deserializeConfig segment color restore ==="
gh api repos/wled/WLED/contents/wled00/json.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n "col\|color\|seg" | grep -i "deserialize\|load\|restore" | head -20

# Check cfg.cpp for segment color deserialization
echo "=== cfg.cpp segment loading ==="
gh api repos/wled/WLED/contents/wled00/cfg.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n -B2 -A5 "col\|colors\[" | grep -i "seg\|color\|col" | head -40

# Check if there is any segment state persistence/restore
echo "=== presets.cpp for segment state ==="
gh api repos/wled/WLED/contents/wled00/presets.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n "applyPreset\|colors\|col\[" | head -30

# Check beginStrip in the PR branch for what colorUpdated does
echo "=== beginStrip full ==="
gh api repos/wled/WLED/contents/wled00/wled.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n -A 80 "beginStrip\(\)" | head -100

Length of output: 11612


🏁 Script executed:

#!/bin/bash
# Find the DEFAULT Segment constructor (not copy/move)
echo "=== Default Segment constructor ==="
gh api repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n -B2 -A30 "^Segment::Segment(uint" | head -50

# Check FX.h for the Segment struct default member initializers
echo "=== FX.h Segment struct colors init ==="
gh api repos/wled/WLED/contents/wled00/FX.h?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n -A5 "colors\[" | head -30

echo "=== FX.h Segment struct col default ==="
gh api repos/wled/WLED/contents/wled00/FX.h?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n "colors\b\|uint32_t col" | head -20

# Check makeAutoSegments to see if it sets colors
echo "=== makeAutoSegments ==="
gh api repos/wled/WLED/contents/wled00/FX_fcn.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n -A 40 "makeAutoSegments" | head -60

# Check the full beginStrip in PR branch (continuation from line 607)
echo "=== beginStrip full PR branch ==="
gh api repos/wled/WLED/contents/wled00/wled.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | sed -n '607,700p'

# Check turnOnAtBoot usage in beginStrip
echo "=== turnOnAtBoot in beginStrip ==="
gh api repos/wled/WLED/contents/wled00/wled.cpp?ref=refs/pull/5676/head \
  --jq '.content' | base64 -d | grep -n "turnOnAtBoot\|briLast\|briS\b" | head -20

Length of output: 12004



@willmmiles You are correct, and I apologize for the inaccurate analysis. After looking at the actual code:

You are right on both counts.

  1. No "restore flash state to segs" — There is no such code path. makeAutoSegments() always creates fresh segments using the default constructor, which in FX.h line 565 initializes colors{DEFAULT_COLOR, BLACK, BLACK}. Nothing in beginStrip(), cfg.cpp, or anywhere else restores segment colors from flash into the segment buffer before the boot preset is applied.

  2. colors[1] and colors[2] are always BLACK — The constructor initializer colors{DEFAULT_COLOR, BLACK, BLACK} makes this explicit. My claim that they were "untouched" was wrong.

Corrected picture of the initial state:

  • turnOnAtBoot=truecolors[0] = DEFAULT_COLOR (orange), brightness from config
  • turnOnAtBoot=falsebri=0, fill(BLACK)

So any software-reproducible flash would specifically be the orange DEFAULT_COLOR crossfading to the boot preset — only when turnOnAtBoot=true and bootPreset>0. If turnOnAtBoot=false, the initial state is already black and there's no software-driven flash.

Your hardware hypothesis (LEDs retaining state briefly after power-down, plus the earlier strip.show() in this PR being the meaningful functional change) is a more credible explanation for what the reporter observes. The question of whether the "turn on at boot + boot preset → orange flash" case is worth fixing separately is a distinct, narrower issue.

Again, I'm sorry for the noise in the earlier review — the root-cause description I gave was wrong.


✏️ Learnings added
Learnt from: willmmiles
URL: https://github.com/wled/WLED/pull/5676

Timestamp: 2026-06-11T02:50:36.862Z
Learning: In WLED (wled00/FX.h), the Segment default constructor initializes `colors[NUM_COLORS]` as `{DEFAULT_COLOR, BLACK, BLACK}` — colors[1] and colors[2] are always BLACK at construction. There is NO code path that restores segment colors from flash/config into the segment buffer; segment state after makeAutoSegments() is always freshly initialized. The initial LED state depends only on "turn on at boot" (DEFAULT_COLOR orange or black/bri=0), "create one segment per output", and the global brightness setting.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@blazoncek

Copy link
Copy Markdown
Contributor

Why don't you just keep turnOnAtBoot off and let boot preset turn LEDs on?

That was the intended behavior as not all boot presets turn WLED on. I.e. just set up segments.

However, if you want to keep turnOnAtBoot on and prevent transition from default orange color to a boot preset defined one there is a much simpler way to do it.

@softhack007 softhack007 marked this pull request as draft June 11, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Partly generated by an AI. Make sure that the contributor fully understands the code!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants