Skip to content

Add boot_lor usermod - enforces a configured realtime override at startup#5569

Open
smitty078 wants to merge 7 commits intowled:mainfrom
smitty078:usermod-boot-lor
Open

Add boot_lor usermod - enforces a configured realtime override at startup#5569
smitty078 wants to merge 7 commits intowled:mainfrom
smitty078:usermod-boot-lor

Conversation

@smitty078
Copy link
Copy Markdown

@smitty078 smitty078 commented May 4, 2026

Applies a configured realtime override (lor) value after initial network activity.

Useful for API-driven setups (HomeKit, Home Assistant, etc.) where realtime streaming (DDP/E1.31) should not take control at boot.

  • Optional additional delay before applying
  • Reasserts value for a configurable duration to handle startup races

Summary by CodeRabbit

  • New Features

    • Boot LOR usermod: waits for network, optional delay, applies a configured realtime override at boot, reasserts for a set duration, then stops.
  • Documentation

    • README with configuration schema, runtime status indicators, example config, tested hardware notes, and behavioral details.
  • Chores

    • Packaging manifest added for installation.
    • New usermod identifier added for platform integration.

Applies a configured realtime override (lor) value after initial network or UDP activity.

Useful for API-driven setups (HomeKit, Home Assistant, etc.) where realtime streaming (DDP/E1.31) should not take control at boot.

- Optional wait for first UDP packet
- Optional additional delay before applying
- Reasserts value for a configurable duration to handle startup races
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

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: b928600f-a3e2-43e2-8d63-ebb19d54d561

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3e2da and f6759a6.

📒 Files selected for processing (1)
  • usermods/boot_lor/readme.md

Walkthrough

Adds a new WLED usermod "boot_lor" that enforces a configured realtimeOverride at boot. Includes PlatformIO manifest, README, C++ usermod implementation with lifecycle/config/JSON hooks, and a new USERMOD_ID_BOOT_LOR constant.

Changes

Boot LOR Usermod

Layer / File(s) Summary
Data / Config Schema
usermods/boot_lor/usermod_boot_lor.cpp
Adds persisted config object "boot_lor" with fields bootLor, additionalWaitSec, assertForSec; validates bootLor; exposes runtime JSON at info -> u -> "Boot LOR" as [bootLor, state, realtimeOverride].
Core Implementation
usermods/boot_lor/usermod_boot_lor.cpp
Implements BootLorUsermod : public Usermod with setup(), connected(), loop(), addToConfig(), readFromConfig(), addToJsonInfo(), getId(); non-blocking flow: wait for network + optional delay → set realtimeOverride = bootLor → record first apply → reassert until assertForSec elapsed → mark finished.
Registration / IDs
wled00/const.h, usermods/boot_lor/usermod_boot_lor.cpp
Adds #define USERMOD_ID_BOOT_LOR 59; defines static boot_lor_usermod and registers it via REGISTER_USERMOD(boot_lor_usermod).
Manifest / Packaging
usermods/boot_lor/library.json
Adds PlatformIO library.json for boot_lor v1.0.0 targeting espressif32/arduino with libArchive: false and srcDir: ".".
Documentation
usermods/boot_lor/readme.md
Adds README describing purpose, startup sequence, config schema and defaults, runtime info, install snippet, tested targets, and example configuration.

Sequence Diagram

sequenceDiagram
  participant WLED as WLED System
  participant Usermod as BootLor Usermod
  participant Network as Network Layer
  participant Config as Config System

  WLED->>Usermod: setup()
  Usermod->>Config: read "boot_lor" config
  Network->>Usermod: connected() / WLED_CONNECTED
  Usermod->>Usermod: record connected timestamp
  Usermod->>Usermod: wait additionalWaitSec
  Usermod->>WLED: set realtimeOverride = bootLor
  Usermod->>Usermod: record firstAppliedMs, state = applied
  Usermod->>Usermod: reassert until assertForSec elapsed
  Usermod->>Usermod: set state = finished
  Usermod->>Config: add runtime info to info -> u
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • wled/WLED#5403: Rewrites usermod registration/registration mechanism and may affect REGISTER_USERMOD usage and discovery for the new BootLor usermod.

Suggested reviewers

  • softhack007
  • willmmiles
  • DedeHai
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a new usermod that enforces a realtime override at startup, which directly matches the changeset's primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
usermods/boot_lor/usermod_boot_lor.cpp (1)

3-18: ⚡ Quick win

4-space indentation throughout — guideline requires 2-space.

The entire class body uses 4-space indentation. The C++ coding conventions mandate 2-space indentation (no tabs) across the project.

As per coding guidelines: "Use 2-space indentation in C++ files (no tabs)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/boot_lor/usermod_boot_lor.cpp` around lines 3 - 18, The class
BootLorUsermod is indented with 4 spaces throughout; update its formatting to
use 2-space indentation everywhere (no tabs) for the class declaration and all
member declarations/definitions (e.g., BootLorUsermod, _name, bootLor,
waitForUdpPacket, additionalWaitSec, assertForSec, referenceMs, firstAppliedMs,
referenceSet, applied, finished) so the file conforms to the project's 2-space
C++ indentation guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@usermods/boot_lor/readme.md`:
- Line 33: Fix the typo in the README text: replace "relaibly" with "reliably"
in the sentence "Using JSON or HTTP API in a boot preset does not relaibly set
lor" so it reads "Using JSON or HTTP API in a boot preset does not reliably set
lor".
- Around line 47-49: Two fenced code blocks open without a language tag (e.g.
the block containing "WiFi connected → first UDP packet received → (optional
delay) → apply lor → assert for N seconds → stop" and the other fenced block
later) which triggers MD040; update each opening fence from ``` to ```text (or
another appropriate language specifier) so the Markdown linter is satisfied and
rendering improves. Locate the blocks by the exact fence content and replace the
three backticks with a language-tagged fence.

In `@usermods/boot_lor/usermod_boot_lor.cpp`:
- Around line 122-124: getId() currently returns USERMOD_ID_RESERVED which is
forbidden; update it to use a proper usermod ID: add a new constant e.g.
USERMOD_ID_59 = 59 to wled00/const.h and change usermod_boot_lor::getId() to
return USERMOD_ID_59; alternatively, if you don't want to claim a permanent slot
now, return the provided generic fallback constant (e.g.
USERMOD_ID_GENERIC_FALLBACK) instead—refer to the getId() method and the
constants in wled00/const.h when making the change.

---

Nitpick comments:
In `@usermods/boot_lor/usermod_boot_lor.cpp`:
- Around line 3-18: The class BootLorUsermod is indented with 4 spaces
throughout; update its formatting to use 2-space indentation everywhere (no
tabs) for the class declaration and all member declarations/definitions (e.g.,
BootLorUsermod, _name, bootLor, waitForUdpPacket, additionalWaitSec,
assertForSec, referenceMs, firstAppliedMs, referenceSet, applied, finished) so
the file conforms to the project's 2-space C++ indentation guideline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e10ef806-04d4-42ee-abb7-93ee16c710cb

📥 Commits

Reviewing files that changed from the base of the PR and between e321514 and ce0ca4e.

📒 Files selected for processing (3)
  • usermods/boot_lor/library.json
  • usermods/boot_lor/readme.md
  • usermods/boot_lor/usermod_boot_lor.cpp

Comment thread usermods/boot_lor/readme.md Outdated
Comment thread usermods/boot_lor/readme.md Outdated
Comment thread usermods/boot_lor/usermod_boot_lor.cpp Outdated
@coderabbitai coderabbitai Bot added the usermod usermod related label May 5, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
usermods/boot_lor/readme.md (1)

123-125: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code block.

Line 123 still opens a fence with plain ; use something like text to satisfy MD040.

Suggested fix
-```
+```text
 Boot LOR: [bootLor, state, realtimeOverride]
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @usermods/boot_lor/readme.md around lines 123 - 125, The fenced code block
starting with in the README entry "Boot LOR: [bootLor, state, realtimeOverride]" needs a language tag to satisfy MD040; update the opening fence from to a tagged fence such as ```text (and keep the closing fence
unchanged) so the block is explicitly marked as plain text.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @usermods/boot_lor/readme.md:

  • Around line 123-125: The fenced code block starting with in the README entry "Boot LOR: [bootLor, state, realtimeOverride]" needs a language tag to satisfy MD040; update the opening fence from to a tagged fence such as
as plain text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2343374f-a0c9-4604-9942-f7904cfeb196

📥 Commits

Reviewing files that changed from the base of the PR and between ce0ca4e and d58db84.

📒 Files selected for processing (3)
  • usermods/boot_lor/readme.md
  • usermods/boot_lor/usermod_boot_lor.cpp
  • wled00/const.h

…raffic. Additional changes suggested by coderabbit including docstrings
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
usermods/boot_lor/usermod_boot_lor.cpp (2)

164-172: ⚡ Quick win

Consider F() wrappers on literal JSON keys/strings.

"u", "Boot LOR", "waiting", "applied", "finished" are string literals that live in RAM on ESP8266. While addToJsonInfo is not a hot-loop path, the guideline calls for F("string") on string constants. ArduinoJson's operator[] and add() both accept __FlashStringHelper*.

As per coding guidelines: "Use F("string") for string constants in C++ to save RAM on ESP8266."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/boot_lor/usermod_boot_lor.cpp` around lines 164 - 172, In
addToJsonInfo, wrap literal JSON keys and constant strings with F() to store
them in flash instead of RAM: use F("u") when indexing root (operator[]),
F("Boot LOR") when calling createNestedArray, and
F("finished")/F("applied")/F("waiting") when adding the status; keep adding
bootLor and realtimeOverride unchanged and ensure JsonArray::add calls accept
__FlashStringHelper* (they do) so replace only the string literals in the code
paths that reference root["u"], createNestedArray("Boot LOR"), and add("...").

12-12: ⚡ Quick win

_name should use PROGMEM storage per usermod coding guidelines.

static constexpr const char* places the pointer as constexpr but does not guarantee the string data lands in flash on ESP8266. The coding guideline explicitly requires static const char[] PROGMEM for repeated strings in usermod code.

♻️ Proposed fix
-  static constexpr const char* _name = "boot_lor"; ///< JSON configuration key for this usermod.
+  static const char _name[] PROGMEM; ///< JSON configuration key for this usermod.

And add outside the class (before the static instance at line 184):

+const char BootLorUsermod::_name[] PROGMEM = "boot_lor";

As per coding guidelines: "Store repeated strings as static const char[] PROGMEM in C++ usermod code."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usermods/boot_lor/usermod_boot_lor.cpp` at line 12, Replace the constexpr
pointer with a PROGMEM-stored C-string: in the class, remove or replace "static
constexpr const char* _name" and declare "static const char _name[] PROGMEM;"
(use the actual class name, e.g., usermod_boot_lor::_name) and then add the
definition outside the class: "const char <ClassName>::_name[] PROGMEM =
"boot_lor";" so the string data is placed in flash; ensure the external
definition appears before the static instance/registration so the symbol is
available at link time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@usermods/boot_lor/readme.md`:
- Line 52: Remove all references to the removed option "waitForUdpPacket" in the
README: delete the behavioral line that says "Optionally waits for the first UDP
packet (recommended for DDP setups)", remove the "waitForUdpPacket": true
entries from both config JSON examples, and remove the waitForUdpPacket row from
the options table; replace one of those spots (e.g., under Options or
Recommended settings) with a short note stating the waitForUdpPacket option was
removed and any such key in user configs is ignored to avoid silent no-op
behavior.

---

Nitpick comments:
In `@usermods/boot_lor/usermod_boot_lor.cpp`:
- Around line 164-172: In addToJsonInfo, wrap literal JSON keys and constant
strings with F() to store them in flash instead of RAM: use F("u") when indexing
root (operator[]), F("Boot LOR") when calling createNestedArray, and
F("finished")/F("applied")/F("waiting") when adding the status; keep adding
bootLor and realtimeOverride unchanged and ensure JsonArray::add calls accept
__FlashStringHelper* (they do) so replace only the string literals in the code
paths that reference root["u"], createNestedArray("Boot LOR"), and add("...").
- Line 12: Replace the constexpr pointer with a PROGMEM-stored C-string: in the
class, remove or replace "static constexpr const char* _name" and declare
"static const char _name[] PROGMEM;" (use the actual class name, e.g.,
usermod_boot_lor::_name) and then add the definition outside the class: "const
char <ClassName>::_name[] PROGMEM = "boot_lor";" so the string data is placed in
flash; ensure the external definition appears before the static
instance/registration so the symbol is available at link time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0d6d8a9c-d36e-4a9d-94d0-6ae7118d1e20

📥 Commits

Reviewing files that changed from the base of the PR and between d58db84 and a8c4dce.

📒 Files selected for processing (2)
  • usermods/boot_lor/readme.md
  • usermods/boot_lor/usermod_boot_lor.cpp

Comment thread usermods/boot_lor/readme.md Outdated
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@smitty078

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as outdated.

@smitty078 smitty078 changed the title Add boot_lor usermod Add boot_lor usermod - enforces a configured realtime override at startup May 5, 2026
coderabbitai[bot]

This comment was marked as resolved.

Clarified note about realtime streaming in readme.
@softhack007

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@netmindz
Copy link
Copy Markdown
Member

netmindz commented May 6, 2026

Thanks for the submission, but it feels like we should fix the underlying issue of not being able to set that value at boot than a usermod messing about with state

@netmindz
Copy link
Copy Markdown
Member

netmindz commented May 6, 2026

@coderabbitai please investigate why setting lor as part of a boot preset does not work 100% of the time

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Tip

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

🧩 Analysis chain

Let me dig into the WLED codebase to trace the boot preset and realtimeOverride lifecycle.


🏁 Script executed:

#!/bin/bash
# Find all places where realtimeOverride is assigned/modified
echo "=== realtimeOverride assignments ==="
rg -n 'realtimeOverride\s*=' --type cpp -C 3

echo ""
echo "=== realtimeOverride declaration ==="
rg -n 'realtimeOverride' --type cpp -B1 -A1 | head -120

Length of output: 9667


🏁 Script executed:

#!/bin/bash
# Find boot preset loading logic
echo "=== bootPreset / CALL_MODE_INIT / applyPreset at boot ==="
rg -n 'bootPreset|CALL_MODE_INIT|applyPreset|loadPreset' --type cpp -C 3 | head -200

echo ""
echo "=== realtime mode reset / realtimeLock / realtimeTimeout ==="
rg -n 'realtimeLock\|realtimeTimeout\|exitRealtime\|realtimeMode' --type cpp -C 3 | head -200

Length of output: 11749


Let me look deeper at the preset async mechanism and the network/UDP initialization sequence.


🏁 Script executed:

#!/bin/bash
echo "=== handlePresets call sites and JSON buffer lock ==="
rg -n 'handlePresets|presetToApply|callModeToApply|requestJSONBufferLock' --type cpp -C 3 | head -200

echo ""
echo "=== wled main loop - service order ==="
rg -n 'void WLED::loop\|handlePresets\|handleNotifications\|handleUDP\|handleHue\|userLoop\|e131\|ddp' wled00/wled.cpp -C 2 | head -120

echo ""
echo "=== WiFi connected callback / onWifiConnect ==="
rg -n 'onWifiConnect\|WiFiEvent\|WiFi.*connect\|ARDUINO_EVENT_WIFI' --type cpp -C 3 | head -100

Length of output: 10606


🏁 Script executed:

#!/bin/bash
echo "=== realtimeLock function ==="
rg -n 'void realtimeLock\|realtimeLock(' --type cpp -C 10 | head -100

echo ""
echo "=== exitRealtime full function ==="
sed -n '438,460p' wled00/udp.cpp

echo ""
echo "=== wled.cpp boot sequence around colorUpdated/applyPreset ==="
sed -n '600,660p' wled00/wled.cpp

Length of output: 2863


🏁 Script executed:

#!/bin/bash
echo "=== WLED main loop full ==="
sed -n '80,180p' wled00/wled.cpp

echo ""
echo "=== realtimeLock function implementation ==="
rg -n 'realtimeLock' --type cpp -C 5 | grep -A 15 'void realtimeLock'

echo ""
echo "=== UDP/E1.31 socket open / handleNotifications timing ==="
rg -n 'udpConnected\|e131\|ddp\|handleNotifications\|notificationCount\|notifyCount' --type cpp wled00/wled.cpp -C 3 | head -100

echo ""
echo "=== WiFi connected / onWifiConnect sequence ==="
rg -n 'onWifiConnect\|initInterfaces\|udpConnected\|WiFi.status' --type cpp wled00/network.cpp -C 4 | head -120

Length of output: 4902


🏁 Script executed:

#!/bin/bash
echo "=== network.cpp onWifiConnect / udp connect sequence ==="
cat wled00/network.cpp | head -300

echo ""
echo "=== handleNotifications - where UDP packets are read ==="
sed -n '390,460p' wled00/udp.cpp

echo ""
echo "=== main loop beginning (before strip section) to see handleNotifications call order ==="
sed -n '1,85p' wled00/wled.cpp

Length of output: 14528


@netmindz The investigation reveals three compounding root causes for why lor set in a boot preset is unreliable:


Root Cause 1: Async/Deferred Preset Loading (Primary)

applyPreset() (wled00/presets.cpp:131-132) does not apply the preset immediately — it only schedules it:

bool applyPreset(byte index, byte callMode) {
  presetToApply = index;      // just sets a flag
  callModeToApply = callMode;
  return true;
}

The actual JSON (including lor) is applied later in handlePresets()deserializeState(). The critical problem is the main loop execution order in wled00/wled.cpp:

handleConnection()      // 1. WiFi management
handleNotifications()   // 2. reads UDP packets → calls realtimeLock() ← STREAMING LOCK HAPPENS HERE
userLoop()              // 3. usermods
...
handlePresets()         // 4. DEFERRED: finally applies boot preset lor ← TOO LATE
strip.service()

Once WiFi connects and a streaming controller (DDP/E1.31/Hyperion) sends its first packet, handleNotifications() calls realtimeLock() before handlePresets() has had a chance to apply the boot preset's lor value. The lor takes effect one loop iteration too late.


Root Cause 2: JSON Buffer Lock Contention Widens the Window

handlePresets() (wled00/presets.cpp:155) silently bails out if the shared JSON buffer is held by any other subsystem:

if (presetToApply == 0 || !requestJSONBufferLock(JSON_LOCK_PRESET_LOAD)) return;

During the boot period, the JSON buffer is frequently contended (WebSocket send, HTTP responses, etc.), so the boot preset application can be deferred across multiple loop iterations, giving streaming data even more opportunity to race ahead.


Root Cause 3: exitRealtime() Destroys OVERRIDE_ONCE

Even if lor=1 (REALTIME_OVERRIDE_ONCE) is successfully applied by the boot preset, exitRealtime() (wled00/udp.cpp:443) permanently consumes it the moment the first streaming session ends:

void exitRealtime() {
  if (!realtimeMode) return;
  if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE; // ← destroyed here
  ...
}

This means the boot preset lor is a one-shot that doesn't survive the first stream timeout, leaving all subsequent streaming sessions completely unprotected.


Why the Usermod Works (and the Core Fix)

The boot_lor usermod addresses this by continuously re-asserting realtimeOverride in loop() for a configurable duration after connectivity — working around all three issues. But the proper fix in core WLED would be one or more of:

  1. Persist realtimeOverride in cfg.json — it's currently a runtime-only global (wled00/wled.h:834, _INIT(REALTIME_OVERRIDE_NONE)). If it were loaded from config during deserializeConfig(), it would be set before the main loop ever starts, eliminating the race entirely.

  2. Reorder the main loop — run handlePresets() before handleNotifications(). This is a minimal change that ensures the boot preset lor is applied before any streaming packet can lock the strip.

  3. Don't reset realtimeOverride in exitRealtime() unconditionally — distinguish between "set by a persistent/config source" vs. "one-shot API call". If lor came from a boot preset or config, it should survive stream timeouts.

Option 1 is probably the cleanest long-term fix, as it removes the dependency on loop ordering entirely.

@smitty078
Copy link
Copy Markdown
Author

Thanks for the submission, but it feels like we should fix the underlying issue of not being able to set that value at boot than a usermod messing about with state

Thanks for the feedback. It’s encouraging that you’re interested in going after a fix in core so that presets can work reliably for this use case. As a (new) user looking for this use-case to work for me, the usermod was the quick fix I needed and thought I’d submit as it may help others looking for the same behavior. As I investigated initially I found a fair number of “setting lor in a preset doesn’t work” type reports around the web that corroborated my own experience. Having no prior knowledge of the codebase, I didn’t initially attempt to look for a fix in core. I’m happy to take a stab at it and/or test a proposed fix as this is my primary use-case currently, so I’m invested in it working. Is the way forward to work towards implementing a proposed fix myself, or await a fix from you/the team?

@willmmiles
Copy link
Copy Markdown
Member

Why the Usermod Works (and the Core Fix)

The boot_lor usermod addresses this by continuously re-asserting realtimeOverride in loop() for a configurable duration after connectivity — working around all three issues. But the proper fix in core WLED would be one or more of:

1. **Persist `realtimeOverride` in `cfg.json`** — it's currently a runtime-only global (`wled00/wled.h:834`, `_INIT(REALTIME_OVERRIDE_NONE)`). If it were loaded from config during `deserializeConfig()`, it would be set before the main loop ever starts, eliminating the race entirely.

2. **Reorder the main loop** — run `handlePresets()` before `handleNotifications()`. This is a minimal change that ensures the boot preset `lor` is applied before any streaming packet can lock the strip.

3. **Don't reset `realtimeOverride` in `exitRealtime()` unconditionally** — distinguish between "set by a persistent/config source" vs. "one-shot API call". If `lor` came from a boot preset or config, it should survive stream timeouts.

Option 1 is probably the cleanest long-term fix, as it removes the dependency on loop ordering entirely.

@coderabbitai Option 1 would be flat out wrong. Live override is meant to be a dynamic property of the system, so that it can be enabled or disabled along with other all the other effect state. Persisting the value every time it's changed would cause excessive flash wear as the cfg would be constantly rewritten as that feature is turned on and off; the obvious alternative, naiively adding it to the cfg serialization, would have it be persisted only when some other cfg setting is saved, causing the at-boot behaviour to mysteriously change depending on what value it happened to have when some other setting was saved.

Probably the correct fix here is to actually apply the boot preset JSON before starting the main loop (instead of using the defer-execution approach we use to manage resource contention). This would also have the benefit of being able to absolutely guarantee user-controlled initialization of the FX state before any frames are rendered.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Tip

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

🧩 Analysis chain

You're absolutely right, and I apologize for the flawed Option 1 suggestion. Let me look at the boot sequence to better understand the synchronous pre-loop approach.


🏁 Script executed:

#!/bin/bash
echo "=== WLED::setup() boot sequence ==="
rg -n 'void WLED::setup\|beginStrip\|loadPreset\|applyPreset\|handlePresets\|deserializeState\|bootPreset\|deserializeConfig' wled00/wled.cpp -C 2 | head -150

echo ""
echo "=== setup() full body ==="
sed -n '1,80p' wled00/wled_main.cpp 2>/dev/null || rg -n 'WLED::setup' --type cpp -A 80 | head -120

echo ""
echo "=== where bootPreset is applied in setup vs. loop ==="
rg -n 'bootPreset' --type cpp -C 5 | head -100

echo ""
echo "=== handlePresets internals - the JSON buffer lock and actual deserializeState call ==="
rg -n 'void handlePresets\|handlePresets' --type cpp -A 30 | head -80

echo ""
echo "=== requestJSONBufferLock usage and what holds it during boot ==="
rg -n 'requestJSONBufferLock\|releaseJSONBufferLock' --type cpp -C 2 | head -80

Unrecoverable error: Failed to clone repository


@willmmiles — thank you for the correction, that's a much clearer analysis.

You're right on both counts:

  • Option 1 is wrong. realtimeOverride is an intentionally ephemeral runtime property — persisting it to cfg.json would either hammer flash on every toggle, or (if only saved on explicit cfg writes) silently bake in whatever transient value happened to be active at save time, creating hard-to-diagnose boot behaviour drift.

  • Synchronous boot preset application is the right path. The deferred presetToApply / handlePresets() mechanism exists specifically to manage runtime JSON buffer contention (WebSocket sends, HTTP responses, etc.). At boot — inside setup() — none of that contention exists, so a direct synchronous deserializeState() call for the boot preset would be both safe and deterministic. It would guarantee that lor (and all other FX state) is fully applied before loop() ever starts, before handleNotifications() can open any streaming socket, and before any frame is rendered. That eliminates the race completely without any of the fragility of the workaround approaches.

I've updated my understanding for future reviews.


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

Timestamp: 2026-05-06T19:15:29.619Z
Learning: In WLED, `realtimeOverride` (`wled00/wled.h`) is an intentionally ephemeral runtime property and must NOT be persisted to `cfg.json`. Persisting it would cause excessive flash wear (it changes frequently as lor is toggled) or mysterious boot-state drift (stale value baked in at last cfg save). The correct fix for boot-preset `lor` unreliability is to apply the boot preset JSON synchronously in `WLED::setup()` before entering the main loop, bypassing the deferred `presetToApply`/`handlePresets()` mechanism which exists only for runtime JSON buffer contention management.

Learnt from: willmmiles
URL: https://github.com/wled/WLED/pull/5569

Timestamp: 2026-05-06T19:15:29.619Z
Learning: In WLED, the deferred preset execution path (`applyPreset()` sets `presetToApply` flag → `handlePresets()` applies it in the main loop) exists to manage runtime JSON buffer contention (WebSocket, HTTP, etc.). At boot time in `setup()`, this contention does not exist, so a synchronous `deserializeState()` for the boot preset is safe and is the architecturally correct approach to guarantee FX/lor state before any frames are rendered or streaming sockets are opened.

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.

@smitty078
Copy link
Copy Markdown
Author

I did my own analysis today and I’ve concluded that I believe the best and correct option is to call handlePresets() immediately after applyPreset() in startStrip(), where the boot preset is initially added to the queue. This totally resolves the issue with the only side effect being a single use case: a boot preset that does not set lor:1 or lor:2 is set AND realtime data is received immediate or soon after boot. In the case, the user would observe a preset-> realtime transition in state, which might be undesirable, but should not be unexpected. The solution would be to avoid a boot preset that sets state that interferes with potential realtime data. This is easily enough explained in documentation and is not a behavioral bug.

@smitty078
Copy link
Copy Markdown
Author

Would it be appropriate to test this solution and submit a PR for it? Is your preference to have a new PR or to revert and amend this one for sake of discussion that has already occurred?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

usermod usermod related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants