Skip to content

Net debug - Runtime Config#5559

Draft
netmindz wants to merge 4 commits intowled:mainfrom
netmindz:net-debug
Draft

Net debug - Runtime Config#5559
netmindz wants to merge 4 commits intowled:mainfrom
netmindz:net-debug

Conversation

@netmindz
Copy link
Copy Markdown
Member

@netmindz netmindz commented May 3, 2026

This pull request introduces significant improvements to the WLED network debug (Net Debug) system, shifting from hostname-based configuration to direct IP address and port configuration, and making Net Debug settings user-configurable through the web UI and JSON config. It also unifies and simplifies debug macro usage throughout the codebase.

Key changes:

Net Debug System Overhaul

  • Replaces the previous WLED_DEBUG_HOST (hostname-based) configuration with a new WLED_ENABLE_NET_DEBUG system that uses a direct IP address (netDebugPrintIP) and port (netDebugPrintPort) for UDP debug output, improving reliability and flexibility. Legacy WLED_DEBUG_HOST support is maintained for backward compatibility. [1] [2] [3] [4]
  • Updates the Net Debug UDP output logic to always use the configured IP and port, removing runtime DNS resolution and related member variables. [1] [2] [3]
  • Initializes Net Debug IP and port with sensible defaults if not set, using compile-time or runtime network information.

Debug Macro and Output Refactoring

  • Refactors all debug output macros (DEBUG_PRINT, DEBUGFX_PRINT, DEBUGBUS_PRINT, etc.) to use unified function-style macros (DEBUGOUT(x), DEBUGOUTLN(x), DEBUGOUTF(x...)), simplifying the code and ensuring consistent debug output routing. [1] [2] [3] [4]
  • Adjusts conditional logic throughout the codebase to use WLED_ENABLE_NET_DEBUG instead of WLED_DEBUG_HOST, clarifying feature toggling and pin allocation. [1] [2] [3]

Configuration and Web UI Enhancements

  • Adds Net Debug IP and port fields to the settings web UI (settings_sync.htm), enabling users to configure network debug output directly from the browser.
  • Serializes and deserializes Net Debug IP and port in the JSON configuration, ensuring settings persist across reboots and can be managed via config files or API. [1] [2] [3] [4]

JSON API and Info Output

  • Updates JSON API and info output to reflect the new Net Debug system, including reporting Net Debug status and options correctly. [1] [2] [3]

These changes modernize and robustify the Net Debug system, make debug output more maintainable, and improve user configurability and transparency.

Summary by CodeRabbit

Release Notes

  • Refactor

    • Consolidated and simplified debug output macro implementations across core components
  • New Features

    • Network debug configuration now available in Sync Settings, enabling users to configure network debug output with custom IP address (four octets) and port number
  • UI Changes

    • Debug interface labels renamed from "Debug" to "Net Debug" throughout the settings interface

ewoudwijma and others added 3 commits May 3, 2026 11:27
Cherry-picked from MoonModules/WLED-MM@bb6662ee3.

- cfg.cpp, set.cpp, wled.cpp, xml.cpp: persist netDebugPrintIP and
  netDebugPrintPort in config and settings
- net_debug.h/cpp: use stored IP/port instead of compile-time host;
  init moved to initInterfaces
- wled.h: DEBUGOUT/DEBUGOUTLN/DEBUGOUTF/DEBUGOUTFlush macros route to
  NetDebug or Serial based on netDebugEnabled; USER_PRINT* macros added
- data/settings_sync.htm: Net Debug IP/port fields in Sync settings
- data/index.js: relabel debug link as 'Net Debug'
- bus_manager.h, FX.h: remove local DEBUGOUT object alias; update
  DEBUGBUS_*/DEBUGFX_* macros to use DEBUGOUT()/DEBUGOUTLN()/DEBUGOUTF()
- usermods/mpu6050_imu: update local DEBUG_* redefinitions to match
- platformio.ini: retain only this branch's usermods env additions;
  discard all MM-specific environments
- usermods/audioreactive/audio_reactive.cpp: no changes (kept HEAD)
…HOST as guard

WLED_DEBUG_HOST was used both as a feature-enable guard and as a
compile-time IP/hostname string, which was backwards — you had to
supply a host at build time just to enable a feature whose whole point
is runtime configuration.

Replace all preprocessor guards with WLED_ENABLE_NET_DEBUG, following
the existing WLED_ENABLE_* convention. A compat shim in wled.h
automatically defines WLED_ENABLE_NET_DEBUG when WLED_DEBUG_HOST is
set, so existing build configs continue to work. The string value of
WLED_DEBUG_HOST (compile-time default IP) is still honoured where used.

Files changed: wled.h, wled.cpp, cfg.cpp, set.cpp, xml.cpp, json.cpp,
net_debug.cpp
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Walkthrough

This PR refactors the network debug system by replacing the WLED_DEBUG_HOST compilation guard with a dedicated WLED_ENABLE_NET_DEBUG flag. Debug output macros across multiple files are rewired to use function-style calls instead of method calls, public net-debug globals are reorganized from hostname-based to IP/port configuration, and configuration persistence and UI controls are added for the network debug destination.

Changes

Network Debug System Refactor

Layer / File(s) Summary
Public API Reorganization
wled00/wled.h
Introduces netDebugEnabled, netDebugPrintPort, and netDebugPrintIP globals (replacing netDebugPrintHost). Switches macro routing strategy from host-based selection to WLED_ENABLE_NET_DEBUG-gated routing. Routes DEBUGOUT(...), DEBUGOUTLN(...), DEBUGOUTF(...) to NetDebug when enabled, otherwise Serial.
Debug Macro Refactoring
wled00/FX.h, wled00/bus_manager.h, usermods/mpu6050_imu/mpu6050_imu.cpp
Changes DEBUGFX_* and DEBUGBUS_* macro implementations from DEBUGOUT.print/println/printf method calls to DEBUGOUT(...), DEBUGOUTLN(...), DEBUGOUTF(...) function-style forwarding.
Network Debug Implementation
wled00/net_debug.cpp, wled00/net_debug.h
Moves compilation guard from WLED_DEBUG_HOST to WLED_ENABLE_NET_DEBUG. Removes hostname resolution logic and debugPrintHostIP member. UDP packet sending now directly uses netDebugPrintIP and netDebugPrintPort.
Initialization & Boot Logic
wled00/wled.cpp
Updates CDC USB debug delay and debug TX pin reservation to use WLED_ENABLE_NET_DEBUG guard. Adds initInterfaces() initialization block to resolve WLED_DEBUG_HOST hostname (if set) to IP or fall back to local IP, and configure netDebugPrintPort.
Configuration Persistence
wled00/cfg.cpp, wled00/json.cpp
Adds deserialization of interfaces["ndb"] object (IP octets and port) and serialization back to config JSON under WLED_ENABLE_NET_DEBUG guard. Updates json.cpp net-debug guards to WLED_ENABLE_NET_DEBUG.
Settings & Web UI
wled00/set.cpp, wled00/data/settings_sync.htm, wled00/data/index.js
Adds HTTP request handler to parse N0N3 (IP octets) and NP (port) into netDebugPrintIP/netDebugPrintPort. Adds "Net Debug ☾" UI section to sync settings with conditional blocks and form inputs. Renames debug label in info popup from "Debug" to "Net Debug".
Conditional Compilation Gates
wled00/xml.cpp
Updates debug TX pin reservation condition from !defined(WLED_DEBUG_HOST) to !defined(WLED_ENABLE_NET_DEBUG). Adds settings JavaScript output of netDebugPrintIP and netDebugPrintPort when WLED_ENABLE_NET_DEBUG is defined.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% 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 'Net debug - Runtime Config' directly aligns with the PR's main objective to enable runtime configuration of Net Debug settings and expose them via the web UI and JSON config.
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.

@coderabbitai coderabbitai Bot added enhancement connectivity Issue regarding protocols, WiFi connection or availability of interfaces labels May 3, 2026
@softhack007
Copy link
Copy Markdown
Member

While adding new debug macros - how about also adding a debug statement that is always printed, like WLED-MM USER_PRINTxx() ?

The idea would be that some errors (unexpected situations like "cannot allocate pin", "file not found", "invalid cfg.json", etc) could always be logged on serial, in case that serial is not allocated to leds or used for other protocols. Or via net debug (UDP) if enabled.

https://github.com/MoonModules/WLED-MM/blob/459a66491d7a3993ef456eb5cbfaad4c9d806f3b/wled00/wled.h#L885

https://github.com/MoonModules/WLED-MM/blob/459a66491d7a3993ef456eb5cbfaad4c9d806f3b/wled00/wled_serial.cpp#L90

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: 5

🧹 Nitpick comments (1)
wled00/net_debug.h (1)

10-10: ⚡ Quick win

Remove the stale commented member declaration.

This commented field is dead code now and should be deleted to avoid confusion in the class layout.

♻️ Suggested cleanup
-    // IPAddress debugPrintHostIP;

As per coding guidelines: **/*.{cpp,h,hpp,js}: Remove dead/unused code — justify or delete it.

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

In `@wled00/net_debug.h` at line 10, Remove the stale commented member declaration
"IPAddress debugPrintHostIP;" from net_debug.h; locate the commented line and
delete it so the header no longer contains dead/unused commented field, leaving
no other changes to the class layout or surrounding comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/cfg.cpp`:
- Around line 660-666: When deserializing the "ndb" interface, validate the
parsed netDebugPrintPort from interfaces["ndb"] (the if_ndb JsonObject) before
accepting it: ensure the value is within 0..65535 and is non-negative; if it's
out of range or not an integer, either clamp to a safe default (e.g. 0 or
previously configured netDebugPrintPort) and emit a warning, or reject it and
leave netDebugPrintPort unchanged. Update the assignment that currently takes
if_ndb["port"] directly to perform this range check and explicit conversion to
an unsigned 16-bit type so downstream UDP calls use a safe port value.

In `@wled00/data/settings_sync.htm`:
- Line 299: The Net Debug port input (input name="NP") allows values up to 99999
which exceeds the valid UDP port range; update the input element for NP to
constrain values to the valid port range by setting min="1" and max="65535" (and
keep type="number") so the browser enforces only 1..65535 can be submitted.

In `@wled00/set.cpp`:
- Around line 526-532: The net debug block currently unconditionally overwrites
netDebugPrintIP[] and netDebugPrintPort which can reset them to 0 when args are
missing or invalid; change the logic to first check for the presence and
validity of each argument from request (e.g. for each "N"+String(i) call
request->hasArg(...) or test that request->arg(...) is non-empty and numeric)
before assigning netDebugPrintIP[i], and likewise verify request->hasArg("NP")
and that its value is a valid port number before assigning netDebugPrintPort so
existing settings are preserved when args are absent/invalid.

In `@wled00/wled.cpp`:
- Around line 830-855: Wrap usage of WLED_DEBUG_HOST in an `#ifdef`
WLED_DEBUG_HOST block and invert the fromString logic so a successful parse
keeps the configured host IP; specifically, inside the netDebugPrintIP
initialization (symbol: netDebugPrintIP and its method fromString) attempt
netDebugPrintIP.fromString(WLED_DEBUG_HOST) only when WLED_DEBUG_HOST is defined
and if that call returns true do not overwrite netDebugPrintIP, otherwise fall
back to Network.localIP(); when assigning the fallback IP from Network.localIP()
(symbol: Network::localIP or Network.localIP) copy all four octets (0..3) into
netDebugPrintIP so the fourth byte is not left uninitialized; leave the
netDebugPrintPort defaulting logic as-is but ensure the
WLED_DEBUG_HOST-dependent code is guarded by `#ifdef/`#endif.

In `@wled00/wled.h`:
- Around line 990-998: The DEBUGOUT macros were changed to function-style calls
which breaks sites expecting a stream object (e.g., DEBUGSR_PRINT macros that
call DEBUGOUT.print()/println()/printf() and server.printStatus(DEBUGOUT));
restore stream-style usage by adding a helper that returns a Print& (e.g.,
implement inline Print& debugOutStream() that returns NetDebug or Serial based
on netDebugEnabled and build flag), expose a macro like DEBUGOUT_STREAM() that
expands to debugOutStream(), and then redefine
DEBUGOUT/DEBUGOUTLN/DEBUGOUTF/DEBUGOUTFlush to call
DEBUGOUT_STREAM().print/println/printf/flush while leaving DEBUGOUT_STREAM()
available for passing into functions such as printStatus and the
DEBUGSR_PRINT/DEBUGSR_PRINTLN/DEBUGSR_PRINTF sites so both function-call and
stream-object patterns work.

---

Nitpick comments:
In `@wled00/net_debug.h`:
- Line 10: Remove the stale commented member declaration "IPAddress
debugPrintHostIP;" from net_debug.h; locate the commented line and delete it so
the header no longer contains dead/unused commented field, leaving no other
changes to the class layout or surrounding comments.
🪄 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: f31603f6-4021-4a4d-8dad-1c9e995dba40

📥 Commits

Reviewing files that changed from the base of the PR and between 47af195 and 30d1617.

📒 Files selected for processing (13)
  • usermods/mpu6050_imu/mpu6050_imu.cpp
  • wled00/FX.h
  • wled00/bus_manager.h
  • wled00/cfg.cpp
  • wled00/data/index.js
  • wled00/data/settings_sync.htm
  • wled00/json.cpp
  • wled00/net_debug.cpp
  • wled00/net_debug.h
  • wled00/set.cpp
  • wled00/wled.cpp
  • wled00/wled.h
  • wled00/xml.cpp

Comment thread wled00/cfg.cpp
Comment on lines +660 to +666
#ifdef WLED_ENABLE_NET_DEBUG
JsonObject if_ndb = interfaces["ndb"];
JsonArray if_ndb_ip = if_ndb["ip"];
for (byte i = 0; i < 4; i++)
CJSON(netDebugPrintIP[i], if_ndb_ip[i]);
CJSON(netDebugPrintPort, if_ndb["port"]);
#endif
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

Validate netDebugPrintPort range on deserialize.

netDebugPrintPort is accepted directly from JSON here. A negative or out-of-range value can propagate into UDP calls after integer conversion.

💡 Suggested patch
 `#ifdef` WLED_ENABLE_NET_DEBUG
   JsonObject if_ndb = interfaces["ndb"];
   JsonArray if_ndb_ip = if_ndb["ip"];
   for (byte i = 0; i < 4; i++)
     CJSON(netDebugPrintIP[i], if_ndb_ip[i]);
   CJSON(netDebugPrintPort, if_ndb["port"]);
+  if (netDebugPrintPort < 0 || netDebugPrintPort > 65535) netDebugPrintPort = 0; // fallback handled during interface init
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/cfg.cpp` around lines 660 - 666, When deserializing the "ndb"
interface, validate the parsed netDebugPrintPort from interfaces["ndb"] (the
if_ndb JsonObject) before accepting it: ensure the value is within 0..65535 and
is non-negative; if it's out of range or not an integer, either clamp to a safe
default (e.g. 0 or previously configured netDebugPrintPort) and emit a warning,
or reject it and leave netDebugPrintPort unchanged. Update the assignment that
currently takes if_ndb["port"] directly to perform this range check and explicit
conversion to an unsigned 16-bit type so downstream UDP calls use a safe port
value.

<input name="N2" type="number" class="s" min="0" max="255" > .
<input name="N3" type="number" class="s" min="0" max="255" ><br>
Netcat host Port:<br>
<input name="NP" type="number" min="0" max="99999" ><br>
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

Constrain Net Debug port to valid UDP range in the form.

Line 299 allows 99999, but valid port values are 1..65535.

🐛 Suggested fix
-<input name="NP" type="number" min="0" max="99999" ><br>
+<input name="NP" type="number" min="1" max="65535" ><br>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<input name="NP" type="number" min="0" max="99999" ><br>
<input name="NP" type="number" min="1" max="65535" ><br>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/data/settings_sync.htm` at line 299, The Net Debug port input (input
name="NP") allows values up to 99999 which exceeds the valid UDP port range;
update the input element for NP to constrain values to the valid port range by
setting min="1" and max="65535" (and keep type="number") so the browser enforces
only 1..65535 can be submitted.

Comment thread wled00/set.cpp
Comment on lines +526 to +532
#ifdef WLED_ENABLE_NET_DEBUG
for (int i=0;i<4;i++){
String a = "N"+String(i);
netDebugPrintIP[i] = request->arg(a).toInt();
}
netDebugPrintPort = request->arg("NP").toInt();
#endif
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

Guard and validate Net Debug fields before applying them.

Line 529 and Line 531 currently overwrite values even when args are absent/invalid, which can silently reset settings to 0 on partial submissions.

🐛 Suggested hardening
 `#ifdef` WLED_ENABLE_NET_DEBUG
-      for (int i=0;i<4;i++){
-        String a = "N"+String(i);
-        netDebugPrintIP[i] = request->arg(a).toInt();
-      }
-      netDebugPrintPort = request->arg("NP").toInt();
+      for (int i = 0; i < 4; i++) {
+        String a = "N" + String(i);
+        if (request->hasArg(a)) {
+          netDebugPrintIP[i] = (uint8_t)constrain(request->arg(a).toInt(), 0, 255);
+        }
+      }
+      if (request->hasArg(F("NP"))) {
+        int np = request->arg(F("NP")).toInt();
+        if (np > 0 && np <= 65535) netDebugPrintPort = np;
+      }
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/set.cpp` around lines 526 - 532, The net debug block currently
unconditionally overwrites netDebugPrintIP[] and netDebugPrintPort which can
reset them to 0 when args are missing or invalid; change the logic to first
check for the presence and validity of each argument from request (e.g. for each
"N"+String(i) call request->hasArg(...) or test that request->arg(...) is
non-empty and numeric) before assigning netDebugPrintIP[i], and likewise verify
request->hasArg("NP") and that its value is a valid port number before assigning
netDebugPrintPort so existing settings are preserved when args are
absent/invalid.

Comment thread wled00/wled.cpp
Comment on lines +830 to +855
#ifdef WLED_ENABLE_NET_DEBUG
if (netDebugPrintIP[0] == 0) {
if (!netDebugPrintIP && !netDebugPrintIP.fromString(WLED_DEBUG_HOST)) {
#ifdef ESP8266
WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP, 750);
#else
#ifdef WLED_USE_ETHERNET
ETH.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
#else
WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
#endif
#endif
} else {
IPAddress ipAddress = Network.localIP();
netDebugPrintIP[0] = ipAddress[0];
netDebugPrintIP[1] = ipAddress[1];
netDebugPrintIP[2] = ipAddress[2];
}
}
if (netDebugPrintPort == 0)
#ifdef WLED_DEBUG_PORT
netDebugPrintPort = WLED_DEBUG_PORT;
#else
netDebugPrintPort = 7868; //Default value
#endif
#endif
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== WLED_DEBUG_HOST define presence =="
rg -n --type=cpp --type=h '^\s*#\s*define\s+WLED_DEBUG_HOST\b|^\s*#\s*ifndef\s+WLED_DEBUG_HOST\b'

echo
echo "== netDebug init block =="
rg -n -C4 'netDebugPrintIP|WLED_DEBUG_HOST|netDebugPrintPort' wled00/wled.cpp

echo
echo "== netDebugPrintIP octet assignments in wled.cpp =="
rg -n 'netDebugPrintIP\[[0-3]\]\s*=' wled00/wled.cpp

Repository: wled/WLED

Length of output: 91


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Lines 830-855 in wled00/wled.cpp ==="
sed -n '830,855p' wled00/wled.cpp

echo
echo "=== Context around netDebugPrintIP declaration ==="
rg -n 'netDebugPrintIP' wled00/wled.cpp | head -20

echo
echo "=== IPAddress class definition ==="
fd -t f '\.h$' -x grep -l 'class IPAddress' {} \;

echo
echo "=== Check if WLED_DEBUG_HOST is defined anywhere in project ==="
rg -r '\.{1,2}' --type=cpp --type=h 'define.*WLED_DEBUG_HOST|WLED_DEBUG_HOST\s*=' . 2>/dev/null || true

Repository: wled/WLED

Length of output: 1432


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for WLED_DEBUG or DEBUG_HOST in config files ==="
find . -type f \( -name "*.h" -o -name "*.cpp" -o -name "*.json" -o -name "*.md" \) -exec grep -l "DEBUG_HOST\|DEBUG_PORT" {} \; 2>/dev/null || true

echo
echo "=== Check wled.h for netDebugPrintIP declaration ==="
grep -n 'netDebugPrintIP\|IPAddress' wled00/wled.h | head -20

echo
echo "=== Look for IPAddress usage with bracket notation ==="
rg -n 'IPAddress.*\[[0-3]\]' wled00/ | head -10

echo
echo "=== Check default values or config setup ==="
rg -n 'WLED_DEBUG_HOST|netDebugPrintIP' wled00/set.cpp | head -20

Repository: wled/WLED

Length of output: 825


Net-debug init has compile-time dependency, inverted logic, and incomplete IP assignment bugs.

Line 832 references WLED_DEBUG_HOST without #ifdef WLED_DEBUG_HOST guard despite it being undefined in the codebase. The condition if (!netDebugPrintIP && !netDebugPrintIP.fromString(WLED_DEBUG_HOST)) inverts the fallback logic: when fromString() succeeds, the else branch overwrites it with the local IP instead of keeping the parsed value. Additionally, the local IP fallback (lines 844–846) assigns only octets 0–2, leaving octet 3 unset when IPAddress requires all 4 octets.

Suggested fix
 `#ifdef` WLED_ENABLE_NET_DEBUG
-  if (netDebugPrintIP[0] == 0) {
-    if (!netDebugPrintIP && !netDebugPrintIP.fromString(WLED_DEBUG_HOST)) {
+  bool netDebugIpUnset = (netDebugPrintIP[0] == 0 && netDebugPrintIP[1] == 0 && netDebugPrintIP[2] == 0 && netDebugPrintIP[3] == 0);
+  if (netDebugIpUnset) {
+    `#ifdef` WLED_DEBUG_HOST
+    if (!netDebugPrintIP.fromString(WLED_DEBUG_HOST)) {
       `#ifdef` ESP8266
         WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP, 750);
       `#else`
         `#ifdef` WLED_USE_ETHERNET
           ETH.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
         `#else`
           WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
         `#endif`
       `#endif`
-    } else {
-      IPAddress ipAddress = Network.localIP();
-      netDebugPrintIP[0] = ipAddress[0];
-      netDebugPrintIP[1] = ipAddress[1];
-      netDebugPrintIP[2] = ipAddress[2];
     }
+    `#else`
+    IPAddress ipAddress = Network.localIP();
+    netDebugPrintIP[0] = ipAddress[0];
+    netDebugPrintIP[1] = ipAddress[1];
+    netDebugPrintIP[2] = ipAddress[2];
+    netDebugPrintIP[3] = ipAddress[3];
+    `#endif`
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#ifdef WLED_ENABLE_NET_DEBUG
if (netDebugPrintIP[0] == 0) {
if (!netDebugPrintIP && !netDebugPrintIP.fromString(WLED_DEBUG_HOST)) {
#ifdef ESP8266
WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP, 750);
#else
#ifdef WLED_USE_ETHERNET
ETH.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
#else
WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
#endif
#endif
} else {
IPAddress ipAddress = Network.localIP();
netDebugPrintIP[0] = ipAddress[0];
netDebugPrintIP[1] = ipAddress[1];
netDebugPrintIP[2] = ipAddress[2];
}
}
if (netDebugPrintPort == 0)
#ifdef WLED_DEBUG_PORT
netDebugPrintPort = WLED_DEBUG_PORT;
#else
netDebugPrintPort = 7868; //Default value
#endif
#endif
`#ifdef` WLED_ENABLE_NET_DEBUG
bool netDebugIpUnset = (netDebugPrintIP[0] == 0 && netDebugPrintIP[1] == 0 && netDebugPrintIP[2] == 0 && netDebugPrintIP[3] == 0);
if (netDebugIpUnset) {
`#ifdef` WLED_DEBUG_HOST
if (!netDebugPrintIP.fromString(WLED_DEBUG_HOST)) {
`#ifdef` ESP8266
WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP, 750);
`#else`
`#ifdef` WLED_USE_ETHERNET
ETH.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
`#else`
WiFi.hostByName(WLED_DEBUG_HOST, netDebugPrintIP);
`#endif`
`#endif`
}
`#else`
IPAddress ipAddress = Network.localIP();
netDebugPrintIP[0] = ipAddress[0];
netDebugPrintIP[1] = ipAddress[1];
netDebugPrintIP[2] = ipAddress[2];
netDebugPrintIP[3] = ipAddress[3];
`#endif`
}
if (netDebugPrintPort == 0)
`#ifdef` WLED_DEBUG_PORT
netDebugPrintPort = WLED_DEBUG_PORT;
`#else`
netDebugPrintPort = 7868; //Default value
`#endif`
`#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/wled.cpp` around lines 830 - 855, Wrap usage of WLED_DEBUG_HOST in an
`#ifdef` WLED_DEBUG_HOST block and invert the fromString logic so a successful
parse keeps the configured host IP; specifically, inside the netDebugPrintIP
initialization (symbol: netDebugPrintIP and its method fromString) attempt
netDebugPrintIP.fromString(WLED_DEBUG_HOST) only when WLED_DEBUG_HOST is defined
and if that call returns true do not overwrite netDebugPrintIP, otherwise fall
back to Network.localIP(); when assigning the fallback IP from Network.localIP()
(symbol: Network::localIP or Network.localIP) copy all four octets (0..3) into
netDebugPrintIP so the fourth byte is not left uninitialized; leave the
netDebugPrintPort defaulting logic as-is but ensure the
WLED_DEBUG_HOST-dependent code is guarded by `#ifdef/`#endif.

Comment thread wled00/wled.h
Comment on lines +990 to +998
#define DEBUGOUT(x) netDebugEnabled?NetDebug.print(x):Serial.print(x)
#define DEBUGOUTLN(x) netDebugEnabled?NetDebug.println(x):Serial.println(x)
#define DEBUGOUTF(x...) netDebugEnabled?NetDebug.printf(x):Serial.printf(x)
#define DEBUGOUTFlush() netDebugEnabled?NetDebug.flush():Serial.flush()
#else
#define DEBUGOUT Serial
#define DEBUGOUT(x) Serial.print(x)
#define DEBUGOUTLN(x) Serial.println(x)
#define DEBUGOUTF(x...) Serial.printf(x)
#define DEBUGOUTFlush() Serial.flush()
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 3, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Object-style DEBUGOUT member calls (incompatible with function-like DEBUGOUT) =="
rg -nP --type=cpp --type=h '\bDEBUGOUT\s*\.\s*(print|println|printf|flush)\s*\('

echo
echo "== Bare DEBUGOUT identifier usages in affected areas =="
rg -nP '\bDEBUGOUT\b(?!\s*\()' wled00/wled.cpp usermods/audioreactive/audio_reactive.cpp

Repository: wled/WLED

Length of output: 1346


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the exact content of wled.h around lines 990-998
echo "=== wled.h lines 980-1010 ==="
sed -n '980,1010p' wled00/wled.h | cat -n

echo
echo "=== Check if there are any wrapper functions for stream compatibility ==="
rg -nA5 'debugOutStream|DEBUGOUT_STREAM|Print.*DEBUGOUT' wled00/wled.h

echo
echo "=== Verify the exact definitions of DEBUGOUT-related macros ==="
rg -nP '^\s*#define\s+(DEBUGOUT|DEBUGSR_|PLOT_)' wled00/wled.h usermods/audioreactive/audio_reactive.cpp | head -20

Repository: wled/WLED

Length of output: 1519


DEBUGOUT API change breaks existing stream-style call sites.

Making DEBUGOUT a function-style macro breaks code that uses it as a stream object. usermods/audioreactive/audio_reactive.cpp:55-67 defines DEBUGSR_PRINT, DEBUGSR_PRINTLN, and DEBUGSR_PRINTF using DEBUGOUT.print(), DEBUGOUT.println(), and DEBUGOUT.printf() syntax. Additionally, wled00/wled.cpp:323 calls server.printStatus(DEBUGOUT), passing DEBUGOUT as a stream argument. These become compile-time errors under the new macro definition.

Consider using a wrapper function that returns a reference to the appropriate stream, allowing both function-call and stream-object usage patterns:

`#if` defined(WLED_ENABLE_NET_DEBUG)
  inline Print& debugOutStream() { return netDebugEnabled ? static_cast<Print&>(NetDebug) : static_cast<Print&>(Serial); }
`#else`
  inline Print& debugOutStream() { return static_cast<Print&>(Serial); }
`#endif`
`#define` DEBUGOUT_STREAM() debugOutStream()
`#define` DEBUGOUT(x) DEBUGOUT_STREAM().print(x)
`#define` DEBUGOUTLN(x) DEBUGOUT_STREAM().println(x)
`#define` DEBUGOUTF(x...) DEBUGOUT_STREAM().printf(x)
`#define` DEBUGOUTFlush() DEBUGOUT_STREAM().flush()

Then migrate stream-argument call sites (e.g., printStatus(...)) to use DEBUGOUT_STREAM().

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

In `@wled00/wled.h` around lines 990 - 998, The DEBUGOUT macros were changed to
function-style calls which breaks sites expecting a stream object (e.g.,
DEBUGSR_PRINT macros that call DEBUGOUT.print()/println()/printf() and
server.printStatus(DEBUGOUT)); restore stream-style usage by adding a helper
that returns a Print& (e.g., implement inline Print& debugOutStream() that
returns NetDebug or Serial based on netDebugEnabled and build flag), expose a
macro like DEBUGOUT_STREAM() that expands to debugOutStream(), and then redefine
DEBUGOUT/DEBUGOUTLN/DEBUGOUTF/DEBUGOUTFlush to call
DEBUGOUT_STREAM().print/println/printf/flush while leaving DEBUGOUT_STREAM()
available for passing into functions such as printStatus and the
DEBUGSR_PRINT/DEBUGSR_PRINTLN/DEBUGSR_PRINTF sites so both function-call and
stream-object patterns work.

Copy link
Copy Markdown
Member

@softhack007 softhack007 May 3, 2026

Choose a reason for hiding this comment

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

DEBUGSR_ macros in audioreactive UM should be adjusted. It's not necessary to have a legacy support helper only for this usermod.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== DEBUGSR_ macros in audioreactive ==="
rg -nP '#define\s+DEBUGSR_' usermods/audioreactive/audio_reactive.cpp

echo
echo "=== wled.cpp printStatus call ==="
rg -n 'printStatus' wled00/wled.cpp

Length of output: 363


@softhack007, understood — the DEBUGSR_ macros in the audioreactive usermod just need to be updated to use the new function-style macros directly. Here's the fix:

// usermods/audioreactive/audio_reactive.cpp, lines 55-57
`#define` DEBUGSR_PRINT(x)    DEBUGOUT(x)
`#define` DEBUGSR_PRINTLN(x)  DEBUGOUTLN(x)
`#define` DEBUGSR_PRINTF(x...) DEBUGOUTF(x)

One separate issue remains in wled00/wled.cpp line 323:

server.printStatus(DEBUGOUT);  // DEBUGOUT is no longer a stream object

printStatus() takes a Print& argument, so DEBUGOUT (now a function-like macro) can't be passed here directly. This call site will need to be handled — either by replacing it with a different logging approach, or by introducing a small helper just for this one call site. How would you like to handle that one?


✏️ Learnings added
Learnt from: softhack007
Repo: wled/WLED PR: 5559
File: wled00/wled.h:990-998
Timestamp: 2026-05-03T11:37:43.177Z
Learning: In the WLED audioreactive usermod (usermods/audioreactive/audio_reactive.cpp), DEBUGSR_PRINT/DEBUGSR_PRINTLN/DEBUGSR_PRINTF macros should use the function-style DEBUGOUT(x)/DEBUGOUTLN(x)/DEBUGOUTF(x...) macros, not the old stream-style DEBUGOUT.print()/println()/printf() syntax.

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.


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Learnt from: softhack007
Repo: wled/WLED PR: 4838
File: lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp:30-35
Timestamp: 2026-03-27T12:33:48.499Z
Learning: In C/C++ preprocessor conditionals (`#if`, `#elif`) GCC/Clang treat `&&` as short-circuit evaluated during preprocessing. This means guards like `#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)` are safe even if the macro/function-like macro on the RHS (e.g., `ESP_IDF_VERSION_VAL`) is not defined on some targets, because the RHS will not be expanded when the LHS is false (e.g., `defined(...)` evaluates to 0). During code review, avoid flagging such cases as “undefined function-like macro invocation” if they are protected by short-circuiting `defined(...) && ...`/`||` logic; some tools like cppcheck may not model this and can produce false positives. Also, don’t suggest refactoring that moves ESP32-specific includes/headers (e.g., `esp_idf_version.h`) outside of these guarded preprocessor blocks, since that will break targets (e.g., ESP8266) where the headers don’t exist.

Comment thread wled00/net_debug.cpp
}

debugUdp.beginPacket(debugPrintHostIP, netDebugPrintPort);
debugUdp.beginPacket(netDebugPrintIP, netDebugPrintPort);
Copy link
Copy Markdown
Member

@softhack007 softhack007 May 3, 2026

Choose a reason for hiding this comment

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

Maybe it's also a good time to scrutinise the net debug UDP core code - I found it quite unreliable in the past, even when only dumping packets via netcat. Maybe we should directly move to a standard logging protocol? Afaik the main difference is in establishing the connection, after that its basicially a printf with some meta data added.

Netdebug was an overnight hack by a contributor, it was never tested under "real load".

Comment thread wled00/net_debug.cpp
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.

@coderabbitai please analyze the net debug core code for possible stability issues, buffer overruns and potential for heap fragmentation. Suggest improvements / re-design options that would improve stability.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch net_debug.h full content
fd -e h -g 'net_debug*' --exec cat {}

echo "===== wled.h DEBUGOUT section ====="
# Extract DEBUGOUT/NetDebug/debugUdp relevant sections from wled.h
rg -n "DEBUGOUT|NetDebug|debugUdp|netDebugPrint|netDebugEnabled|USER_PRINT|USER_FLUSH" wled00/wled.h | head -80

echo "===== debugUdp declaration ====="
rg -n "debugUdp" wled00/ --type cpp --type h -C2

echo "===== WiFiUDP buffer sizes ====="
rg -rn "WiFiUDP|UDP_TX_PACKET_MAX|UDP_RX_PACKET_MAX" wled00/ --type cpp --type h | head -30

echo "===== NetworkDebugPrinter class definition ====="
ast-grep --pattern 'class NetworkDebugPrinter { $$$ }'

echo "===== wled.h net debug block ====="
grep -n -A 5 -B 2 "WLED_ENABLE_NET_DEBUG\|DEBUGOUT\b\|DEBUGOUTLN\|DEBUGOUTF" wled00/wled.h | head -100

Length of output: 6723


@softhack007, here is a detailed stability analysis of the net debug core, with redesign suggestions:


Net Debug Stability Analysis

🔴 Issue 1 — Per-character UDP packets (Critical heap fragmentation)

This is the most severe problem. write(uint8_t c) fires a complete UDP transaction for every single byte:

debugUdp.beginPacket(netDebugPrintIP, netDebugPrintPort); // heap alloc
debugUdp.write(c);                                         // 1 byte payload
debugUdp.endPacket();                                      // heap free + syscall

NetworkDebugPrinter inherits from Print, and Print::print(const char*) iterates character-by-character, calling write(uint8_t) for each. So DEBUG_PRINT("hello world\n") generates 12 heap alloc/free cycles and 12 UDP packets. A typical boot sequence with dozens of DEBUG_PRINTLN calls produces thousands of these micro-transactions. On ESP32, WiFiUDP::beginPacket() calls into lwIP and allocates a pbuf chain from the heap each time; this is the primary driver of heap fragmentation.

Fix — add a line-oriented ring buffer:

// net_debug.h — add to private section:
  static constexpr size_t TX_BUF_SIZE = 512;
  uint8_t  _txBuf[TX_BUF_SIZE];
  size_t   _txLen = 0;

// net_debug.cpp — replace both write() overrides:
size_t NetworkDebugPrinter::write(uint8_t c) {
  if (!WLED_CONNECTED || !netDebugEnabled) return 0;
  if (_txLen < TX_BUF_SIZE) _txBuf[_txLen++] = c;
  if (c == '\n' || _txLen == TX_BUF_SIZE) flush();
  return 1;
}

size_t NetworkDebugPrinter::write(const uint8_t *buf, size_t size) {
  if (!WLED_CONNECTED || buf == nullptr || size == 0 || !netDebugEnabled) return 0;
  size_t written = 0;
  while (written < size) {
    size_t chunk = min(size - written, TX_BUF_SIZE - _txLen);
    memcpy(_txBuf + _txLen, buf + written, chunk);
    _txLen   += chunk;
    written  += chunk;
    if (_txLen == TX_BUF_SIZE || _txBuf[_txLen - 1] == '\n') flush();
  }
  return written;
}

void NetworkDebugPrinter::flush() {
  if (_txLen == 0 || !WLED_CONNECTED || !netDebugEnabled) return;
  if (debugUdp.beginPacket(netDebugPrintIP, netDebugPrintPort)) {
    debugUdp.write(_txBuf, _txLen);
    debugUdp.endPacket();
  }
  _txLen = 0;
}

This reduces packet count from N-per-string to 1-per-line while keeping _txBuf as a stack-like member (no additional heap allocation).


🔴 Issue 2 — No IP / Port validation before beginPacket()

netDebugPrintPort is int, initialized to 0. netDebugPrintIP is initialized to 0.0.0.0. Neither is validated before calling beginPacket(). On some lwIP builds, sending to 0.0.0.0:0 can assert or silently corrupt socket state.

Fix:

// Add guard at top of both write() / flush():
if (netDebugPrintPort == 0 || (uint32_t)netDebugPrintIP == 0) return 0;

Also, netDebugPrintPort should be uint16_t, not int, to make the type semantics explicit and avoid accidental negative values from deserialization:

// wled.h
WLED_GLOBAL uint16_t netDebugPrintPort _INIT(0);

🟠 Issue 3 — No mutex / thread-safety for the global NetDebug singleton

WLED runs multiple FreeRTOS tasks on ESP32 (network task, async webserver, main loop, etc.). DEBUG_PRINT* macros can be called from several of these. debugUdp is a stateful object — concurrent beginPacket/write/endPacket calls from two tasks will corrupt its internal send-buffer pointer. The _txLen counter in the proposed ring buffer is equally vulnerable.

Fix — add a lightweight portMUX or SemaphoreHandle:

// net_debug.h — private section:
  portMUX_TYPE _mux = portMUX_INITIALIZER_UNLOCKED;

// net_debug.cpp — in flush():
  taskENTER_CRITICAL(&_mux);
  // ... send ...
  taskEXIT_CRITICAL(&_mux);

Alternatively, for a non-IRQ context, a binary SemaphoreHandle_t is less invasive (won't disable interrupts). On ESP8266, the single-core architecture makes this less critical but still worth guarding against re-entrancy via yield() inside lwIP.


🟠 Issue 4 — DEBUGOUT macro ternary without statement wrapping

`#define` DEBUGOUT(x) netDebugEnabled?NetDebug.print(x):Serial.print(x)

This bare ternary can cause surprising parse behavior when used as a statement in certain contexts (e.g., immediately after an if without braces, or as part of a comma expression). The return value is silently discarded. Prefer a do { } while(0) pattern or an if/else form:

`#define` DEBUGOUT(x)   do { if (netDebugEnabled) NetDebug.print(x);   else Serial.print(x);   } while(0)
`#define` DEBUGOUTLN(x) do { if (netDebugEnabled) NetDebug.println(x); else Serial.println(x); } while(0)
`#define` DEBUGOUTF(x...) do { if (netDebugEnabled) NetDebug.printf(x); else Serial.printf(x); } while(0)

🟡 Issue 5 — USER_FLUSH() is a no-op when net debug is active

`#ifdef` WLED_ENABLE_NET_DEBUG
  `#define` USER_FLUSH() {}   // ← silent no-op

With the proposed ring buffer, a flush() call becomes meaningful (drains buffered content). The no-op means that any partial line left in the buffer (e.g., a message without a trailing \n) will never be sent. USER_FLUSH() should call NetDebug.flush() once flush() is properly implemented.


🟡 Issue 6 — No size cap check in write(const uint8_t*, size_t)

Currently:

size = debugUdp.write(buf, size);

The UDP MTU on the local network is ~1472 bytes (Ethernet) and the ESP32 lwIP send-buffer for UDP is bounded by CONFIG_LWIP_UDP_RECVMBOX_SIZE. A caller passing a very large size (unlikely for debug strings but possible in principle) will silently truncate without returning a correct written count. The ring buffer approach above naturally handles this by chunking.


Summary Table

Issue Severity Impact
Per-character UDP packets 🔴 Critical Heavy heap fragmentation, network spam
No IP/port validation 🔴 Critical Potential lwIP assert / socket corruption
No thread-safety (mutex) 🟠 Moderate Data corruption under concurrent tasks
Bare ternary DEBUGOUT macros 🟠 Moderate Parse hazard in edge call sites
USER_FLUSH() no-op 🟡 Low Partial lines silently dropped
No UDP size cap 🟡 Low Silent truncation for large buffers
int instead of uint16_t for port 🟡 Low Semantic / type safety

The single highest-impact change is Issue 1 (ring buffer with newline-triggered flush) — it addresses heap fragmentation, packet count, and indirectly the size-cap issue in one pass.


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Learnt from: softhack007
Repo: wled/WLED PR: 4893
File: wled00/set.cpp:95-95
Timestamp: 2026-03-14T20:56:46.543Z
Learning: Guideline: Ensure WiFi hostname is set after WiFi.mode() but before WiFi.begin() to avoid default esp-XXXXXX hostname being used in DHCP. This ordering only takes effect after the STA interface exists (so avoid placing hostname setting before initConnection). In WLED, place the hostname configuration inside initConnection() (after WiFi.disconnect(true) and before WiFi.begin()) rather than in earlier boot code like deserializeConfig(). This rule should be applied in code reviews for WLED’s network initialization paths in wled00/*.cpp, and note that on ESP8266 the ordering is less strict but still desirable for consistency.

Learnt from: softhack007
Repo: wled/WLED PR: 4838
File: lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp:30-35
Timestamp: 2026-03-27T12:33:48.499Z
Learning: In C/C++ preprocessor conditionals (`#if`, `#elif`) GCC/Clang treat `&&` as short-circuit evaluated during preprocessing. This means guards like `#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)` are safe even if the macro/function-like macro on the RHS (e.g., `ESP_IDF_VERSION_VAL`) is not defined on some targets, because the RHS will not be expanded when the LHS is false (e.g., `defined(...)` evaluates to 0). During code review, avoid flagging such cases as “undefined function-like macro invocation” if they are protected by short-circuiting `defined(...) && ...`/`||` logic; some tools like cppcheck may not model this and can produce false positives. Also, don’t suggest refactoring that moves ESP32-specific includes/headers (e.g., `esp_idf_version.h`) outside of these guarded preprocessor blocks, since that will break targets (e.g., ESP8266) where the headers don’t exist.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/cfg.cpp:673-673
Timestamp: 2026-03-27T21:17:45.980Z
Learning: When calling raw lwIP APIs (e.g., around `ntpUdp.begin()` or any `lwIP`/`tcpip`-layer call) in this codebase on ESP32 Arduino-ESP32 platforms where core locking/checking is enabled, wrap the lwIP call with `LOCK_TCPIP_CORE()` / `UNLOCK_TCPIP_CORE()` from `lwip/tcpip.h`. This prevents thread-safety/core-violation crashes without requiring `sdkconfig` changes.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/cfg.cpp:673-673
Timestamp: 2026-03-27T21:17:45.980Z
Learning: When using lwIP “raw” APIs in WLED on ESP32 (Arduino-ESP32 / IDF 5.5+), don’t rely on LOCK_TCPIP_CORE()/UNLOCK_TCPIP_CORE() unless CONFIG_LWIP_TCPIP_CORE_LOCKING=y is guaranteed. CONFIG_LWIP_CHECK_THREAD_SAFETY=y can trigger the assertion “Required to lock TCPIP core functionality!” when raw lwIP calls occur off the TCPIP thread. If the locking mode isn’t enabled (or can’t be changed via sdkconfig), schedule lwIP work (e.g., ntpUdp.begin() and similar raw lwIP calls) via tcpip_callback() so it runs on the TCPIP thread; this should work regardless of the locking-mode setting. Review any similar raw lwIP usage for correct thread/context handling.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/wled.cpp:698-700
Timestamp: 2026-03-28T01:37:15.541Z
Learning: In this WLED codebase, when using `DEBUG_PRINTLN(F("..."))`, an explicit trailing `\n` inside the `F("...")` string (e.g., `DEBUG_PRINTLN(F("Warning!\n"))`) may be intentional to create a blank line in debug output as a visual separator. During code review, do not automatically flag these as “double newline” or recommend removing the `\n`—first verify with the author/context that the extra newline is deliberate.

Learnt from: willmmiles
Repo: wled/WLED PR: 5462
File: wled00/json.cpp:1189-1198
Timestamp: 2026-03-30T15:32:02.808Z
Learning: When working with WLED strings stored in PROGMEM on ESP8266/ESP32, assume you can’t use `strchr_P` for character scanning. If you need to scan a PROGMEM string for a character (e.g., searching for '@' within effect data), review changes to ensure they perform an explicit RAM copy first (e.g., via `strncpy_P` into a buffer) and then scan in RAM. Avoid proposing incremental/streaming PROGMEM character-by-character scans without a RAM buffer on these targets, since it’s impractical with the available APIs.

Learnt from: DedeHai
Repo: wled/WLED PR: 5464
File: wled00/FX_fcn.cpp:1699-1701
Timestamp: 2026-04-09T07:26:09.816Z
Learning: In WLED’s WLED code, if a pixel/buffer allocation uses `BFRALLOC_NOBYTEACCESS` (and especially on classic ESP32 where byte-level access to IRAM-resident buffers is unsafe), avoid using byte-wise operations like `memset`/`memcpy` on that buffer. Specifically, do not combine `BFRALLOC_CLEAR | BFRALLOC_NOBYTEACCESS` and do not perform `memcpy`/`memset` over `Segment::pixels` (e.g., in `setGeometry()`/`finalizeInit()` or copy ctor/assignment). Instead, use element-wise 32-bit access (loop over `uint32_t*` and assign/copy per element) to ensure 32-bit access only.

Learnt from: DedeHai
Repo: wled/WLED PR: 5464
File: wled00/FX_fcn.cpp:1699-1701
Timestamp: 2026-04-09T09:14:29.218Z
Learning: In WLED, when reviewing ESP32 code that uses pixel buffers allocated with `BFRALLOC_NOBYTEACCESS` (e.g., `WS2812FX::_pixels` and `Segment::pixels`), treat `memset`/`memcpy` on those buffers as an intentionally low-risk, accepted pattern. These buffers are designed to be IRAM-safe for 32-bit-only access, and the Xtensa newlib assembly implementations perform 32-bit word stores for word-aligned, word-multiple-sized buffers (so the IRAM constraint is satisfied in practice). Do not flag `memset`/`memcpy` on `BFRALLOC_NOBYTEACCESS` pixel buffers as a critical bug; if any concern arises, downgrade severity to low and focus on potential future regressions rather than an immediate correctness violation.

Learnt from: softhack007
Repo: wled/WLED PR: 5508
File: wled00/FX.cpp:4137-4142
Timestamp: 2026-04-16T09:49:58.587Z
Learning: In WLED effect/overlay implementations (C++ under wled00), ensure effects operate through the SEGMENT.* read/write APIs for their own pixels/overlay state. Reserve global frame reads/writes like strip.getPixelColor* for copy_* style post-processing only. Do not manually composite/blend across lower layers inside the effect; segment-to-segment blending is handled later by WS2812FX::blendSegment(), so effects should produce their per-segment contributions without trying to composite with underlying layers themselves.

Comment thread wled00/wled.h
#endif

// macros to print "user messages" to Serial
// cannot do this on -S2, due to buggy USBCDC serial driver
Copy link
Copy Markdown
Member

@softhack007 softhack007 May 3, 2026

Choose a reason for hiding this comment

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

@netmindz ah, you already have a provision for USER_PRINT 👍 did not see this when writing my first comment.

The all you'd need is to also import caUseSerial from WLED-MM - this allows to always support USER_PRINT even without netdebug.

Comment thread wled00/wled.h
#endif
#else
// if serial is available, we use Serial.print directly
#define USER_PRINT(x) { if (canUseSerial()) DEBUGOUT(x); }
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.

@coderabbitai where is the implementation of ``canUseSerial()`?

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "canUseSerial" --type=cpp --type=c -C3

Length of output: 501


🏁 Script executed:

#!/bin/bash
rg -n "canUseSerial" -C3

Length of output: 501


🏁 Script executed:

#!/bin/bash
# Search in .ino files (not picked up by default rg type filters)
rg -n "canUseSerial" --glob="*.ino" -C3
echo "---"
# Also check fcn_declare.h for declaration
grep -n "canUseSerial" wled00/fcn_declare.h
echo "---"
# Check all files including hidden
rg -n "canUseSerial" --no-ignore -C3

Length of output: 509


🏁 Script executed:

#!/bin/bash
# Check if canUseSerial exists on main branch
git fetch origin main 2>/dev/null
git grep -n "canUseSerial" origin/main

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for any serial availability/guard helper functions
rg -n "bool.*[Ss]erial" wled00/ -C2
echo "---"
# Check if serialCanRX / serialCanTX are used as guards
rg -n "serialCan" wled00/ -C2

Length of output: 12284


@softhack007canUseSerial() does not exist anywhere in this repository. It is referenced in the newly added USER_PRINT/USER_PRINTLN/USER_PRINTF macros (Lines 1030–1032 of wled.h) that are active in the default build (when neither WLED_DEBUG nor WLED_ENABLE_NET_DEBUG is defined), but there is no declaration or definition of it in any .h, .cpp, or .ino file on this branch — or on main.

This would cause a compilation error for a standard build. The existing WLED idiom for guarding serial output is the global boolean serialCanTX (declared in wled.h line 529 and set in wled.cpp lines 524–525 based on pin-allocation checks), which is already used consistently throughout wled_serial.cpp and ir.cpp.

The macros should likely read:

`#define` USER_PRINT(x)      { if (serialCanTX) Serial.print(x); }
`#define` USER_PRINTLN(x)    { if (serialCanTX) Serial.println(x); }
`#define` USER_PRINTF(x...)  { if (serialCanTX) Serial.printf(x); }

(or route through DEBUGOUT/DEBUGOUTLN/DEBUGOUTF if that's preferred, but still guarded by serialCanTX).


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Learnt from: softhack007
Repo: wled/WLED PR: 4838
File: lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp:30-35
Timestamp: 2026-03-27T12:33:48.499Z
Learning: In C/C++ preprocessor conditionals (`#if`, `#elif`) GCC/Clang treat `&&` as short-circuit evaluated during preprocessing. This means guards like `#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)` are safe even if the macro/function-like macro on the RHS (e.g., `ESP_IDF_VERSION_VAL`) is not defined on some targets, because the RHS will not be expanded when the LHS is false (e.g., `defined(...)` evaluates to 0). During code review, avoid flagging such cases as “undefined function-like macro invocation” if they are protected by short-circuiting `defined(...) && ...`/`||` logic; some tools like cppcheck may not model this and can produce false positives. Also, don’t suggest refactoring that moves ESP32-specific includes/headers (e.g., `esp_idf_version.h`) outside of these guarded preprocessor blocks, since that will break targets (e.g., ESP8266) where the headers don’t exist.

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.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Try to fetch the WLED-MM canUseSerial function from raw GitHub
curl -s "https://raw.githubusercontent.com/MoonModules/WLED-MM/459a66491d7a3993ef456eb5cbfaad4c9d806f3b/wled00/wled_serial.cpp" | sed -n '80,110p'

Length of output: 1794


@softhack007 — confirmed, that's exactly the missing piece. The function retrieved from WLED-MM is richer than a simple serialCanTX check — it guards against several serial-conflict scenarios:

bool canUseSerial(void) {
  // On S3/C3/C6/P4, USB CDC blocks if disconnected
  `#if` ARDUINO_USB_CDC_ON_BOOT && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32P4)) && !defined(WLED_ENABLE_NET_DEBUG)
  if (!Serial) return false;
  `#endif`
  // TX pin allocated to LEDs or other non-debug function
  if (pinManager.isPinAllocated(hardwareTX) && (pinManager.getPinOwner(hardwareTX) != PinOwner::DebugOut))
    return false;
  // Serial in use for Adalight / TPM2NET / generic realtime streaming
  if ((realtimeMode == REALTIME_MODE_GENERIC) || (realtimeMode == REALTIME_MODE_ADALIGHT) || (realtimeMode == REALTIME_MODE_TPM2NET))
    return false;
  // Don't interfere with IMPROV
  if (improvActive > 0) return false;
  // Continuous Serial Streaming active
  if (continuousSendLED == true) return false;
  return true;
}

Two things to note for the port:

  1. The !defined(WLED_DEBUG_HOST) guard in the USB-CDC check should be updated to !defined(WLED_ENABLE_NET_DEBUG) to match the new flag used in this PR.
  2. The function needs to be declared in fcn_declare.h (or equivalent) so it is visible when wled.h macros expand it — that declaration is also absent from this PR.

Without both the definition and the declaration, any build that doesn't define WLED_DEBUG or WLED_ENABLE_NET_DEBUG will fail to compile at the USER_PRINT/USER_PRINTLN/USER_PRINTF macros.


✏️ Learnings added
Learnt from: softhack007
Repo: wled/WLED PR: 5559
File: wled00/wled.h:1030-1030
Timestamp: 2026-05-03T11:55:40.143Z
Learning: In WLED (wled00/wled_serial.cpp), the function `canUseSerial()` (ported from WLED-MM) checks whether Serial output is safe to use: it blocks on disconnected USB-CDC (S3/C3/C6/P4 with ARDUINO_USB_CDC_ON_BOOT), TX pin conflicts, realtime modes GENERIC/ADALIGHT/TPM2NET, improvActive > 0, and continuousSendLED. This function must be declared in fcn_declare.h and defined in wled_serial.cpp. The upstream WLED main branch does NOT have this function — it only has the simpler `serialCanTX` boolean. The WLED_ENABLE_NET_DEBUG guard (not WLED_DEBUG_HOST) should be used inside it.

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.


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Learnt from: softhack007
Repo: wled/WLED PR: 4838
File: lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp:30-35
Timestamp: 2026-03-27T12:33:48.499Z
Learning: In C/C++ preprocessor conditionals (`#if`, `#elif`) GCC/Clang treat `&&` as short-circuit evaluated during preprocessing. This means guards like `#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)` are safe even if the macro/function-like macro on the RHS (e.g., `ESP_IDF_VERSION_VAL`) is not defined on some targets, because the RHS will not be expanded when the LHS is false (e.g., `defined(...)` evaluates to 0). During code review, avoid flagging such cases as “undefined function-like macro invocation” if they are protected by short-circuiting `defined(...) && ...`/`||` logic; some tools like cppcheck may not model this and can produce false positives. Also, don’t suggest refactoring that moves ESP32-specific includes/headers (e.g., `esp_idf_version.h`) outside of these guarded preprocessor blocks, since that will break targets (e.g., ESP8266) where the headers don’t exist.

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.

I agree that the minimal replacement could be checking if (serialCanTX

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.

@softhack007 — agreed, serialCanTX is the right minimal replacement here. The three affected macros in the #else branch become:

  // if serial is available, we use Serial.print directly
  `#define` USER_PRINT(x)      { if (serialCanTX) Serial.print(x); }
  `#define` USER_PRINTLN(x)    { if (serialCanTX) Serial.println(x); }
  `#define` USER_PRINTF(x...)  { if (serialCanTX) Serial.printf(x); }
  `#define` USER_FLUSH()       { Serial.flush(); }

This avoids the missing-function compile error and stays consistent with how serialCanTX is already used throughout wled_serial.cpp and ir.cpp. No declaration/definition changes are needed in fcn_declare.h or wled_serial.cpp.


✏️ Learnings added
Learnt from: softhack007
Repo: wled/WLED PR: 5559
File: wled00/wled.h:1030-1030
Timestamp: 2026-05-03T11:57:15.014Z
Learning: In WLED (wled00/wled.h), the USER_PRINT/USER_PRINTLN/USER_PRINTF macros (active when neither WLED_DEBUG nor WLED_ENABLE_NET_DEBUG is defined) should guard serial output with `if (serialCanTX)` — i.e., `#define USER_PRINT(x) { if (serialCanTX) Serial.print(x); }`. The `canUseSerial()` function from WLED-MM is NOT present in upstream WLED and must NOT be referenced here; use the existing `serialCanTX` boolean instead.

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.


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Learnt from: softhack007
Repo: wled/WLED PR: 4838
File: lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp:30-35
Timestamp: 2026-03-27T12:33:48.499Z
Learning: In C/C++ preprocessor conditionals (`#if`, `#elif`) GCC/Clang treat `&&` as short-circuit evaluated during preprocessing. This means guards like `#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)` are safe even if the macro/function-like macro on the RHS (e.g., `ESP_IDF_VERSION_VAL`) is not defined on some targets, because the RHS will not be expanded when the LHS is false (e.g., `defined(...)` evaluates to 0). During code review, avoid flagging such cases as “undefined function-like macro invocation” if they are protected by short-circuiting `defined(...) && ...`/`||` logic; some tools like cppcheck may not model this and can produce false positives. Also, don’t suggest refactoring that moves ESP32-specific includes/headers (e.g., `esp_idf_version.h`) outside of these guarded preprocessor blocks, since that will break targets (e.g., ESP8266) where the headers don’t exist.

@netmindz netmindz added this to the 16.1 milestone May 3, 2026
Comment thread wled00/wled.h
#endif

// macros to print "user messages" to Serial
// cannot do this on -S2, due to buggy USBCDC serial driver
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.

@coderabbitai please verify if the restriction for -S2 (buggy USBCDC) is still needed. For comparison, there is a workaround in WLED-MM but it has the side-effect that sometimes serial output could get lost

https://github.com/MoonModules/WLED-MM/blob/459a66491d7a3993ef456eb5cbfaad4c9d806f3b/wled00/wled.cpp#L498

https://github.com/MoonModules/WLED-MM/blob/459a66491d7a3993ef456eb5cbfaad4c9d806f3b/wled00/wled.cpp#L525-L556

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.

🧩 Analysis chain

Let me look at the referenced WLED-MM code and check the current state of the S2 USBCDC bug.


🏁 Script executed:

#!/bin/bash
# Find all S2/USBCDC related guards and comments in WLED codebase
rg -n "S2|USBCDC|CDC_ON_BOOT|USB_CDC|canUseSerial" --type=cpp --type=h -C2

Length of output: 86561


🏁 Script executed:

#!/bin/bash
# Find canUseSerial and S2 serial-related guards
rg -n "canUseSerial|CONFIG_IDF_TARGET_ESP32S2|ARDUINO_USB_CDC_ON_BOOT|USBCDC" --type=cpp --type=h -C3

echo "---"
# Also check fcn_declare for canUseSerial declaration
rg -n "canUseSerial" -C3

Length of output: 37006


🏁 Script executed:

#!/bin/bash
# Find canUseSerial and S2 serial-related guards
rg -n "canUseSerial|CONFIG_IDF_TARGET_ESP32S2|ARDUINO_USB_CDC_ON_BOOT|USBCDC" --type=cpp --type=h -C3

echo "=== wled.cpp S2 serial handling ==="
rg -n "S2\|serial\|Serial\|canUseSerial" wled00/wled.cpp -i -C2 | head -80

echo "=== fcn_declare for canUseSerial ==="
rg -n "canUseSerial" -C3

Length of output: 37075


🌐 Web query:

ESP32-S2 USBCDC Arduino Serial driver bug status 2025

💡 Result:

As of 2025, the main “Arduino Serial over native USB CDC on ESP32‑S2” bugs people commonly refer to were already fixed upstream in the Espressif Arduino core:

  • Dropped RX data / setRxBufferSize() not taking effect / RX queue forced to 256 bytes (USB CDC on boot): fixed by PR #6413, merged Mar 28, 2022 (milestone 2.0.3). So any Arduino-ESP32 core ≥ 2.0.3 should include this fix. (github.com)
  • ESP_LOG* / log_e not printing when using USB‑CDC (TinyUSB OTG) on ESP32‑S2/S3: fixed by PR #10123, merged Aug 13, 2024; it’s listed in the Arduino-ESP32 3.1.0-RC1 release notes and later. (github.com)

If what you’re seeing in 2025 is “no serial output / can’t open COM port”, that’s often configuration/host-side, not the old core bug—e.g. needing Tools → “USB CDC On Boot” enabled and the right USB mode/flash settings. (docs.espressif.com)

Citations:


Let me look at the canUseSerial() implementation and fetch the WLED-MM code for comparison.


🏁 Script executed:

#!/bin/bash
echo "=== canUseSerial definition ==="
ast-grep --pattern 'bool canUseSerial() { $$$ }'
rg -n "canUseSerial" --type=cpp --type=h -A5

echo "=== fcn_declare.h for canUseSerial ==="
rg -n "canUseSerial" wled00/fcn_declare.h -C3

echo "=== WLED-MM referenced lines via curl ==="
curl -s "https://raw.githubusercontent.com/MoonModules/WLED-MM/459a66491d7a3993ef456eb5cbfaad4c9d806f3b/wled00/wled.cpp" | sed -n '490,560p'

Length of output: 4309


🏁 Script executed:

#!/bin/bash
echo "=== canUseSerial definition ==="
rg -n "canUseSerial" -A10

echo "=== arduino-esp32 issue 9043 context - S2 setTxTimeoutMs ==="
curl -s "https://raw.githubusercontent.com/MoonModules/WLED-MM/459a66491d7a3993ef456eb5cbfaad4c9d806f3b/wled00/wled.cpp" | sed -n '470,500p'

Length of output: 2178


🏁 Script executed:

#!/bin/bash
echo "=== Search canUseSerial in all files ==="
rg -rn "canUseSerial" .

echo "=== wled.cpp lines around Serial for canUseSerial ==="
grep -n "canUseSerial\|bool.*serial\|Serial.*bool" wled00/wled.cpp wled00/util.cpp wled00/network.cpp 2>/dev/null | head -30

echo "=== Check fcn_declare.h for canUseSerial ==="
cat wled00/fcn_declare.h | grep -n "canUseSerial\|serial"

Length of output: 1237


🏁 Script executed:

#!/bin/bash
echo "=== wled_serial.cpp canUseSerial ==="
cat wled00/wled_serial.cpp | grep -n "canUseSerial" -A10 -B5

echo "=== Check all .cpp and .h files for canUseSerial definition ==="
grep -rn "bool canUseSerial\|canUseSerial()" wled00/ --include="*.cpp" --include="*.h" --include="*.ino"

Length of output: 359


@softhack007 — I checked the codebase and researched the current status of the S2 USBCDC driver. Here are the key findings:


1. The S2 USBCDC restriction is still relevant

The S2 CDC-over-USB crash is still being actively worked around in the current codebase — in wled00/FX_2Dfcn.cpp, there is an explicit delay(1) guard specifically for S2:

`#if` defined(CONFIG_IDF_TARGET_ESP32S2)
delay(1); // on S2 the CDC output can crash without a delay
`#endif`

The WLED-MM workaround applies Serial.setTxTimeoutMs(0) (for arduino-esp32 issue #9043) only to S3/C3/C6/P4 — the ESP32-S2 is intentionally excluded:

// WLED-MM: only for S3/C3/C6/P4, NOT S2:
`#if` ARDUINO_USB_CDC_ON_BOOT && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || ...)
Serial.setTxTimeoutMs(0); // potential side-effect: incomplete debug output

Some specific arduino-esp32 bugs have been fixed — the RX buffer bug was resolved in PR #6413 (merged March 2022, milestone 2.0.3), and the ESP_LOG* not printing via TinyUSB was fixed in PR #10123 (merged August 2024, in 3.1.0-RC1). However, blocking TX/flush behavior for the S2 remains unaddressed by setTxTimeoutMs(0), which is why WLED-MM explicitly excludes the S2 from that fix.


2. Critical issue: canUseSerial() is undefined ⚠️

More importantly, searching the entire PR branch reveals that canUseSerial() is referenced in three macros but never defined anywhere:

// wled.h — only usages, no definition found:
`#define` USER_PRINT(x)      { if (canUseSerial()) DEBUGOUT(x); }
`#define` USER_PRINTLN(x)    { if (canUseSerial()) DEBUGOUTLN(x); }
`#define` USER_PRINTF(x...)  { if (canUseSerial()) DEBUGOUTF(x); }

This #else branch is entered when neither WLED_DEBUG nor WLED_ENABLE_NET_DEBUG is defined — i.e., the standard release build. Any such build will fail to compile/link with an undefined reference.

The intent appears to be a runtime guard similar to WLED-MM's if (Serial) checks. A minimal implementation could be:

// in wled00/fcn_declare.h or inline:
inline bool canUseSerial() {
`#if` defined(ARDUINO_USB_CDC_ON_BOOT) || defined(ARDUINO_USB_MODE)
  return (bool)Serial; // only safe if USB CDC has actually initialized
`#else`
  return true;         // standard UART serial is always ready after Serial.begin()
`#endif`
}

For S2 specifically, given the CDC crash risk under heavy output, a more conservative approach (like WLED-MM's Serial.setTxTimeoutMs(0) equivalent or the delay(1) pattern) may also be needed. The WLED-MM note that setTxTimeoutMs(0) has "the side-effect that sometimes serial output could get lost" is the acknowledged trade-off.

Summary: The S2 restriction comment is still warranted. The more pressing issue is that canUseSerial() must be defined before this PR is mergeable.


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Learnt from: softhack007
Repo: wled/WLED PR: 4838
File: lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp:30-35
Timestamp: 2026-03-27T12:33:48.499Z
Learning: In C/C++ preprocessor conditionals (`#if`, `#elif`) GCC/Clang treat `&&` as short-circuit evaluated during preprocessing. This means guards like `#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)` are safe even if the macro/function-like macro on the RHS (e.g., `ESP_IDF_VERSION_VAL`) is not defined on some targets, because the RHS will not be expanded when the LHS is false (e.g., `defined(...)` evaluates to 0). During code review, avoid flagging such cases as “undefined function-like macro invocation” if they are protected by short-circuiting `defined(...) && ...`/`||` logic; some tools like cppcheck may not model this and can produce false positives. Also, don’t suggest refactoring that moves ESP32-specific includes/headers (e.g., `esp_idf_version.h`) outside of these guarded preprocessor blocks, since that will break targets (e.g., ESP8266) where the headers don’t exist.

@softhack007

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

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: 4

🤖 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/bus_manager.h`:
- Line 43: The DEBUGBUS_PRINTF_P macro currently maps to DEBUGOUTF which calls
plain printf and therefore breaks PROGMEM PSTR() format strings on ESP8266; fix
by adding a PROGMEM-aware path: either implement a new DEBUGOUTF_P that calls
printf_P() (or NetDebug.printf_P()) on ESP8266 and fallback to printf on other
platforms, or modify DEBUGOUTF to detect _P usage and route to printf_P; then
change DEBUGBUS_PRINTF_P to map to DEBUGOUTF_P (or the new PROGMEM-aware
DEBUGOUTF) so calls like DEBUGBUS_PRINTF_P(PSTR(...), ...) print correctly.

In `@wled00/json.cpp`:
- Around line 371-373: The runtime handling currently masks the net-debug toggle
behind WLED_DEBUG; change the conditional so netDebugEnabled is updated when
WLED_ENABLE_NET_DEBUG is defined (regardless of WLED_DEBUG). Specifically,
replace the guarded line that sets netDebugEnabled from root[F("debug")] so it
is compiled when WLED_ENABLE_NET_DEBUG is defined (use `#if`
defined(WLED_ENABLE_NET_DEBUG) ... `#endif`) and keep the assignment
netDebugEnabled = root[F("debug")] | netDebugEnabled; so the /json/state "debug"
field actually toggles net debug at runtime; keep existing symbols
netDebugEnabled and root[F("debug")] to locate and modify the code.
- Around line 867-872: The net-debug bit handling is incorrectly inside the
WLED_DEBUG preprocessor guard so builds with only WLED_ENABLE_NET_DEBUG won't
advertise support or status; move or duplicate the WLED_ENABLE_NET_DEBUG-related
logic out of the `#ifdef` WLED_DEBUG block so that the code that sets the os
status bits for net-debug runs regardless of WLED_DEBUG. Specifically, ensure
the code that inspects netDebugEnabled and sets/clears the 0x0100/0x0080 bits
(the os variable and netDebugEnabled checks) is executed for builds with
WLED_ENABLE_NET_DEBUG so /json/info reports net-debug capability and runtime
status while still keeping any verbose debug-only behavior inside WLED_DEBUG.

In `@wled00/wled.h`:
- Around line 1019-1028: The USER_PRINT*, USER_PRINTLN, and USER_PRINTF macros
currently forward to DEBUG_PRINT* even when only WLED_ENABLE_NET_DEBUG is
defined, which makes them no-ops because DEBUG_PRINT is compiled out unless
WLED_DEBUG is set; change the macro logic so that when WLED_DEBUG is defined
USER_PRINT*/USER_PRINTF map to DEBUG_PRINT*/DEBUG_PRINTF, but when only
WLED_ENABLE_NET_DEBUG is defined they map to the Net-Debug output functions (do
not forward to DEBUG_PRINT); also ensure USER_FLUSH uses the Net-Debug flush in
that branch (keep references to USER_PRINT, USER_PRINTLN, USER_PRINTF,
DEBUG_PRINT, WLED_ENABLE_NET_DEBUG, WLED_DEBUG, USER_FLUSH, DEBUGOUTFlush to
locate and update the macros).
🪄 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: fde3cfb3-3c9a-4448-a39f-5e5180ea8911

📥 Commits

Reviewing files that changed from the base of the PR and between 30d1617 and d316625.

📒 Files selected for processing (5)
  • wled00/bus_manager.h
  • wled00/cfg.cpp
  • wled00/data/index.js
  • wled00/json.cpp
  • wled00/wled.h
✅ Files skipped from review due to trivial changes (1)
  • wled00/data/index.js

Comment thread wled00/bus_manager.h
#define DEBUGBUS_PRINT(x) DEBUGOUT(x)
#define DEBUGBUS_PRINTLN(x) DEBUGOUTLN(x)
#define DEBUGBUS_PRINTF(x...) DEBUGOUTF(x)
#define DEBUGBUS_PRINTF_P(x...) DEBUGOUTF(x)
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
# Find definition of DEBUGOUTF and DEBUGOUT macros
rg -n --type=cpp --type=h -C3 'define\s+DEBUGOUTF\b'
rg -n --type=cpp --type=h -C3 'define\s+DEBUGOUT\b'

Repository: wled/WLED

Length of output: 1720


🏁 Script executed:

# Check line 482 in bus_manager.h for the actual usage
sed -n '475,490p' wled00/bus_manager.h

# Search for printf_P usage to see if it's used elsewhere
rg -n 'printf_P' wled00/ --type=cpp --type=h | head -20

# Search for historical _P debugging patterns
rg -n 'DEBUGOUT.*_P\b' wled00/ --type=cpp --type=h | head -20

Repository: wled/WLED

Length of output: 2811


DEBUGBUS_PRINTF_P mapping violates PROGMEM string handling.

Line 43 maps DEBUGBUS_PRINTF_P to DEBUGOUTF, which from wled.h (lines 993, 998) calls Serial.printf(x) or NetDebug.printf(x) — not printf_P().

The actual callsite at line 482 uses:

DEBUGBUS_PRINTF_P(PSTR("Bus: Config (%d-%d, type:%d, CO:%d, rev:%d, skip:%d, AW:%d kHz:%d, mA:%d/%d, driver:%s)\n"), ...)

On ESP8266, PSTR() strings reside in PROGMEM and must be read via printf_P(). Calling plain printf() on a PROGMEM pointer produces garbled output or faults.

The project guideline states: "Use PSTR() with DEBUG_PRINTF_P() for format strings." This change violates that by silently dropping the _P distinction.

Either:

  1. Define a separate DEBUGOUTF_P macro that calls printf_P(), or
  2. Make DEBUGOUTF itself PROGMEM-aware (route _P calls through printf_P() on ESP8266).

Then wire DEBUGBUS_PRINTF_P to the correct macro.

🤖 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/bus_manager.h` at line 43, The DEBUGBUS_PRINTF_P macro currently maps
to DEBUGOUTF which calls plain printf and therefore breaks PROGMEM PSTR() format
strings on ESP8266; fix by adding a PROGMEM-aware path: either implement a new
DEBUGOUTF_P that calls printf_P() (or NetDebug.printf_P()) on ESP8266 and
fallback to printf on other platforms, or modify DEBUGOUTF to detect _P usage
and route to printf_P; then change DEBUGBUS_PRINTF_P to map to DEBUGOUTF_P (or
the new PROGMEM-aware DEBUGOUTF) so calls like DEBUGBUS_PRINTF_P(PSTR(...), ...)
print correctly.

Comment thread wled00/json.cpp
Comment on lines +371 to 373
#if defined(WLED_DEBUG) && defined(WLED_ENABLE_NET_DEBUG)
netDebugEnabled = root[F("debug")] | netDebugEnabled;
#endif
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 | 🟠 Major | ⚡ Quick win

/json/state can't toggle net debug unless WLED_DEBUG is also on.

The runtime flag is still guarded by WLED_DEBUG && WLED_ENABLE_NET_DEBUG, so a WLED_ENABLE_NET_DEBUG build silently ignores {"debug":...} even though the PR exposes net debug as a runtime feature.

Suggested fix
-  `#if` defined(WLED_DEBUG) && defined(WLED_ENABLE_NET_DEBUG)
+  `#ifdef` WLED_ENABLE_NET_DEBUG
   netDebugEnabled = root[F("debug")] | netDebugEnabled;
   `#endif`
🤖 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/json.cpp` around lines 371 - 373, The runtime handling currently masks
the net-debug toggle behind WLED_DEBUG; change the conditional so
netDebugEnabled is updated when WLED_ENABLE_NET_DEBUG is defined (regardless of
WLED_DEBUG). Specifically, replace the guarded line that sets netDebugEnabled
from root[F("debug")] so it is compiled when WLED_ENABLE_NET_DEBUG is defined
(use `#if` defined(WLED_ENABLE_NET_DEBUG) ... `#endif`) and keep the assignment
netDebugEnabled = root[F("debug")] | netDebugEnabled; so the /json/state "debug"
field actually toggles net debug at runtime; keep existing symbols
netDebugEnabled and root[F("debug")] to locate and modify the code.

Comment thread wled00/json.cpp
Comment on lines 867 to 872
#ifdef WLED_DEBUG
os = 0x80;
#ifdef WLED_DEBUG_HOST
#ifdef WLED_ENABLE_NET_DEBUG
os |= 0x0100;
if (!netDebugEnabled) os &= ~0x0080;
#endif
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 | 🟠 Major | ⚡ Quick win

Net-debug status bits are still hidden behind the outer WLED_DEBUG guard.

Because this block only runs inside #ifdef WLED_DEBUG, /json/info won't advertise net-debug support/status for WLED_ENABLE_NET_DEBUG-only builds. That makes the runtime feature look unavailable even when it is compiled in.

Suggested fix
   uint16_t os = 0;
   `#ifdef` WLED_DEBUG
-  os  = 0x80;
-    `#ifdef` WLED_ENABLE_NET_DEBUG
-    os |= 0x0100;
-    if (!netDebugEnabled) os &= ~0x0080;
-    `#endif`
+  os |= 0x0080;
   `#endif`
+  `#ifdef` WLED_ENABLE_NET_DEBUG
+  os |= 0x0100;
+  if (netDebugEnabled) os |= 0x0080;
+  `#endif`
🤖 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/json.cpp` around lines 867 - 872, The net-debug bit handling is
incorrectly inside the WLED_DEBUG preprocessor guard so builds with only
WLED_ENABLE_NET_DEBUG won't advertise support or status; move or duplicate the
WLED_ENABLE_NET_DEBUG-related logic out of the `#ifdef` WLED_DEBUG block so that
the code that sets the os status bits for net-debug runs regardless of
WLED_DEBUG. Specifically, ensure the code that inspects netDebugEnabled and
sets/clears the 0x0100/0x0080 bits (the os variable and netDebugEnabled checks)
is executed for builds with WLED_ENABLE_NET_DEBUG so /json/info reports
net-debug capability and runtime status while still keeping any verbose
debug-only behavior inside WLED_DEBUG.

