Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions wled00/presets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,6 @@ void handlePresets()

JsonObject fdo;

presetToApply = 0; //clear request for preset
callModeToApply = 0;

DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset);

#if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3)
Expand All @@ -180,9 +177,27 @@ void handlePresets()
}
fdo = pDoc->as<JsonObject>();

// only reset errorflag if previous error was preset-related
// only reset error flag if previous error was preset-related
if ((errorFlag == ERR_NONE) || (errorFlag == ERR_FS_PLOAD)) errorFlag = presetErrFlag;

// is this the boot preset and will the preset set lor
const bool isBootPreset = (tmpMode==CALL_MODE_INIT || tmpPreset==bootPreset);
const bool presetWillSetLor = (!fdo["lor"].isNull() && fdo["lor"].as<int>() > REALTIME_OVERRIDE_NONE);

// During setup, only allow the boot preset itself or safe boot-preset chains.
const bool shouldAllowPresetApply = (
setupComplete || isBootPreset ||
(currentPreset == bootPreset && realtimeOverride > REALTIME_OVERRIDE_NONE) ||
(currentPreset == bootPreset && currentPlaylist > 0 && presetWillSetLor)
);
if (shouldAllowPresetApply) {
presetToApply = 0; //clear request for preset
callModeToApply = 0;
} else {
releaseJSONBufferLock();
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is inappropriate and should be removed. We should not limit which presets are applied at boot. Supporting setting the boot preset to something like {"ps": "1~5r"}, so it essentially randomly selects a preset between 1 and 5 at boot, would be a useful feature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing path:
boot preset is set -> realtime mode starts -> main loop queues boot preset -> realtime mode ends -> boot preset is applied

New path:
boot preset is set -> if boot preset overrides realtime mode, boot preset is applied immediately, if not, the behavior above applies (this is why it is gated like this)

Proposed change:
Same as "New path" if boot preset sets realtime override
If boot preset does not set realtime override:
boot preset is applied -> realtime starts -> transition from boot preset to realtime

This could make sense, but it introduces an unexpected transition from the boot preset to the realtime data. For a user that wants to boot into realtime data, there is no longer a way to achieve this without turning off boot preset, which means that there is no preset state in the queue for when/if realtime ends.

This is specifically why I gated it the way that it did. Perhaps the documentation is a little unclear - it's not that the boot preset is never getting applied when it does not set lor, it's that it remains queued until realtime is ended, which preserves the previously existing behavior.

Copy link
Copy Markdown
Member

@willmmiles willmmiles May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boot preset is set -> realtime mode starts -> main loop queues boot preset

This is a nondeterministic race, and this outcome is not and cannot be guaranteed -- whether the selected boot preset is applied by the main loop before or after processing the first event that triggers real-time mode will be sensitive to local network timing and subtle code timing changes in WLED or the platform toolkit we depend on. I don't think we should encourage users to bet on races, regardless of how often they might win in their particular configurations.

Always applying the boot preset in setup() has the major advantage of being strictly deterministic about the starting state. (As well as offering a guarantee that the "first light" state will be exactly the user preset state instead of only "off" or "yellow" -- this has been a source of confusion in the past!)

If someone wants the behavior of "start up and wait for a little bit to allow real-time mode to start, then fall back to preset N", I think this could be implemented by setting a boot preset with a little playlist, eg. {"playlist":{"ps":[n_waiting, n_fallback], "dur": [ wait_time, 1]}}, where n_waiting points to the waiting state preset, and n_fallback is the "fallback after live mode ends" preset.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree with you. I believe I overthought this in an attempt to preserve existing behavior as much as possible rather than go all in on new behavior. The reality is that the new behavior is better, and that's the whole point. Check my latest commit.

//HTTP API commands
const char* httpwin = fdo["win"];
if (httpwin) {
Expand All @@ -194,8 +209,11 @@ void handlePresets()
changePreset = true;
} else {
if (!fdo["seg"].isNull() || !fdo["on"].isNull() || !fdo["bri"].isNull() || !fdo["nl"].isNull() || !fdo["ps"].isNull() || !fdo[F("playlist")].isNull()) changePreset = true;
if (!(tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~')))
fdo.remove("ps"); // remove load request for presets to prevent recursive crash (if not called by button and contains preset cycling string "1~5~")

// only allow load requests from boot presets that set lor or button calls
const bool isButtonException = (tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~'));
const bool shouldAllowLoadRequest = (isBootPreset && presetWillSetLor) || (tmpMode == CALL_MODE_BUTTON_PRESET && fdo["ps"].is<const char *>() && strchr(fdo["ps"].as<const char *>(),'~') != strrchr(fdo["ps"].as<const char *>(),'~'));
if (!shouldAllowLoadRequest) fdo.remove("ps"); // remove load request for presets to prevent recursive crash
Comment thread
smitty078 marked this conversation as resolved.
Outdated
deserializeState(fdo, CALL_MODE_NO_NOTIFY, tmpPreset); // may change presetToApply by calling applyPreset()
}
if (!errorFlag && tmpPreset < 255 && changePreset) currentPreset = tmpPreset;
Expand Down Expand Up @@ -282,4 +300,4 @@ void deletePreset(byte index) {
writeObjectToFileUsingId(getPresetsFileName(), index, &empty);
presetsModifiedTime = toki.second(); //unix time
updateFSInfo();
}
}
9 changes: 9 additions & 0 deletions wled00/wled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ void WLED::disableWatchdog() {

void WLED::setup()
{
setupComplete = false; // flag to indicate setup is in progress
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detection
#endif
Expand Down Expand Up @@ -503,6 +504,12 @@ void WLED::setup()

if (needsCfgSave) serializeConfigToFS(); // usermods required new parameters; need to wait for strip to be initialised #4752

if (bootPreset > 0) {
handlePresets(); // handle boot preset
handlePlaylist(); // handle playlist if preset queued one
handlePresets(); // handle presets again to give a chance for anything queued by the boot preset or playlist
}

if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0 && !configBackupExists())
showWelcomePage = true;

Expand Down Expand Up @@ -595,6 +602,8 @@ void WLED::setup()
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector
#endif
markOTAvalid();

setupComplete = true; // safety check setup function has completed
}

void WLED::beginStrip()
Expand Down
3 changes: 3 additions & 0 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,9 @@ WLED_GLOBAL byte optionType;
WLED_GLOBAL bool configNeedsWrite _INIT(false); // flag to initiate saving of config
WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers

// setup status
WLED_GLOBAL bool setupComplete _INIT(false); // flag for preset loading safety
Comment thread
smitty078 marked this conversation as resolved.
Outdated

// status led
#if defined(STATUSLED)
WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0);
Expand Down
Loading