Skip to content

Conversation

DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Oct 10, 2025

  • Enabling DDP over WebSocket: this allows for html pages to stream data to the LEDs much faster than through the JSON API
  • Moved the duplicate function to establish a WS connection from the live-view htm files to common.js

uses only 100bytes of additional flash and is a feature I could make use of in the matrix tool.
Also the new custom palette editor has a "preview" feature that currently uses the JSON API to do a live-preview on the LEDs. Using DDP instead would make it much faster.
I ran some experiments doing video streaming from a ESP-Hosted webpage to the LEDs and this also did work quite well.

Summary by CodeRabbit

  • New Features
    • Live View now supports binary WebSocket updates for direct LED color streaming.
  • Performance
    • Chunked frame transmission reduces overhead and improves latency for Live View updates.
  • Compatibility
    • Improved WebSocket handling for deployments behind reverse proxies or custom base paths.
  • Bug Fixes
    • Safer packet validation and bounds checks to prevent malformed binary packets from causing errors.
  • Refactor
    • Consolidated WebSocket setup into a shared script for simpler, more reliable connections.

- Enabling DDP over WebSocket: this allows for UI or html tools to stream data to the LEDs much faster than through the JSON API.
- Moved the duplicate function to establish a WS connection from the live-view htm files to common.js
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds client-side WebSocket helpers (connectWs, sendDDP), updates liveview pages to use them, and implements server-side binary DDP frame handling plus extra bounds checks when parsing E1.31/DDP payloads.

Changes

Cohort / File(s) Summary
Web client WS utilities
wled00/data/common.js
Added connectWs(onOpen) to create/reuse a WebSocket (handles reverse-proxy base paths) and sendDDP(ws, start, len, colors) to send RGB data in framed chunks with a 10-byte DDP header and chunking logic.
Liveview pages WS init refactor
wled00/data/liveview.htm, wled00/data/liveviewws2D.htm
Added <script src="common.js"></script> and replaced manual WebSocket initialization with connectWs(...) callbacks; simplified open/send flow and adjusted message handling to read e.data. Removed legacy URL-construction and explicit binaryType assignments.
Server WS binary DDP handling
wled00/ws.cpp
Added handling for WS_BINARY frames: reads DDP payload length from header bytes, accounts for optional timecode flag, validates packet length against header+payload, and forwards valid packets to handleE131Packet as DDP (P_DDP).
E1.31 DDP bounds check
wled00/e131.cpp
Replaced direct htons(p->dataLen) usage with a uint16_t dataLen local and added bounds-check logic computing numLeds/maxDataIndex; rejects packets where maxDataIndex > dataLen to avoid out-of-bounds parsing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • blazoncek
  • willmmiles

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely identifies the two main changes in the pull request by noting the addition of DDP support over WebSocket and the migration of duplicated WebSocket connection logic into a shared common.js file, making it directly relevant to the described objectives without extraneous detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 492d6b2 and f8727a8.

📒 Files selected for processing (1)
  • wled00/data/common.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/data/common.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)

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

❤️ Share

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
wled00/data/liveviewws2D.htm (1)

36-36: Pre-existing bug: Parameter name mismatch in message handler.

The event listener parameter is named e, but line 36 references event.data instead of e.data. While this may work in some browsers due to the global event object, it's not standard and could fail in strict mode or other environments.

Apply this diff to fix the parameter reference:

-            let leds = new Uint8Array(event.data);
+            let leds = new Uint8Array(e.data);

Note: This bug exists in the original code, not introduced by this PR.

wled00/data/liveview.htm (1)

72-72: Pre-existing bug: Parameter name mismatch in message handler.

Same issue as in liveviewws2D.htm: the event listener parameter is e, but the code references event.data instead of e.data.

Apply this diff:

-            let leds = new Uint8Array(event.data);
+            let leds = new Uint8Array(e.data);

Note: This is a pre-existing bug, not introduced by this PR.

🧹 Nitpick comments (2)
wled00/ws.cpp (1)

74-83: Consider adding DDP header validation.

The current implementation validates packet length but does not verify the DDP magic number or version fields. While the length checks prevent buffer overflows, adding validation of bytes 0-3 would improve robustness and help reject malformed packets early.

For example, verify the DDP header magic bytes before forwarding:

 }else if (info->opcode == WS_BINARY) {
   // interpreted binary data using DDP protocol
   if (len < 10) return; // DDP header is 10 bytes
+  // Validate DDP magic number (0x41 = 'A', typically followed by version/flags)
+  if (data[0] != 0x41) return;
   size_t ddpDataLen = (data[8] << 8) | data[9]; // data length in bytes from DDP header
   if (len < (10 + ddpDataLen)) return; // not enough data, prevent out of bounds read
   // could be a valid DDP packet, forward to handler
   IPAddress clientIP = client->remoteIP();
   e131_packet_t *p = (e131_packet_t*)data;
   handleE131Packet(p, clientIP, P_DDP);
 }
wled00/data/common.js (1)

147-169: Consider adding error handling and return value propagation.

The sendDDP function correctly chunks data and constructs DDP headers, but could be more robust:

  1. The function returns true on success but doesn't catch exceptions from ws.send(), which could throw if the WebSocket is closing.
  2. WebSocket send operations can fail silently when the buffer is full.

Consider wrapping the send operation in a try-catch:

     pkt.set(colors.subarray(cOff, cOff + dLen), 10);
 
-    ws.send(pkt.buffer);
+    try {
+      ws.send(pkt.buffer);
+    } catch (err) {
+      console.error("DDP send failed:", err);
+      return false;
+    }
   }
   return true;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f1f986 and 4090af3.

📒 Files selected for processing (4)
  • wled00/data/common.js (1 hunks)
  • wled00/data/liveview.htm (3 hunks)
  • wled00/data/liveviewws2D.htm (2 hunks)
  • wled00/ws.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/common.js
  • wled00/data/liveviewws2D.htm
  • wled00/data/liveview.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/common.js
  • wled00/data/liveviewws2D.htm
  • wled00/data/liveview.htm
wled00/**/*.cpp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for C++ source files (.cpp)

Files:

  • wled00/ws.cpp
🧬 Code graph analysis (2)
wled00/data/common.js (1)
wled00/data/index.js (13)
  • e (1501-1501)
  • l (223-223)
  • pathn (233-233)
  • paths (234-234)
  • url (1415-1415)
  • i (632-632)
  • i (766-766)
  • i (941-941)
  • i (1473-1473)
  • i (2212-2212)
  • i (2219-2219)
  • i (2236-2236)
  • i (2819-2819)
wled00/ws.cpp (1)
wled00/e131.cpp (2)
  • handleE131Packet (57-120)
  • handleE131Packet (57-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
🔇 Additional comments (4)
wled00/data/common.js (2)

119-140: LGTM! WebSocket connection helper is well-designed.

The connectWs function properly handles:

  • Reusing existing parent WebSocket connections
  • Reverse proxy path detection (consistent with existing code in index.js)
  • Binary frame support via arraybuffer type
  • Optional initialization callbacks

153-153: DDP byte offset calculation is correct

The JS off = (start + i) * 3 sends the byte offset (pixel index × 3 bytes), and the server’s htonl(p->channelOffset)/ddpChannelsPerLed conversion aligns with the DDP header interpretation. No changes required.

wled00/data/liveviewws2D.htm (1)

30-32: LGTM! Clean integration with connectWs.

The refactored WebSocket initialization properly uses the new connectWs helper from common.js and sends the liveview message immediately upon connection.

wled00/data/liveview.htm (1)

65-68: LGTM! Consistent refactoring to use connectWs.

The WebSocket initialization has been properly refactored to use the new connectWs helper, making the code more maintainable and eliminating duplication.

Copy link
Collaborator

@blazoncek blazoncek left a comment

Choose a reason for hiding this comment

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

I hope it will be useful.

wled00/ws.cpp Outdated
size_t ddpDataLen = (data[8] << 8) | data[9]; // data length in bytes from DDP header
if (len < (10 + ddpDataLen)) return; // not enough data, prevent out of bounds read
// could be a valid DDP packet, forward to handler
IPAddress clientIP = client->remoteIP();
Copy link
Collaborator

Choose a reason for hiding this comment

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

realtimeIP = client->remoteIP();

wled00/ws.cpp Outdated
// could be a valid DDP packet, forward to handler
IPAddress clientIP = client->remoteIP();
e131_packet_t *p = (e131_packet_t*)data;
handleE131Packet(p, clientIP, P_DDP);
Copy link
Collaborator

Choose a reason for hiding this comment

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

handleDDPPacket((e131_packet_t*)data);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did that at first, then wanted to support the other protocols and realized they cannot be destinguished reliably just by the data and additional variables over JSON would be needed. Since I only have use for DDP I dropped the others but kept the function call to set the IP, but setting the IP directly and calling handleDDPPacket makes a lot more sense. Thanks for the review.

Copy link
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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4090af3 and 7cf935c.

📒 Files selected for processing (4)
  • wled00/data/common.js (1 hunks)
  • wled00/data/liveview.htm (3 hunks)
  • wled00/e131.cpp (1 hunks)
  • wled00/ws.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • wled00/data/common.js
  • wled00/ws.cpp
🧰 Additional context used
📓 Path-based instructions (3)
wled00/**/*.cpp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for C++ source files (.cpp)

Files:

  • wled00/e131.cpp
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/liveview.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/liveview.htm
🧠 Learnings (2)
📚 Learning: 2025-10-10T16:33:28.649Z
Learnt from: DedeHai
PR: wled/WLED#4997
File: wled00/ws.cpp:74-83
Timestamp: 2025-10-10T16:33:28.649Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), malformed packets must be rejected with comprehensive validation: check timecode flag requires ≥14 bytes total, enforce DDP spec limit of 1440 bytes max data length, and validate that loop iterations won't exceed available data by calculating max index (c + (numLeds-1)*channelsPerLed + channelsPerLed - 1) before accessing the data array.

Applied to files:

  • wled00/e131.cpp
📚 Learning: 2025-08-21T00:00:17.196Z
Learnt from: willmmiles
PR: wled/WLED#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.

Applied to files:

  • wled00/e131.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: wled_build / Build Environments (usermods)
🔇 Additional comments (3)
wled00/data/liveview.htm (3)

72-72: LGTM! Correct usage of ArrayBuffer.

The conversion from e.data to Uint8Array is correct, assuming the binaryType verification passes (see previous comment).


69-80: No action required: binaryType is set
common.js sets ws.binaryType = "arraybuffer", so the ArrayBuffer type check in liveview.htm will work as expected.


20-20: common.js verification complete

common.js is present at wled00/data/common.js, exports the connectWs function, and correctly sets ws.binaryType = "arraybuffer". No further action needed.

Copy link
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: 0

🧹 Nitpick comments (4)
wled00/data/liveviewws2D.htm (2)

30-32: Avoid capturing ws in onOpen; prefer passing socket and JSON.stringify.

If connectWs invokes the callback synchronously, ws may still be undefined. Also prefer JSON.stringify to avoid JSON syntax issues.

Consider this, if connectWs can pass the socket:

- var ws = connectWs(()=>{
-   ws.send('{"lv":true}');
- });
+ var ws = connectWs((socket)=>{
+   socket.send(JSON.stringify({ lv: true }));
+ });

If connectWs does not pass the socket, please confirm the callback is always deferred until after ws is assigned (onopen). Based on learnings.


36-36: Ensure binaryType='arraybuffer' and avoid shadowing.

Require connectWs to set ws.binaryType = 'arraybuffer' so e.data is an ArrayBuffer. Also avoid shadowing the outer leds variable for clarity.

-            let leds = new Uint8Array(e.data);
-            if (leds[0] != 76 || leds[1] != 2 || !ctx) return; //'L', set in ws.cpp
-            let mW = leds[2]; // matrix width
-            let mH = leds[3]; // matrix height
+            const buf = new Uint8Array(e.data);
+            if (buf[0] != 76 || buf[1] != 2 || !ctx) return; //'L', set in ws.cpp
+            let mW = buf[2]; // matrix width
+            let mH = buf[3]; // matrix height
             let pPL = Math.min(c.width / mW, c.height / mH); // pixels per LED (width of circle)
             let lOf = Math.floor((c.width - pPL*mW)/2); //left offset (to center matrix)
             var i = 4;
             for (y=0.5;y<mH;y++) for (x=0.5; x<mW; x++) {
-              ctx.fillStyle = `rgb(${leds[i]},${leds[i+1]},${leds[i+2]})`;
+              ctx.fillStyle = `rgb(${buf[i]},${buf[i+1]},${buf[i+2]})`;
               ctx.beginPath();
               ctx.arc(x*pPL+lOf, y*pPL, pPL*0.4, 0, 2 * Math.PI);
               ctx.fill();
               i+=3;
             }
wled00/data/liveview.htm (2)

20-20: Indent with tabs in web UI files.

The new line is indented with spaces. Please switch to tabs for files under wled00/data.

As per coding guidelines.


65-68: Open-callback pattern + JSON formatting

Good: JSON now uses double quotes. To avoid any race with ws assignment and ensure correctness, prefer passing the socket and using JSON.stringify.

-  ws = connectWs(function () {
-    //console.info("Peek WS open");
-    ws.send('{"lv":true}');
-  });
+  ws = connectWs(function (socket) {
+    //console.info("Peek WS open");
+    socket.send(JSON.stringify({ lv: true }));
+  });

If connectWs doesn’t pass the socket, please confirm the callback is guaranteed to run after ws is assigned (onopen).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf935c and 492d6b2.

📒 Files selected for processing (2)
  • wled00/data/liveview.htm (3 hunks)
  • wled00/data/liveviewws2D.htm (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/liveviewws2D.htm
  • wled00/data/liveview.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/liveviewws2D.htm
  • wled00/data/liveview.htm
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
🔇 Additional comments (3)
wled00/data/liveviewws2D.htm (1)

13-13: Good move to centralize WS logic; remember the UI build step.

Including common.js to dedupe WS connection is 👍. Please run npm run build to regenerate embedded headers before firmware build.

As per coding guidelines.

wled00/data/liveview.htm (2)

72-72: Confirm binaryType is set to arraybuffer.

Ensure connectWs sets ws.binaryType = 'arraybuffer'; otherwise e.data may be a Blob and the ArrayBuffer path won’t run.


87-87: No issues.

Closing tag change is fine.

@DedeHai DedeHai requested a review from netmindz October 11, 2025 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants