Skip to content

Allow use of presets (physical button) when receiving live data#5370

Open
IsaccoBenedetti wants to merge 10 commits into
wled:mainfrom
IsaccoBenedetti:fix-unresponsive-buttons-when-receiving-live-data
Open

Allow use of presets (physical button) when receiving live data#5370
IsaccoBenedetti wants to merge 10 commits into
wled:mainfrom
IsaccoBenedetti:fix-unresponsive-buttons-when-receiving-live-data

Conversation

@IsaccoBenedetti
Copy link
Copy Markdown

@IsaccoBenedetti IsaccoBenedetti commented Feb 13, 2026

Hi, first time contributor here!

Recently I ran into a small issue when using physical buttons and HyperHDR. I'm not sure if this was done by design, but currently when WLED is receiving a live data stream physical button inputs are not processed. This makes it impossible to regain local control over the lights once a live source is active, without opening the webUI.

Initially I tried assigning a preset with the API command LO=2 (Live Override) to a physical button, but it didn't work as expected, because the functions that handle preset logic (handlePresets() and handlePlaylist()) are located within the conditional block that is skipped when realtimeMode is true.

This issue has been previously reported by the community on the WLED Discourse forums, but it seems it never got resolved: Live data override on the physical button.

This PR aims to solve the issue, simply by moving the handlePresets() and handlePlaylist() function calls out of the if (!realtimeMode || ...) block in the main loop.

This way button presses and their associated API commands are now processed on every loop cycle, regardless of the live data state. When a button triggers a preset containing LO=2, the realtimeOverride flag is correctly set, allowing WLED to exit the live stream and apply the desired preset.

I was a bit worried these functions were purposely been left inside the check for performance reasons, so I tested it specifically on an ESP8266 instead of an ESP32 with a HyperHDR stream, but I couldn't notice any lag or jitter at all. It seems solid.

What do you think?

Summary by CodeRabbit

New Features

  • Added a new realtime settings option to enable preset switching during realtime mode. This feature allows users to immediately apply preset changes while the device is actively receiving realtime control data, providing greater flexibility and responsiveness in synchronized lighting scenarios without interrupting the connection.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 13, 2026

Walkthrough

This PR adds a feature flag realtimeAllowPresets that enables preset switching during realtime mode. The flag is declared globally, persisted to device configuration, exposed as a checkbox in the web settings UI, and integrated into the button I/O handler to conditionally process presets when enabled.

Changes

Realtime Preset Switching

Layer / File(s) Summary
Global state and configuration persistence
wled00/wled.h, wled00/cfg.cpp
realtimeAllowPresets boolean flag is declared globally (default false) and added to config deserialization and serialization, reading/writing the "rop" JSON key.
Web settings UI integration
wled00/data/settings_sync.htm, wled00/xml.cpp, wled00/set.cpp
HTML checkbox "ROP" is added to the Realtime settings section, JSON serialization exposes the setting in sync settings form, and form submission handler reads the request parameter to update the flag.
Realtime preset handling logic
wled00/button.cpp
handleIO() gains a new conditional block: when realtime mode is active and the flag is enabled, it calls handlePresets(), optionally runs handlePlaylist() if presets don't need saving, and calls handlePresets() again before continuing with standard handleOnOff() logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • wled/WLED#5573: Both PRs modify preset-handling control flow by triggering handlePresets() during realtime; the retrieved PR alters preset sequencing and setup logic while this PR conditionally enables preset switching via the new realtimeAllowPresets flag.

Suggested labels

bug, usermod

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 PR title accurately summarizes the main change: enabling preset handling during live data reception, which is the core feature implemented across all modified files.
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.


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
Copy link
Copy Markdown
Member

softhack007 commented Feb 14, 2026

Hi, thanks for your contribution to wled development 👍

I think this PR is a bit mis-labeled ... in fact it does not modify the button handling code. Buttons are read and button can be used as always while WLED is in "external control" mode. The PR does address a weakness that presets controlled by button are not effective until "real time mode" has ended.

The PR actually changes the preset and playlist logic, by allowing preset changes and playlist activity during realtime mode. That's a behavioural change and I'm not sure we should go that way.

Which the proposed change, the order in the main loop gets changed and presets are now handled prior to OTA, HUE and NightLight. To protect previous behaviour, there should be at new option in "sync interfaces" settings, something like "presets and playlist always active" (maybe find a better wording).

I think the root cause of the problem that @IsaccoBenedetti wants to address is somewhere else:
When entering realtime mode, in some protocols the timeout is "infinite" so the controling device has to explicitly command "end realtime".

WLED/wled00/json.cpp

Lines 451 to 459 in f830ea4

if (root.containsKey("live")) {
if (root["live"].as<bool>()) {
jsonTransitionOnce = true;
strip.setTransition(0);
realtimeLock(65000);
} else {
exitRealtime();
}
}

65000 is a magic number that means "infinite".
Actually it might be better to replace this with realtimetimeout, possibly guarded by a user option.

@softhack007
Copy link
Copy Markdown
Member

softhack007 commented Feb 14, 2026

PS the summary from @coderabbitai is rubbish, it completely missed the point 😄

@coderabbitai

This comment was marked as resolved.

@softhack007
Copy link
Copy Markdown
Member

Would you like me to regenerate the summary with the proper focus on the functional/behavioral implications rather than just the code mechanics?

@coderabbitai yes. And also add that consequences of the behavioural change still need investigation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 14, 2026

@softhack007, I'll regenerate the summary to properly focus on the behavioral implications. The new summary should highlight:

  1. Core behavioral change: handlePresets() and handlePlaylist() now execute during realtime mode (previously skipped), enabling button-triggered presets with LO=2 to override realtime control
  2. User-facing impact: Physical buttons can now trigger presets that provide an escape route from realtime mode deadlock
  3. Execution order change: Presets and playlists now run before OTA, HUE, and NightLight handlers in the main loop
  4. Open questions: The consequences of this behavioral change still need investigation - particularly whether this should be opt-in via a new setting in sync interfaces to preserve previous behavior

I'll trigger the regeneration now.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.
✅ Actions performed

Summary regeneration triggered.

@softhack007
Copy link
Copy Markdown
Member

softhack007 commented Feb 14, 2026

button presets with LO=2

@coderabbitai it's not clear to me what "LO=2" is actually meant for. please inspects utility functions like leds.cpp, set.cpp, cfg.cpp, XML.cpp, json.cpp to find out what this flag is doing.

@coderabbitai

This comment was marked as duplicate.

@coderabbitai

This comment was marked as duplicate.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 14, 2026

Tip

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

🧩 Analysis chain

@softhack007, I'll investigate what the LO=2 flag does by searching through the utility functions you mentioned.


🏁 Script executed:

#!/bin/bash
# Search for "LO" parameter handling in preset-related files
echo "=== Searching for LO in json.cpp ==="
rg -n -C5 '"LO"' wled00/json.cpp

echo -e "\n=== Searching for LO in xml.cpp ==="
rg -n -C5 '"LO"' wled00/xml.cpp

echo -e "\n=== Searching for LO in set.cpp ==="
rg -n -C5 '"LO"' wled00/set.cpp

echo -e "\n=== Searching for realtimeOverride usage ==="
rg -n -C3 'realtimeOverride' wled00/

echo -e "\n=== Searching for Live Override comments or definitions ==="
rg -n -i -C2 'live.*override|override.*live' wled00/ --type cpp

Length of output: 10090


🏁 Script executed:

#!/bin/bash
# Find the constant definitions for REALTIME_OVERRIDE
echo "=== Searching for REALTIME_OVERRIDE constant definitions ==="
rg -n 'REALTIME_OVERRIDE' wled00/ --type cpp | head -30

echo -e "\n=== Looking for the enum or define ==="
rg -n -B2 -A5 'define.*REALTIME_OVERRIDE|enum.*realtime' wled00/ --type cpp

Length of output: 1632


@softhack007, I've found the LO flag definition and usage. Here's what it does:

LO = Live Override (defined in const.h:267-269):

`#define` REALTIME_OVERRIDE_NONE    0
`#define` REALTIME_OVERRIDE_ONCE    1
`#define` REALTIME_OVERRIDE_ALWAYS  2

How it works:

  1. LO=0 (NONE): Normal behavior - realtime data from external sources (E1.31, DDP, Hyperion, Adalight, etc.) is applied to LEDs

  2. LO=1 (ONCE): Temporarily override realtime data. When exitRealtime() is called, this flag automatically resets to NONE (udp.cpp:443)

  3. LO=2 (ALWAYS): Continuously override/ignore all incoming realtime data

Implementation details:

  • Set via HTTP API parameter LO= (set.cpp:1228-1236) or JSON API parameter "lor" (json.cpp:444-449)
  • When realtimeOverride is active, all realtime protocol handlers skip pixel updates:
    • udp.cpp:508,581,613 - Hyperion, TPM2, WARLS
    • e131.cpp:167,179,318 - E1.31/DMX
    • e131.cpp:56 - DDP
    • wled_serial.cpp:178 - Adalight
  • Also affects brightness application (led.cpp:68) and frame buffer clearing (FX_fcn.cpp:1613)

Special behavior: When useMainSegmentOnly is enabled, the override request is ignored and realtimeOverride is forced to NONE. Instead, the main segment's freeze state is toggled based on the requested override value (set.cpp:1232-1234, json.cpp:446-448).

So LO=2 in a button preset tells WLED: "ignore all incoming realtime data and apply this preset instead" - which is the escape mechanism from realtime mode that this PR enables by allowing handlePresets() to run during realtime.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: DedeHai
Repo: wled/WLED PR: 4923
File: wled00/FX.cpp:4883-4901
Timestamp: 2025-09-12T17:29:43.826Z
Learning: In WLED’s web UI, only one slider value (e.g., SEGMENT.intensity or SEGMENT.custom1) changes at a time; code relying on this may use simplified change guards, though presets/JSON can still update multiple fields atomically.

Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Comment thread wled00/wled.cpp
@DedeHai
Copy link
Copy Markdown
Collaborator

DedeHai commented Feb 14, 2026

another option would be to add a checkmark "exit real time mode on button press" in the config and do just that, any button press will exit?

@coderabbitai

This comment was marked as duplicate.

@softhack007
Copy link
Copy Markdown
Member

softhack007 commented Feb 14, 2026

another option would be to add a checkmark "exit real time mode on button press" in the config and do just that, any button press will exit?

I like your idea 👍

Edit: it's still a good chance to also address the "live mode has infinite timeout" problem - the behaviour is different from all other external control modes, which all use realtimetimeout instead of infinity.

@DedeHai
Copy link
Copy Markdown
Collaborator

DedeHai commented Feb 14, 2026

which one is "live mode"? I only ever used DDP and that one times out after a few seconds.

@softhack007
Copy link
Copy Markdown
Member

softhack007 commented Feb 14, 2026

which one is "live mode"? I only ever used DDP and that one times out after a few seconds.

Not sure ... its triggered by a json message with "live": true so it's not directly DDP or art-net.

Some external tools (hyperion? falcon player?) appear to use this command, but then don't send "live": false when you simply switch off your control box -> wled stuck.

@softhack007 softhack007 changed the title Allow use of pysical buttons when receiving live data Allow use of pysical button presets when receiving live data Feb 14, 2026
@softhack007 softhack007 changed the title Allow use of pysical button presets when receiving live data Allow use of presets (physical button) when receiving live data Feb 14, 2026
@softhack007
Copy link
Copy Markdown
Member

softhack007 commented Feb 14, 2026

@coderabbitai please research the internet on protocol specifications related to the "live mode" or "realtime control" supported by WLED.

Which protocols are started by a json message "live": true (directly or via MQTT) ?

Do these pprotocols mandate an "infinite timeout" until "live": false, or would a user-defined timeout of a few seconds also satisfy the needs of the protocol?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 14, 2026

Tip

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

🧩 Analysis chain

@softhack007, let me research the protocol specifications related to WLED's live mode and realtime control.


🌐 Web query:

WLED JSON API "live" parameter protocol specification

💡 Result:

WLED JSON API live parameter (protocol/spec)

Where it is used

  • Send as part of a JSON POST to /json/state (or /json). [1]

Type

  • live: boolean. [1]

Semantics

  • {"live": true}:
    • Forces WLED into realtime mode and blanks the LEDs. [1]
    • The configured realtime timeout does not apply when entered this way; WLED will stay in realtime mode until you change the normal lighting state (color/effect/segments; brightness changes are excluded). [1]
  • You are expected to send {"live": false} when you stop sending live/realtime data. [1]

Response/visibility

  • live is not included in the /json/state response. [1]
  • Realtime status is exposed as info.live (boolean) in the JSON info object (e.g., /json/info or /json/si). [1]

Related control: live override

  • lor (integer) = live data override behavior:
    • 0 off
    • 1 override until live data ends
    • 2 override until reboot (available since 0.10.0) [1]

Related “live data” endpoints/streaming (often confused with live)

  • WebSocket LED-value “peek” stream: send {"lv": true} to ws://<ip>/ws; format matches /json/live. [2]
  • Note: WLED 0.15.x release notes mention /json/live (JSON live data/peek) behavior changed (only enabled when WebSockets are disabled). [3]

Sources: WLED JSON API docs [1], WLED WebSocket wiki [2], WLED release notes [3].


🌐 Web query:

WLED realtime control protocols DDP E1.31 Hyperion timeout requirements

💡 Result:

What “timeout” means in WLED realtime modes (DDP, E1.31/sACN, Hyperion)

WLED enters a realtime/streaming lock whenever it receives realtime pixel data (e.g., E1.31/Art-Net, DDP, Hyperion UDP). It will stay in realtime mode only while packets keep arriving; when packets stop, WLED waits the configured Timeout and then resumes normal WLED control (presets/effects/UI). WLED’s Sync settings describe this as: Timeout (100..65000 ms) = time after which WLED resumes normal mode; 65000 ms keeps the streamed data indefinitely. [1]

Practical “requirements” for senders (what you must do)

  • Keep sending frames more often than WLED’s Timeout.
    If your sender pauses longer than the configured Timeout, WLED will drop out of realtime and revert/restore. (So the “requirement” is simply: don’t let the gap between packets exceed the Timeout.) [1]
  • If you want WLED to hold the last streamed frame when the sender stops, set Timeout = 65000 ms (effectively “don’t time out”). [1]

Notes specific to the protocols you mentioned

  • E1.31 (sACN) / Art-Net: These are supported as WLED realtime protocols. [2]
    Also, WLED’s E1.31 priority handling notes that higher-priority senders can overrule lower-priority senders with a 3-second timeout behavior in that priority context. [2]
  • DDP: Supported by WLED as an alternative realtime protocol (also used by Hyperion as a default streaming option). WLED requires selecting DDP mode in Sync settings (then reboot). [2]
  • Hyperion → WLED: Hyperion’s WLED integration can stream using UDP DDP or UDP RAW. It saves WLED’s state before streaming and restores it when streaming stops (independent of WLED’s own Timeout behavior). [3]
    • If using Hyperion UDP RAW, Hyperion documents a protocol limitation (packet fragmentation) and notes a practical LED-count limit for that UDP-raw protocol. [4]

Sources:
[1] WLED Settings (Sync Interfaces “Timeout 100..65000 … 65000 will keep the data indefinitely”)
[2] WLED E1.31 / Art-Net docs (DDP support, priority timeout note)
[3] Hyperion documentation: WLED device (protocols, restore state behavior)
[4] Hyperion documentation: UDP Raw (protocol notes / latch time)


🌐 Web query:

WLED live mode infinite timeout 65000 protocol specification

💡 Result:

WLED “Live / Realtime” mode timeout: how “infinite” works

1) UDP Realtime protocol (WARLS/DRGB/DRGBW/DNRGB)

WLED’s UDP Realtime packets are:

  • Byte 0: protocol selector
    (e.g., 1=WARLS, 2=DRGB, 3=DRGBW, 4=DNRGB)
  • Byte 1: timeout in seconds (how long WLED stays in realtime mode after the last packet)
    • Typical values: 1–2 seconds
    • 255 = no timeout (stay in realtime mode until another control method takes over) [1]

(Then bytes 2+ carry pixel data per the selected mode.) [1]

2) Why people use “65000” (e.g., Hyperion setting in milliseconds)

In the WLED community, it’s confirmed that setting a realtime timeout to ~65000 ms (e.g., in Hyperion’s UI) can effectively disable the automatic timeout / fallback behavior. [2]

3) JSON API “live” flag (different behavior than UDP timeout)

Via the JSON API, sending {"live": true} forces realtime mode and the configured realtime timeout does not apply; WLED stays in realtime until you change state (color/effect/segments, excluding brightness) or explicitly end it (expected: send {"live": false} when done). [3]

Sources: [1] [2] [3]


@softhack007, I've researched the WLED live mode and realtime control protocols. Here are the findings:

Which protocols use JSON "live": true?

The JSON "live": true command is not protocol-specific. It's a general WLED API command sent via HTTP POST to /json/state that:

  • Forces WLED into realtime mode and blanks the LEDs
  • Can be used by any external tool (Hyperion, Falcon Player, custom scripts) regardless of which actual streaming protocol they use afterward
  • Is independent from the actual realtime data protocols (DDP, E1.31, Hyperion UDP, etc.)

Does any protocol mandate "infinite timeout"?

No! The infinite timeout is purely a WLED implementation choice, not a protocol requirement:

Actual protocol timeout behaviors:

  1. UDP Realtime protocols (WARLS/DRGB/DRGBW/DNRGB):

    • Byte 1 of each packet specifies timeout in seconds
    • Typical values: 1-2 seconds
    • 255 = no timeout (optional, not required)
  2. DDP, E1.31/sACN, Art-Net, Hyperion:

    • All work with WLED's configured timeout (100-65000 ms)
    • Typical usage: 1-2 second timeouts
    • These protocols simply require sending frames more frequently than the timeout interval
  3. JSON "live": true (the problem case):

    • WLED implementation deliberately bypasses the configured realtimetimeout
    • Stays in realtime until you explicitly change state (color/effect/segments) or send "live": false
    • This is not a protocol requirement—it's a design decision

Answer to your question:

A user-defined timeout of a few seconds would absolutely satisfy all protocol needs. The "infinite timeout" when using JSON "live": true is creating the deadlock problem you identified, not solving any protocol requirement.

The root issue is exactly what you suspected: some external tools send "live": true to prepare WLED for streaming but never send "live": false when they shut down, leaving WLED stuck because the normal timeout mechanism is bypassed.

Recommendation: Replacing the infinite behavior with realtimetimeout (or adding a user option to control this) would fix the deadlock without breaking any protocol specifications.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.

Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.

Learnt from: willmmiles
Repo: wled/WLED PR: 4859
File: usermods/udp_name_sync/udp_name_sync.cpp:62-70
Timestamp: 2025-08-21T00:00:17.196Z
Learning: In WLED codebase, UDP packet processing code must never rely on null termination being present on the wire and must always validate packet size first. The codebase already has some vulnerabilities where malformed data can crash the software, so avoiding introduction of new ones is a priority.

Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/data/index.js:2406-2413
Timestamp: 2025-09-28T09:53:42.670Z
Learning: In WLED, code paths that appear unreachable may actually be safety fallbacks for low-heap conditions. These fallbacks are intentional defensive programming measures to ensure UI functionality is preserved when system resources are constrained.

Learnt from: ksedgwic
Repo: wled/WLED PR: 4883
File: usermods/usermod_v2_skystrip/rest_json_client.cpp:40-46
Timestamp: 2025-08-29T00:22:34.115Z
Learning: For ESP32/ESP8266 HTTP requests in WLED, set timeouts on the underlying WiFiClient (using client.setTimeout()) before calling http_.begin(), rather than using HTTPClient::setTimeout(). This pattern is used successfully in the Klipper usermod to prevent connection hangs.

@coderabbitai

This comment was marked as duplicate.

@softhack007
Copy link
Copy Markdown
Member

softhack007 commented Feb 14, 2026

@IsaccoBenedetti maybe the latest discussion / research is a bit off-topic.

Can you explain your use case of the button solution?

  1. wled stuck after live streaming?

  2. you want to abort live streaming while leds are still sent from an external app ? I.e. during live streaming?

  3. I'm not sure which external app you use - could you also stream leds without explicitly switching WLED to "live forever" via JSON API? (wled is automatically switching to live mode when a UDP stream arrives, and uses a short timeout when the stream ends)

@IsaccoBenedetti
Copy link
Copy Markdown
Author

IsaccoBenedetti commented Feb 15, 2026

Hi @softhack007, and thank you for your review!

You're right, maybe I should explain my use case better. Basically, it's one of those classic "wife approval" factors: this wled instance drives a led strip in our living room, which I connected both to a HyperHDR instance (which is this fork of Hyperion: https://github.com/awawa-dev/HyperHDR) and to 2 physical buttons on the wall.
The idea is that the led strip should be used both for normal lighting while also be part of an ambilight system when the tv is on and HyperHDR is transmitting live data (so your #2 scenario, basically).
The issue arised when my GF tried to set the led strip to the "warm white" preset while we were watching a film and told me "wait, why doesn't the switch work anymore?"
I kind of agree with her, because it's my opinion that if there's a physical button and I press it, it should take over anything else, even if HyperHDR is still sending data.
So my idea was ok, I'll add LO=2 (or {"lor":2}, it's the same) to the preset, to disable live override with it (basically the same thing as clicking on "Override Until Reboot" in Wled UI) and create another preset to re-enable it, this way I'll be able to switch on and off the ambilight without having to turn off HyperHDR from the app.
Thing is, because preset logic is guarded by that "if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly))", this wouldn't work.
Adjusting the realtimeTimeout wouldn't solve this. Since the movie is still playing, the stream is active and sending packets every few milliseconds. I don't want to wait for a timeout, I want an immediate manual override via physical button.

To be honest, I feel that since api command LO=2 precise purpose is exactly to break out of realtime mode, any user that adds such a command to a preset will probably expect it to work this way, but I also agree that, since the order of the actions in the main loop is slightly changed now, it may introduce some unexpected consequences, though I haven't noticed any so far. Thus, adding a setting in sync interfaces is probably safer. I can look into it if you'd like, do you have any pointers on how you'd do it, since I'm not familiar with the codebase like you?

Let me know what your thoughts are

@netmindz
Copy link
Copy Markdown
Member

See also #5288 @softhack007

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.

🧹 Nitpick comments (1)
wled00/data/settings_sync.htm (1)

129-130: Reminder: regenerate embedded UI headers after this web UI change.

Please run npm run build before firmware builds so the embedded headers are up to date.
As per coding guidelines: “wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/settings_sync.htm` around lines 129 - 130, You added/modified web
UI elements in wled00/data/settings_sync.htm (checkboxes named "RLM" and "ROP");
regenerate the embedded UI headers by running the project's UI build step and
committing the generated artifacts before doing any firmware build—run `npm run
build` in the repository root to rebuild the embedded headers (so the generated
header files reflect the changes to settings_sync.htm) and include those updated
generated files in the PR.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@wled00/data/settings_sync.htm`:
- Around line 129-130: You added/modified web UI elements in
wled00/data/settings_sync.htm (checkboxes named "RLM" and "ROP"); regenerate the
embedded UI headers by running the project's UI build step and committing the
generated artifacts before doing any firmware build—run `npm run build` in the
repository root to rebuild the embedded headers (so the generated header files
reflect the changes to settings_sync.htm) and include those updated generated
files in the PR.

@IsaccoBenedetti
Copy link
Copy Markdown
Author

Hi, small update, I added the option in sync interfaces as suggested, and also refactored the main loop to avoid changing the order of actions.
I tested it on the same ESP8266 and it seems to be working as expected.
Let me know if you have further suggestions

@IsaccoBenedetti
Copy link
Copy Markdown
Author

Hi @softhack007, just a quick ping on this.

I've been running the new version (with the opt-in setting) on my setup without issues so far. Let me know if there is anything else you'd like me to change or if you need further tests before the merge. I'd be happy to help if I can.

PS: I saw your reference to #5288. While it may seem related, I don't think it actually is: that issue is related to how the JSON API handles automatic timeouts when the stream ends, whereas my PR focuses on manual override while the stream is still active.

Thanks!

@softhack007 softhack007 added this to the 16.0.0 beta milestone Mar 9, 2026
@netmindz netmindz modified the milestones: 16.0.0 beta, 16.1 Mar 28, 2026
@coderabbitai coderabbitai Bot added bug usermod usermod related labels May 30, 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
wled00/button.cpp (1)

370-370: 💤 Low value

Optional: simplify the redundant guard term.

Inside this branch realtimeMode is already known to be true, so !(realtimeMode && useMainSegmentOnly) reduces to !useMainSegmentOnly. (The current form mirrors the De Morgan negation of the main-loop skip condition, which is why the extra realtimeMode && crept in.)

♻️ Proposed simplification
-  if (realtimeMode && !realtimeOverride && !(realtimeMode && useMainSegmentOnly) && realtimeAllowPresets) {
+  if (realtimeMode && !realtimeOverride && !useMainSegmentOnly && realtimeAllowPresets) {
🤖 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 `@wled00/button.cpp` at line 370, The if-condition uses a redundant term:
inside the branch realtimeMode is true, so the subexpression !(realtimeMode &&
useMainSegmentOnly) should be simplified to !useMainSegmentOnly; update the
condition in the if statement that references realtimeMode, realtimeOverride,
useMainSegmentOnly, and realtimeAllowPresets to remove the redundant
realtimeMode && portion (i.e., replace !(realtimeMode && useMainSegmentOnly)
with !useMainSegmentOnly) so the logic is equivalent but clearer.
🤖 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 `@wled00/button.cpp`:
- Around line 368-376: The current comment near the realtime button override
branch is unclear about why handlePresets() is called twice; update the comment
to state that we first apply any queued preset (handlePresets()), then
advance/queue the next preset via handlePlaylist() which sets presetToApply
through applyPresetFromPlaylist(...), and finally call handlePresets() again to
actually apply that newly queued preset — mirroring the handlePresets();
handlePlaylist(); handlePresets(); pattern used for bootPreset in
wled00/wled.cpp; also explicitly note that this presets-first ordering in
realtime mode (when realtimeMode && !realtimeOverride && !useMainSegmentOnly &&
realtimeAllowPresets) is intentional and differs from the main loop order so
playlist advancement cannot override a button-requested preset.

---

Nitpick comments:
In `@wled00/button.cpp`:
- Line 370: The if-condition uses a redundant term: inside the branch
realtimeMode is true, so the subexpression !(realtimeMode && useMainSegmentOnly)
should be simplified to !useMainSegmentOnly; update the condition in the if
statement that references realtimeMode, realtimeOverride, useMainSegmentOnly,
and realtimeAllowPresets to remove the redundant realtimeMode && portion (i.e.,
replace !(realtimeMode && useMainSegmentOnly) with !useMainSegmentOnly) so the
logic is equivalent but clearer.
🪄 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: 50729389-e8c0-4729-a159-4a158ca3927d

📥 Commits

Reviewing files that changed from the base of the PR and between 009dca7 and 7324596.

📒 Files selected for processing (6)
  • wled00/button.cpp
  • wled00/cfg.cpp
  • wled00/data/settings_sync.htm
  • wled00/set.cpp
  • wled00/wled.h
  • wled00/xml.cpp
💤 Files with no reviewable changes (3)
  • wled00/wled.h
  • wled00/set.cpp
  • wled00/xml.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • wled00/cfg.cpp
  • wled00/data/settings_sync.htm

Comment thread wled00/button.cpp
Comment on lines +368 to +376
// If in realtime mode and presets are explicitly allowed to override (e.g., via buttons),
// we process them here to avoid messing with the main loop.
if (realtimeMode && !realtimeOverride && !(realtimeMode && useMainSegmentOnly) && realtimeAllowPresets) {
handlePresets();
if (!presetNeedsSaving()) {
handlePlaylist();
}
handlePresets();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect main loop ordering/guards for handlePresets, handlePlaylist, presetNeedsSaving
rg -nP --type=cpp -C4 '\bhandlePresets\s*\(\)|\bhandlePlaylist\s*\(\)|\bpresetNeedsSaving\s*\(\)' wled00/wled.cpp

# Show handlePresets implementation to assess idempotency / queued-preset behavior
ast-grep --pattern 'void handlePresets() {
  $$$
}'

# Show handlePlaylist implementation (does it queue via applyPreset?)
ast-grep --pattern 'void handlePlaylist() {
  $$$
}'

Repository: wled/WLED

Length of output: 900


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the exact button.cpp block around the reported lines
echo "---- button.cpp context (lines ~340-410) ----"
nl -ba wled00/button.cpp | sed -n '340,410p'

# 2) Find definitions/signatures for handlePresets/handlePlaylist
echo "---- locate handlePresets definition ----"
rg -n --type=cpp '\bhandlePresets\s*\(' wled00 -g'*.cpp'

echo "---- locate handlePlaylist definition ----"
rg -n --type=cpp '\bhandlePlaylist\s*\(' wled00 -g'*.cpp'

# 3) Show main loop and bootPreset block rationale in wled.cpp
echo "---- wled.cpp context around first preset/playlist section ----"
nl -ba wled00/wled.cpp | sed -n '80,170p'

echo "---- wled.cpp context around presetNeedsSaving / handlePlaylist / handlePresets ----"
nl -ba wled00/wled.cpp | sed -n '360,440p'

echo "---- wled.cpp context around bootPreset > 0 block ----"
nl -ba wled00/wled.cpp | sed -n '470,530p'

# 4) Print handlePresets/handlePlaylist implementations once located
#    (Use the first definition found per function to keep output bounded.)
HP_LINE=$(rg -n --type=cpp -m1 '\bhandlePresets\s*\(' wled00 -g'*.cpp' | cut -d: -f1)
HP_FILE=$(rg -n --type=cpp -m1 '\bhandlePresets\s*\(' wled00 -g'*.cpp' | cut -d: -f1 --output-separator='' | true)
# Can't reliably extract file via cut above because ripgrep output includes "file:line:..."
# Re-run with stable parsing:
HP_MATCH=$(rg -n --type=cpp -m1 '\bhandlePresets\s*\(' wled00 -g'*.cpp')
HP_FILE=$(echo "$HP_MATCH" | cut -d: -f1)
HP_LINE=$(echo "$HP_MATCH" | cut -d: -f2)

HL_MATCH=$(rg -n --type=cpp -m1 '\bhandlePlaylist\s*\(' wled00 -g'*.cpp')
HL_FILE=$(echo "$HL_MATCH" | cut -d: -f1)
HL_LINE=$(echo "$HL_MATCH" | cut -d: -f2)

echo "---- handlePresets implementation snapshot: ${HP_FILE}:${HP_LINE} ----"
nl -ba "$HP_FILE" | sed -n "$((HP_LINE-20)),$((HP_LINE+60))p"

echo "---- handlePlaylist implementation snapshot: ${HL_FILE}:${HL_LINE} ----"
nl -ba "$HL_FILE" | sed -n "$((HL_LINE-20)),$((HL_LINE+60))p"

Repository: wled/WLED

Length of output: 160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- button.cpp context (lines ~350-390) ----"
cat -n wled00/button.cpp | sed -n '350,390p'

echo "---- locate handlePresets definition ----"
rg -n --type=cpp '\bhandlePresets\s*\(' wled00 -g'*.cpp'

echo "---- locate handlePlaylist definition ----"
rg -n --type=cpp '\bhandlePlaylist\s*\(' wled00 -g'*.cpp'

echo "---- wled.cpp context around presetNeedsSaving / handlePlaylist / handlePresets ----"
cat -n wled00/wled.cpp | sed -n '80,160p'

echo "---- wled.cpp context around bootPreset > 0 block ----"
cat -n wled00/wled.cpp | sed -n '480,535p'

Repository: wled/WLED

Length of output: 8145


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- presets.cpp: handlePresets() implementation (around line ~145) ----"
cat -n wled00/presets.cpp | sed -n '110,240p'

echo "---- playlist.cpp: handlePlaylist() implementation (around line ~151) ----"
cat -n wled00/playlist.cpp | sed -n '120,240p'

echo "---- locate presetNeedsSaving() definition ----"
rg -n --type=cpp '\bpresetNeedsSaving\s*\(' wled00/presets.cpp wled00/*.cpp

echo "---- print region around presetNeedsSaving() definition (if found) ----"
PRESET_NEEDS_LINE=$(rg -n --type=cpp 'presetNeedsSaving\s*\(' wled00/presets.cpp | head -n1 | cut -d: -f2 || true)
PRESET_NEEDS_FILE=$(rg -n --type=cpp 'presetNeedsSaving\s*\(' wled00/presets.cpp | head -n1 | cut -d: -f1 || true)
if [[ -n "${PRESET_NEEDS_LINE:-}" && -n "${PRESET_NEEDS_FILE:-}" ]]; then
  cat -n "$PRESET_NEEDS_FILE" | sed -n "$((PRESET_NEEDS_LINE-30)),$((PRESET_NEEDS_LINE+60))p"
else
  echo "presetNeedsSaving() definition not found in presets.cpp (showing call sites only)."
fi

Repository: wled/WLED

Length of output: 10629


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- presets.cpp: presetNeedsSaving() definition (top of file) ----"
cat -n wled00/presets.cpp | sed -n '1,120p'

echo "---- presets.cpp: call sites where presetNeedsSaving() gates behavior ----"
rg -n --type=cpp '\bpresetNeedsSaving\s*\(' wled00 -g'*.cpp' -g'*.h'

Repository: wled/WLED

Length of output: 4701


Clarify the intent of the double handlePresets() call in realtime button override

  • handlePlaylist() does not apply the next preset directly; it queues one by setting presetToApply via applyPresetFromPlaylist(...), so the second handlePresets() is required to actually apply that queued preset.
  • Update the comment in wled00/button.cpp (368-376) to describe the sequencing (“apply queued preset → optionally advance playlist/queue next preset → apply again”), matching the same handlePresets(); handlePlaylist(); handlePresets(); pattern used for bootPreset > 0 in wled00/wled.cpp.
  • Confirm this presets-first ordering in realtime mode is intentionally different from the normal main loop (handlePlaylist() then handlePresets()), so playlist advancement can’t override the button preset when realtime override is enabled.
🤖 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 `@wled00/button.cpp` around lines 368 - 376, The current comment near the
realtime button override branch is unclear about why handlePresets() is called
twice; update the comment to state that we first apply any queued preset
(handlePresets()), then advance/queue the next preset via handlePlaylist() which
sets presetToApply through applyPresetFromPlaylist(...), and finally call
handlePresets() again to actually apply that newly queued preset — mirroring the
handlePresets(); handlePlaylist(); handlePresets(); pattern used for bootPreset
in wled00/wled.cpp; also explicitly note that this presets-first ordering in
realtime mode (when realtimeMode && !realtimeOverride && !useMainSegmentOnly &&
realtimeAllowPresets) is intentional and differs from the main loop order so
playlist advancement cannot override a button-requested preset.

@IsaccoBenedetti
Copy link
Copy Markdown
Author

Hi, I had a look at PR #5573 and noticed this very useful suggestion from @smitty078 : #5573 (comment)

I decided to implement it the way he suggested and I can confirm it's working as expected, without any need to interfere with the main loop.

Thanks @smitty078 !

I kept the option in the sync interface section for now, but I feel at this point it should be safe to remove it completely.

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

Labels

bug usermod usermod related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants