-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Adding DDP over WS, moving duplicate WS-connection to common.js #4997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- 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
WalkthroughAdds 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
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. Comment |
There was a problem hiding this 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 referencesevent.data
instead ofe.data
. While this may work in some browsers due to the globalevent
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 ise
, but the code referencesevent.data
instead ofe.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:
- The function returns
true
on success but doesn't catch exceptions fromws.send()
, which could throw if the WebSocket is closing.- 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
📒 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, runnpm 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 correctThe JS
off = (start + i) * 3
sends the byte offset (pixel index × 3 bytes), and the server’shtonl(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 fromcommon.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.
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
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.
There was a problem hiding this 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
📒 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, runnpm 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
toUint8Array
is correct, assuming thebinaryType
verification passes (see previous comment).
69-80
: No action required: binaryType is set
common.js setsws.binaryType = "arraybuffer"
, so the ArrayBuffer type check in liveview.htm will work as expected.
20-20
: common.js verification completecommon.js is present at wled00/data/common.js, exports the
connectWs
function, and correctly setsws.binaryType = "arraybuffer"
. No further action needed.
There was a problem hiding this 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 formattingGood: 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
📒 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, runnpm 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.
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