Comment thread wled00/wled.h
Comment on lines +1019 to +1028
#if defined(WLED_DEBUG) || defined(WLED_ENABLE_NET_DEBUG)
// use DEBUG_PRINT
#define USER_PRINT(x) DEBUG_PRINT(x)
#define USER_PRINTLN(x) DEBUG_PRINTLN(x)
#define USER_PRINTF(x...) DEBUG_PRINTF(x)
#ifdef WLED_ENABLE_NET_DEBUG
#define USER_FLUSH() {}
#else
#define USER_FLUSH() {DEBUGOUTFlush();}
#endif
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 | 🟠 Major | ⚡ Quick win

USER_PRINT* becomes a no-op in net-debug-only builds.

This branch is selected when WLED_ENABLE_NET_DEBUG is enabled, but it forwards to DEBUG_PRINT*, which expands to nothing unless WLED_DEBUG is also defined. That drops the runtime/user-facing messages this PR is trying to expose over Net Debug. As per coding guidelines, Use DEBUG_PRINTF() / DEBUG_PRINTLN() for debug output in C++ (compiled out unless -D WLED_DEBUG).

Suggested fix
-#if defined(WLED_DEBUG) || defined(WLED_ENABLE_NET_DEBUG)
-  // use DEBUG_PRINT
+#if defined(WLED_DEBUG)
+  // use DEBUG_PRINT
   `#define` USER_PRINT(x) DEBUG_PRINT(x)
   `#define` USER_PRINTLN(x) DEBUG_PRINTLN(x)
   `#define` USER_PRINTF(x...) DEBUG_PRINTF(x)
+  `#ifdef` WLED_ENABLE_NET_DEBUG
+    `#define` USER_FLUSH() {}
+  `#else`
+    `#define` USER_FLUSH() {DEBUGOUTFlush();}
+  `#endif`
+#elif defined(WLED_ENABLE_NET_DEBUG)
+  // use net debug routing directly even when WLED_DEBUG is off
+  `#define` USER_PRINT(x) DEBUGOUT(x)
+  `#define` USER_PRINTLN(x) DEBUGOUTLN(x)
+  `#define` USER_PRINTF(x...) DEBUGOUTF(x)
   `#ifdef` WLED_ENABLE_NET_DEBUG
     `#define` USER_FLUSH() {}
   `#else`
     `#define` USER_FLUSH() {DEBUGOUTFlush();}
   `#endif`
🤖 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/wled.h` around lines 1019 - 1028, The USER_PRINT*, USER_PRINTLN, and
USER_PRINTF macros currently forward to DEBUG_PRINT* even when only
WLED_ENABLE_NET_DEBUG is defined, which makes them no-ops because DEBUG_PRINT is
compiled out unless WLED_DEBUG is set; change the macro logic so that when
WLED_DEBUG is defined USER_PRINT*/USER_PRINTF map to DEBUG_PRINT*/DEBUG_PRINTF,
but when only WLED_ENABLE_NET_DEBUG is defined they map to the Net-Debug output
functions (do not forward to DEBUG_PRINT); also ensure USER_FLUSH uses the
Net-Debug flush in that branch (keep references to USER_PRINT, USER_PRINTLN,
USER_PRINTF, DEBUG_PRINT, WLED_ENABLE_NET_DEBUG, WLED_DEBUG, USER_FLUSH,
DEBUGOUTFlush to locate and update the macros).

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

Labels

connectivity Issue regarding protocols, WiFi connection or availability of interfaces discussion enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants