From 4090af34d988607bd854b8a151d731db386d19e0 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 10 Oct 2025 18:06:08 +0200 Subject: [PATCH 1/8] DDP over WS, adding WS connection to common.js - 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 --- wled00/data/common.js | 51 ++++++++++++++++++++++++++++++++++++ wled00/data/liveview.htm | 28 ++++---------------- wled00/data/liveviewws2D.htm | 22 +++------------- wled00/ws.cpp | 9 +++++++ 4 files changed, 68 insertions(+), 42 deletions(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index 5a98b4fe1f..c34f0400ac 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -116,3 +116,54 @@ function uploadFile(fileObj, name) { fileObj.value = ''; return false; } +// connect to WebSocket, use parent WS or open new +function connectWs(onOpen) { + let ws; + try { + ws = top.window.ws; + if (ws && ws.readyState === WebSocket.OPEN) { + if (onOpen) onOpen(); + return ws; + } + } catch (e) {} + let l = window.location; + let pathn = l.pathname; + let paths = pathn.slice(1, pathn.endsWith('/') ? -1 : undefined).split("/"); + let url = l.origin.replace("http", "ws"); + if (paths.length > 1) { + url += "/" + paths[0]; // add base path for reverse proxy + } + ws = new WebSocket(url + "/ws"); + ws.binaryType = "arraybuffer"; + if (onOpen) { ws.onopen = onOpen; } + return ws; +} + +// send led colors to ESP using WebSocket and DDP protocol (RGB) +// ws: WebSocket object +// start: start pixel index +// len: number of pixels to send +// colors: Uint8Array with RGB values (3*len bytes) +function sendDDP(ws, start, len, colors) { + let maxDDPpx = 480; // must fit into one WebSocket frame of 1450 bytes, DDP header is 10 bytes -> 480 RGB pixels + if (!ws || ws.readyState !== WebSocket.OPEN) return false; + // send in chunks of maxDDPpx + for (let i = 0; i < len; i += maxDDPpx) { + let cnt = Math.min(maxDDPpx, len - i); + let off = (start + i) * 3; + let dLen = cnt * 3; + let cOff = i * 3; + let pkt = new Uint8Array(10 + dLen); + pkt[0] = 0x41; pkt[1] = 0x01; pkt[2] = 0x01; pkt[3] = 0; + pkt[4] = (off >> 24) & 255; + pkt[5] = (off >> 16) & 255; + pkt[6] = (off >> 8) & 255; + pkt[7] = off & 255; + pkt[8] = (dLen >> 8) & 255; + pkt[9] = dLen & 255; + pkt.set(colors.subarray(cOff, cOff + dLen), 10); + + ws.send(pkt.buffer); + } + return true; +} diff --git a/wled00/data/liveview.htm b/wled00/data/liveview.htm index 8c10ba9624..6b753ea2c8 100644 --- a/wled00/data/liveview.htm +++ b/wled00/data/liveview.htm @@ -17,8 +17,8 @@ position: absolute; } + @@ -26,26 +27,9 @@ var ctx = c.getContext('2d'); if (ctx) { // Access the rendering context // use parent WS or open new - var ws; - try { - ws = top.window.ws; - } catch (e) {} - if (ws && ws.readyState === WebSocket.OPEN) { + var ws = connectWs(()=>{ ws.send("{'lv':true}"); - } else { - let l = window.location; - let pathn = l.pathname; - let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/"); - let url = l.origin.replace("http","ws"); - if (paths.length > 1) { - url += "/" + paths[0]; - } - ws = new WebSocket(url+"/ws"); - ws.onopen = ()=>{ - ws.send("{'lv':true}"); - } - } - ws.binaryType = "arraybuffer"; + }); ws.addEventListener('message',(e)=>{ try { if (toString.call(e.data) === '[object ArrayBuffer]') { diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 3a97459fee..0ec818713b 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -71,6 +71,15 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp // force broadcast in 500ms after updating client //lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); // ESP8266 does not like this } + }else if (info->opcode == WS_BINARY) { + // interpreted binary data using DDP protocol + if (len < 10) return; // DDP header is 10 bytes + 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); } } else { //message is comprised of multiple frames or the frame is split into multiple packets From 7cf935c1bc87f4f46275921e8942ccd72c8f7be1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 10 Oct 2025 20:22:51 +0200 Subject: [PATCH 2/8] add better safety check for DDP, bugfix, reformating (tabs) --- wled00/data/common.js | 80 +++++++++++++++++++++------------------- wled00/data/liveview.htm | 2 +- wled00/e131.cpp | 10 ++++- wled00/ws.cpp | 6 +-- 4 files changed, 55 insertions(+), 43 deletions(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index c34f0400ac..12c71c9ca0 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -118,25 +118,25 @@ function uploadFile(fileObj, name) { } // connect to WebSocket, use parent WS or open new function connectWs(onOpen) { - let ws; - try { - ws = top.window.ws; - if (ws && ws.readyState === WebSocket.OPEN) { - if (onOpen) onOpen(); - return ws; - } - } catch (e) {} - let l = window.location; - let pathn = l.pathname; - let paths = pathn.slice(1, pathn.endsWith('/') ? -1 : undefined).split("/"); - let url = l.origin.replace("http", "ws"); - if (paths.length > 1) { - url += "/" + paths[0]; // add base path for reverse proxy - } - ws = new WebSocket(url + "/ws"); - ws.binaryType = "arraybuffer"; - if (onOpen) { ws.onopen = onOpen; } - return ws; + let ws; + try { + ws = top.window.ws; + if (ws && ws.readyState === WebSocket.OPEN) { + if (onOpen) onOpen(); + return ws; + } + } catch (e) {} + let l = window.location; + let pathn = l.pathname; + let paths = pathn.slice(1, pathn.endsWith('/') ? -1 : undefined).split("/"); + let url = l.origin.replace("http", "ws"); + if (paths.length > 1) { + url += "/" + paths[0]; // add base path for reverse proxy + } + ws = new WebSocket(url + "/ws"); + ws.binaryType = "arraybuffer"; + if (onOpen) { ws.onopen = onOpen; } + return ws; } // send led colors to ESP using WebSocket and DDP protocol (RGB) @@ -146,24 +146,28 @@ function connectWs(onOpen) { // colors: Uint8Array with RGB values (3*len bytes) function sendDDP(ws, start, len, colors) { let maxDDPpx = 480; // must fit into one WebSocket frame of 1450 bytes, DDP header is 10 bytes -> 480 RGB pixels - if (!ws || ws.readyState !== WebSocket.OPEN) return false; + if (!ws || ws.readyState !== WebSocket.OPEN) return false; // send in chunks of maxDDPpx - for (let i = 0; i < len; i += maxDDPpx) { - let cnt = Math.min(maxDDPpx, len - i); - let off = (start + i) * 3; - let dLen = cnt * 3; - let cOff = i * 3; - let pkt = new Uint8Array(10 + dLen); - pkt[0] = 0x41; pkt[1] = 0x01; pkt[2] = 0x01; pkt[3] = 0; - pkt[4] = (off >> 24) & 255; - pkt[5] = (off >> 16) & 255; - pkt[6] = (off >> 8) & 255; - pkt[7] = off & 255; - pkt[8] = (dLen >> 8) & 255; - pkt[9] = dLen & 255; - pkt.set(colors.subarray(cOff, cOff + dLen), 10); - - ws.send(pkt.buffer); - } - return true; + for (let i = 0; i < len; i += maxDDPpx) { + let cnt = Math.min(maxDDPpx, len - i); + let off = (start + i) * 3; + let dLen = cnt * 3; + let cOff = i * 3; + let pkt = new Uint8Array(10 + dLen); + pkt[0] = 0x41; pkt[1] = 0x01; pkt[2] = 0x01; pkt[3] = 0; + pkt[4] = (off >> 24) & 255; + pkt[5] = (off >> 16) & 255; + pkt[6] = (off >> 8) & 255; + pkt[7] = off & 255; + pkt[8] = (dLen >> 8) & 255; + pkt[9] = dLen & 255; + pkt.set(colors.subarray(cOff, cOff + dLen), 10); + try { + ws.send(pkt.buffer); + } catch (e) { + console.error(e); + return false; + } + } + return true; } diff --git a/wled00/data/liveview.htm b/wled00/data/liveview.htm index 6b753ea2c8..45fbf43e7f 100644 --- a/wled00/data/liveview.htm +++ b/wled00/data/liveview.htm @@ -69,7 +69,7 @@ ws.addEventListener('message', (e) => { try { if (toString.call(e.data) === '[object ArrayBuffer]') { - let leds = new Uint8Array(event.data); + let leds = new Uint8Array(e.data); if (leds[0] != 76) return; //'L' // leds[1] = 1: 1D; leds[1] = 2: 1D/2D (leds[2]=w, leds[3]=h) draw(leds[1]==2 ? 4 : 2, 3, leds, (a,i) => `rgb(${a[i]},${a[i+1]},${a[i+2]})`); diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 4d7c7b666c..4309bc9ffd 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -30,11 +30,19 @@ void handleDDPPacket(e131_packet_t* p) { uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; start += DMXAddress / ddpChannelsPerLed; - unsigned stop = start + htons(p->dataLen) / ddpChannelsPerLed; + uint16_t dataLen = htons(p->dataLen); + unsigned stop = start + dataLen / ddpChannelsPerLed; uint8_t* data = p->data; unsigned c = 0; if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later + unsigned numLeds = stop - start; // stop >= start is guaranteed + unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array + if (maxDataIndex > dataLen) { + DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting.")); + return; + } + if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 0ec818713b..886517c339 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -75,11 +75,11 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp // interpreted binary data using DDP protocol if (len < 10) return; // DDP header is 10 bytes size_t ddpDataLen = (data[8] << 8) | data[9]; // data length in bytes from DDP header + uint8_t flags = data[0]; + if ((flags & DDP_TIMECODE_FLAG) ) ddpDataLen += 4; // timecode flag adds 4 bytes to data length 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); + handleE131Packet((e131_packet_t*)data, client->remoteIP(), P_DDP); } } else { //message is comprised of multiple frames or the frame is split into multiple packets From 492d6b2080169c987fa58194bac6a1797f01c1b1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 10 Oct 2025 21:16:06 +0200 Subject: [PATCH 3/8] fix rabbit suggestions --- wled00/data/liveview.htm | 2 +- wled00/data/liveviewws2D.htm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/data/liveview.htm b/wled00/data/liveview.htm index 45fbf43e7f..6f54e06c4d 100644 --- a/wled00/data/liveview.htm +++ b/wled00/data/liveview.htm @@ -64,7 +64,7 @@ // Initialize WebSocket connection ws = connectWs(function () { //console.info("Peek WS open"); - ws.send("{'lv':true}"); + ws.send('{"lv":true}'); }); ws.addEventListener('message', (e) => { try { diff --git a/wled00/data/liveviewws2D.htm b/wled00/data/liveviewws2D.htm index 86180c87ed..a077cb5fef 100644 --- a/wled00/data/liveviewws2D.htm +++ b/wled00/data/liveviewws2D.htm @@ -28,12 +28,12 @@ if (ctx) { // Access the rendering context // use parent WS or open new var ws = connectWs(()=>{ - ws.send("{'lv':true}"); + ws.send('{"lv":true}'); }); ws.addEventListener('message',(e)=>{ try { if (toString.call(e.data) === '[object ArrayBuffer]') { - let leds = new Uint8Array(event.data); + 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 From f8727a816bef4360e4eaaa197a32f86d9afb0900 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 12 Oct 2025 08:53:30 +0200 Subject: [PATCH 4/8] use getLoc() instead of redefining its code. --- wled00/data/common.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index 12c71c9ca0..d0b68ac8fd 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -118,22 +118,16 @@ function uploadFile(fileObj, name) { } // connect to WebSocket, use parent WS or open new function connectWs(onOpen) { - let ws; try { - ws = top.window.ws; - if (ws && ws.readyState === WebSocket.OPEN) { + if (top.window.ws && top.window.ws.readyState === WebSocket.OPEN) { if (onOpen) onOpen(); - return ws; + return top.window.ws; } } catch (e) {} - let l = window.location; - let pathn = l.pathname; - let paths = pathn.slice(1, pathn.endsWith('/') ? -1 : undefined).split("/"); - let url = l.origin.replace("http", "ws"); - if (paths.length > 1) { - url += "/" + paths[0]; // add base path for reverse proxy - } - ws = new WebSocket(url + "/ws"); + + getLoc(); // ensure globals (loc, locip, locproto) are up to date + let url = loc ? getURL('/ws').replace("http","ws") : "ws://"+window.location.hostname+"/ws"; + let ws = new WebSocket(url); ws.binaryType = "arraybuffer"; if (onOpen) { ws.onopen = onOpen; } return ws; From 3cc4c8b05bcc4e178322b576ff289d7ff32b9e1f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 20 Oct 2025 07:27:16 +0200 Subject: [PATCH 5/8] added protocol byte, fixed bugs --- wled00/data/common.js | 32 ++++++++++++++++++++------------ wled00/ws.cpp | 36 +++++++++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index d0b68ac8fd..7c59ef6c54 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -139,23 +139,31 @@ function connectWs(onOpen) { // len: number of pixels to send // colors: Uint8Array with RGB values (3*len bytes) function sendDDP(ws, start, len, colors) { - let maxDDPpx = 480; // must fit into one WebSocket frame of 1450 bytes, DDP header is 10 bytes -> 480 RGB pixels + let maxDDPpx = 472; // must fit into one WebSocket frame of 1428 bytes, DDP header is 10+1 bytes -> 472 RGB pixels + //let maxDDPpx = 172; // ESP8266: must fit into one WebSocket frame of 528 bytes -> 172 RGB pixels TODO: add support for ESP8266? if (!ws || ws.readyState !== WebSocket.OPEN) return false; // send in chunks of maxDDPpx for (let i = 0; i < len; i += maxDDPpx) { let cnt = Math.min(maxDDPpx, len - i); - let off = (start + i) * 3; + let off = (start + i) * 3; // DDP pixel offset in bytes let dLen = cnt * 3; - let cOff = i * 3; - let pkt = new Uint8Array(10 + dLen); - pkt[0] = 0x41; pkt[1] = 0x01; pkt[2] = 0x01; pkt[3] = 0; - pkt[4] = (off >> 24) & 255; - pkt[5] = (off >> 16) & 255; - pkt[6] = (off >> 8) & 255; - pkt[7] = off & 255; - pkt[8] = (dLen >> 8) & 255; - pkt[9] = dLen & 255; - pkt.set(colors.subarray(cOff, cOff + dLen), 10); + let cOff = i * 3; // offset in color buffer + let pkt = new Uint8Array(11 + dLen); // DDP header is 10 bytes, plus 1 byte for WLED websocket protocol indicator + pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1 + pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0 + pkt[2] = 0x00; // reserved + pkt[3] = 0x01; // 1 = RGB (currently only supported mode) + pkt[4] = 0x01; // destination id (not used but 0x01 is default output) + pkt[5] = (off >> 24) & 255; // DDP protocl 4-7 is offset + pkt[6] = (off >> 16) & 255; + pkt[7] = (off >> 8) & 255; + pkt[8] = off & 255; + pkt[9] = (dLen >> 8) & 255; // DDP protocol 8-9 is data length + pkt[10] = dLen & 255; + pkt.set(colors.subarray(cOff, cOff + dLen), 11); + if(i + cnt >= len) { + pkt[1] = 0x41; //if this is last packet, set the "push" flag to render the frame + } try { ws.send(pkt.buffer); } catch (e) { diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 886517c339..6a02247203 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -5,6 +5,12 @@ */ #ifdef WLED_ENABLE_WEBSOCKETS +// define some constants for binary protocols, dont use defines but C++ style constexpr +constexpr uint8_t BINARY_PROTOCOL_GENERIC = 0xFF; // generic / auto detect NOT IMPLEMENTED +constexpr uint8_t BINARY_PROTOCOL_E131 = P_E131; // = 0, untested! +constexpr uint8_t BINARY_PROTOCOL_ARTNET = P_ARTNET; // = 1, untested! +constexpr uint8_t BINARY_PROTOCOL_DDP = P_DDP; // = 2 + uint16_t wsLiveClientId = 0; unsigned long wsLastLiveTime = 0; //uint8_t* wsFrameBuffer = nullptr; @@ -25,7 +31,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp // data packet AwsFrameInfo * info = (AwsFrameInfo*)arg; if(info->final && info->index == 0 && info->len == len){ - // the whole message is in a single frame and we got all of its data (max. 1450 bytes) + // the whole message is in a single frame and we got all of its data (max. 1428 bytes / ESP8266: 528 bytes) if(info->opcode == WS_TEXT) { if (len > 0 && len < 10 && data[0] == 'p') { @@ -72,16 +78,28 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp //lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); // ESP8266 does not like this } }else if (info->opcode == WS_BINARY) { - // interpreted binary data using DDP protocol - if (len < 10) return; // DDP header is 10 bytes - size_t ddpDataLen = (data[8] << 8) | data[9]; // data length in bytes from DDP header - uint8_t flags = data[0]; - if ((flags & DDP_TIMECODE_FLAG) ) ddpDataLen += 4; // timecode flag adds 4 bytes to data length - if (len < (10 + ddpDataLen)) return; // not enough data, prevent out of bounds read - // could be a valid DDP packet, forward to handler - handleE131Packet((e131_packet_t*)data, client->remoteIP(), P_DDP); + // first byte determines protocol. Note: since e131_packet_t is "packed", the compiler handles alignment issues + //DEBUG_PRINTF_P(PSTR("WS binary message: len %u, byte0: %u\n"), len, data[0]); + int offset = 1; // offset to skip protocol byte + switch (data[0]) { + case BINARY_PROTOCOL_E131: + handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_E131); + break; + case BINARY_PROTOCOL_ARTNET: + handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_ARTNET); + break; + case BINARY_PROTOCOL_DDP: + if (len < 10 + offset) return; // DDP header is 10 bytes (+1 protocol byte) + size_t ddpDataLen = (data[8+offset] << 8) | data[9+offset]; // data length in bytes from DDP header + uint8_t flags = data[0+offset]; + if ((flags & DDP_TIMECODE_FLAG) ) ddpDataLen += 4; // timecode flag adds 4 bytes to data length + if (len < (10 + offset + ddpDataLen)) return; // not enough data, prevent out of bounds read + // could be a valid DDP packet, forward to handler + handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_DDP); + } } } else { + DEBUG_PRINTF_P(PSTR("WS multipart message: final %u index %u len %u total %u\n"), info->final, info->index, len, (uint32_t)info->len); //message is comprised of multiple frames or the frame is split into multiple packets //if(info->index == 0){ //if (!wsFrameBuffer && len < 4096) wsFrameBuffer = new uint8_t[4096]; From 900babd7f29c14f895e2bce0b26e21d408bb3f62 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 20 Oct 2025 20:07:08 +0200 Subject: [PATCH 6/8] typo --- wled00/data/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index 7c59ef6c54..65929d1025 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -133,7 +133,7 @@ function connectWs(onOpen) { return ws; } -// send led colors to ESP using WebSocket and DDP protocol (RGB) +// send LED colors to ESP using WebSocket and DDP protocol (RGB) // ws: WebSocket object // start: start pixel index // len: number of pixels to send From 5e128cb8f8fd916ccebca1980f78aedfbe004a4c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 21 Oct 2025 19:16:01 +0200 Subject: [PATCH 7/8] add safety check and pass ws to parent --- wled00/data/common.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wled00/data/common.js b/wled00/data/common.js index 65929d1025..b6add7051e 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -130,6 +130,7 @@ function connectWs(onOpen) { let ws = new WebSocket(url); ws.binaryType = "arraybuffer"; if (onOpen) { ws.onopen = onOpen; } + try { top.window.ws = ws; } catch (e) {} // store in parent for reuse return ws; } @@ -139,6 +140,7 @@ function connectWs(onOpen) { // len: number of pixels to send // colors: Uint8Array with RGB values (3*len bytes) function sendDDP(ws, start, len, colors) { + if (!colors || colors.length < len * 3) return false; // not enough color data let maxDDPpx = 472; // must fit into one WebSocket frame of 1428 bytes, DDP header is 10+1 bytes -> 472 RGB pixels //let maxDDPpx = 172; // ESP8266: must fit into one WebSocket frame of 528 bytes -> 172 RGB pixels TODO: add support for ESP8266? if (!ws || ws.readyState !== WebSocket.OPEN) return false; From 4183e60460a65011a6b0b3f2ac9151a8e20ac799 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 21 Oct 2025 19:39:40 +0200 Subject: [PATCH 8/8] typo --- wled00/data/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index b6add7051e..6e72428d56 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -156,7 +156,7 @@ function sendDDP(ws, start, len, colors) { pkt[2] = 0x00; // reserved pkt[3] = 0x01; // 1 = RGB (currently only supported mode) pkt[4] = 0x01; // destination id (not used but 0x01 is default output) - pkt[5] = (off >> 24) & 255; // DDP protocl 4-7 is offset + pkt[5] = (off >> 24) & 255; // DDP protocol 4-7 is offset pkt[6] = (off >> 16) & 255; pkt[7] = (off >> 8) & 255; pkt[8] = off & 255;