From 30fbf55b9af2de826ac7cd9f444f8bf830a13241 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:09:10 +0000
Subject: [PATCH 01/66] Initial plan
From 93908e758fbd4c63b836c9fa9ea802fb1d44c50e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:17:51 +0000
Subject: [PATCH 02/66] Add ESP32 bootloader upgrade functionality with JSON
API support
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
---
wled00/data/update.htm | 24 ++++++-
wled00/fcn_declare.h | 3 +
wled00/json.cpp | 3 +
wled00/wled_server.cpp | 141 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 170 insertions(+), 1 deletion(-)
diff --git a/wled00/data/update.htm b/wled00/data/update.htm
index 783a609ece..60c4f4163d 100644
--- a/wled00/data/update.htm
+++ b/wled00/data/update.htm
@@ -18,13 +18,25 @@
window.open(getURL("/update?revert"),"_self");
}
function GetV() {/*injected values here*/}
+ var isESP32 = false;
+ function checkESP32() {
+ fetch(getURL('/json/info')).then(r=>r.json()).then(d=>{
+ isESP32 = d.arch && d.arch.startsWith('esp32');
+ if (isESP32) {
+ gId('bootloader-section').style.display = 'block';
+ if (d.bootloaderSHA256) {
+ gId('bootloader-hash').innerText = 'Current bootloader SHA256: ' + d.bootloaderSHA256;
+ }
+ }
+ }).catch(e=>console.error(e));
+ }
-
+
WLED Software Update
+
+
+
ESP32 Bootloader Update
+
+
+
Updating...
Please do not close or refresh the page :)
\ No newline at end of file
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h
index 1d81655d6d..ecd65b7018 100644
--- a/wled00/fcn_declare.h
+++ b/wled00/fcn_declare.h
@@ -545,6 +545,9 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h
void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t error);
void serveSettings(AsyncWebServerRequest* request, bool post = false);
void serveSettingsJS(AsyncWebServerRequest* request);
+#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
+String getBootloaderSHA256Hex();
+#endif
//ws.cpp
void handleWs();
diff --git a/wled00/json.cpp b/wled00/json.cpp
index d2b771c590..b2f1072975 100644
--- a/wled00/json.cpp
+++ b/wled00/json.cpp
@@ -817,6 +817,9 @@ void serializeInfo(JsonObject root)
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
#endif
root[F("lwip")] = 0; //deprecated
+ #ifndef WLED_DISABLE_OTA
+ root[F("bootloaderSHA256")] = getBootloaderSHA256Hex();
+ #endif
#else
root[F("arch")] = "esp8266";
root[F("core")] = ESP.getCoreVersion();
diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp
index 75b4ae3f5a..8233dfd74f 100644
--- a/wled00/wled_server.cpp
+++ b/wled00/wled_server.cpp
@@ -18,6 +18,13 @@
#endif
#include "html_cpal.h"
+#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
+ #include
+ #include
+ #include
+ #include
+#endif
+
// define flash strings once (saves flash memory)
static const char s_redirecting[] PROGMEM = "Redirecting...";
static const char s_content_enc[] PROGMEM = "Content-Encoding";
@@ -28,6 +35,12 @@ static const char s_notimplemented[] PROGMEM = "Not implemented";
static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char _common_js[] PROGMEM = "/common.js";
+#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
+// Cache for bootloader SHA256 digest
+static uint8_t bootloaderSHA256[32];
+static bool bootloaderSHA256Cached = false;
+#endif
+
//Is this an IP?
static bool isIp(const String &str) {
for (size_t i = 0; i < str.length(); i++) {
@@ -176,6 +189,61 @@ static String msgProcessor(const String& var)
return String();
}
+#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
+// Calculate and cache the bootloader SHA256 digest
+static void calculateBootloaderSHA256() {
+ if (bootloaderSHA256Cached) return;
+
+ // Bootloader is at fixed offset 0x1000 (4KB) and is typically 32KB
+ const uint32_t bootloaderOffset = 0x1000;
+ const uint32_t bootloaderSize = 0x8000; // 32KB, typical bootloader size
+
+ mbedtls_sha256_context ctx;
+ mbedtls_sha256_init(&ctx);
+ mbedtls_sha256_starts(&ctx, 0); // 0 = SHA256 (not SHA224)
+
+ const size_t chunkSize = 256;
+ uint8_t buffer[chunkSize];
+
+ for (uint32_t offset = 0; offset < bootloaderSize; offset += chunkSize) {
+ size_t readSize = min(chunkSize, bootloaderSize - offset);
+ if (esp_flash_read(NULL, buffer, bootloaderOffset + offset, readSize) == ESP_OK) {
+ mbedtls_sha256_update(&ctx, buffer, readSize);
+ }
+ }
+
+ mbedtls_sha256_finish(&ctx, bootloaderSHA256);
+ mbedtls_sha256_free(&ctx);
+ bootloaderSHA256Cached = true;
+}
+
+// Get bootloader SHA256 as hex string
+static String getBootloaderSHA256Hex() {
+ calculateBootloaderSHA256();
+
+ char hex[65];
+ for (int i = 0; i < 32; i++) {
+ sprintf(hex + (i * 2), "%02x", bootloaderSHA256[i]);
+ }
+ hex[64] = '\0';
+ return String(hex);
+}
+
+// Verify if uploaded data is a valid ESP32 bootloader
+static bool isValidBootloader(const uint8_t* data, size_t len) {
+ if (len < 32) return false;
+
+ // Check for ESP32 bootloader magic byte (0xE9)
+ if (data[0] != 0xE9) return false;
+
+ // Additional validation: check segment count is reasonable
+ uint8_t segmentCount = data[1];
+ if (segmentCount > 16) return false;
+
+ return true;
+}
+#endif
+
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) {
if (!correctPIN) {
if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
@@ -466,6 +534,79 @@ void initServer()
server.on(_update, HTTP_POST, notSupported, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){});
#endif
+#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
+ // ESP32 bootloader update endpoint
+ server.on(F("/updatebootloader"), HTTP_POST, [](AsyncWebServerRequest *request){
+ if (!correctPIN) {
+ serveSettings(request, true); // handle PIN page POST request
+ return;
+ }
+ if (otaLock) {
+ serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
+ return;
+ }
+ if (Update.hasError()) {
+ serveMessage(request, 500, F("Bootloader update failed!"), F("Please check your file and retry!"), 254);
+ } else {
+ serveMessage(request, 200, F("Bootloader updated successfully!"), FPSTR(s_rebooting), 131);
+ doReboot = true;
+ }
+ },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
+ IPAddress client = request->client()->remoteIP();
+ if (((otaSameSubnet && !inSameSubnet(client)) && !strlen(settingsPIN)) || (!otaSameSubnet && !inLocalSubnet(client))) {
+ DEBUG_PRINTLN(F("Attempted bootloader update from different/non-local subnet!"));
+ request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied));
+ return;
+ }
+ if (!correctPIN || otaLock) return;
+
+ if (!index) {
+ DEBUG_PRINTLN(F("Bootloader Update Start"));
+ #if WLED_WATCHDOG_TIMEOUT > 0
+ WLED::instance().disableWatchdog();
+ #endif
+ lastEditTime = millis(); // make sure PIN does not lock during update
+ strip.suspend();
+ strip.resetSegments();
+
+ // Begin bootloader update - use U_FLASH and specify bootloader partition offset
+ if (!Update.begin(0x8000, U_FLASH, -1, 0x1000)) {
+ DEBUG_PRINTLN(F("Bootloader Update Begin Failed"));
+ Update.printError(Serial);
+ }
+ }
+
+ // Verify bootloader magic on first chunk
+ if (index == 0 && !isValidBootloader(data, len)) {
+ DEBUG_PRINTLN(F("Invalid bootloader file!"));
+ Update.abort();
+ strip.resume();
+ #if WLED_WATCHDOG_TIMEOUT > 0
+ WLED::instance().enableWatchdog();
+ #endif
+ return;
+ }
+
+ if (!Update.hasError()) {
+ Update.write(data, len);
+ }
+
+ if (isFinal) {
+ if (Update.end(true)) {
+ DEBUG_PRINTLN(F("Bootloader Update Success"));
+ bootloaderSHA256Cached = false; // Invalidate cached bootloader hash
+ } else {
+ DEBUG_PRINTLN(F("Bootloader Update Failed"));
+ Update.printError(Serial);
+ strip.resume();
+ #if WLED_WATCHDOG_TIMEOUT > 0
+ WLED::instance().enableWatchdog();
+ #endif
+ }
+ }
+ });
+#endif
+
#ifdef WLED_ENABLE_DMX
server.on(F("/dmxmap"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, FPSTR(CONTENT_TYPE_HTML), PAGE_dmxmap, dmxProcessor);
From a18a661c73036136d3e1c6515d51ed17ede7ce7e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:20:14 +0000
Subject: [PATCH 03/66] Add esp_flash.h include for ESP32 bootloader flash
operations
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
---
wled00/wled_server.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp
index 8233dfd74f..601162def1 100644
--- a/wled00/wled_server.cpp
+++ b/wled00/wled_server.cpp
@@ -21,6 +21,7 @@
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
#include
#include
+ #include
#include
#include
#endif
From c3e18905c17e9c1f3ee5308b82ff38dc0312f410 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:24:29 +0000
Subject: [PATCH 04/66] Improve bootloader flash implementation with proper
erase and write operations
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
---
wled00/wled_server.cpp | 65 ++++++++++++++++++++++++++++++------------
1 file changed, 46 insertions(+), 19 deletions(-)
diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp
index 601162def1..6eb6d1efe3 100644
--- a/wled00/wled_server.cpp
+++ b/wled00/wled_server.cpp
@@ -561,6 +561,11 @@ void initServer()
}
if (!correctPIN || otaLock) return;
+ static size_t bootloaderBytesWritten = 0;
+ static bool bootloaderErased = false;
+ const uint32_t bootloaderOffset = 0x1000;
+ const uint32_t maxBootloaderSize = 0x8000; // 32KB max
+
if (!index) {
DEBUG_PRINTLN(F("Bootloader Update Start"));
#if WLED_WATCHDOG_TIMEOUT > 0
@@ -569,36 +574,58 @@ void initServer()
lastEditTime = millis(); // make sure PIN does not lock during update
strip.suspend();
strip.resetSegments();
+ bootloaderBytesWritten = 0;
+ bootloaderErased = false;
- // Begin bootloader update - use U_FLASH and specify bootloader partition offset
- if (!Update.begin(0x8000, U_FLASH, -1, 0x1000)) {
- DEBUG_PRINTLN(F("Bootloader Update Begin Failed"));
- Update.printError(Serial);
+ // Verify bootloader magic on first chunk
+ if (!isValidBootloader(data, len)) {
+ DEBUG_PRINTLN(F("Invalid bootloader file!"));
+ strip.resume();
+ #if WLED_WATCHDOG_TIMEOUT > 0
+ WLED::instance().enableWatchdog();
+ #endif
+ Update.abort();
+ return;
+ }
+
+ // Erase bootloader region (32KB)
+ DEBUG_PRINTLN(F("Erasing bootloader region..."));
+ esp_err_t err = esp_flash_erase_region(NULL, bootloaderOffset, maxBootloaderSize);
+ if (err != ESP_OK) {
+ DEBUG_PRINTF_P(PSTR("Bootloader erase error: %d\n"), err);
+ strip.resume();
+ #if WLED_WATCHDOG_TIMEOUT > 0
+ WLED::instance().enableWatchdog();
+ #endif
+ Update.abort();
+ return;
}
+ bootloaderErased = true;
}
- // Verify bootloader magic on first chunk
- if (index == 0 && !isValidBootloader(data, len)) {
- DEBUG_PRINTLN(F("Invalid bootloader file!"));
+ // Write data to flash at bootloader offset
+ if (bootloaderErased && bootloaderBytesWritten + len <= maxBootloaderSize) {
+ esp_err_t err = esp_flash_write(NULL, data, bootloaderOffset + bootloaderBytesWritten, len);
+ if (err != ESP_OK) {
+ DEBUG_PRINTF_P(PSTR("Bootloader flash write error: %d\n"), err);
+ Update.abort();
+ } else {
+ bootloaderBytesWritten += len;
+ }
+ } else if (!bootloaderErased) {
+ DEBUG_PRINTLN(F("Bootloader region not erased!"));
+ Update.abort();
+ } else {
+ DEBUG_PRINTLN(F("Bootloader size exceeds maximum!"));
Update.abort();
- strip.resume();
- #if WLED_WATCHDOG_TIMEOUT > 0
- WLED::instance().enableWatchdog();
- #endif
- return;
- }
-
- if (!Update.hasError()) {
- Update.write(data, len);
}
if (isFinal) {
- if (Update.end(true)) {
- DEBUG_PRINTLN(F("Bootloader Update Success"));
+ if (!Update.hasError() && bootloaderBytesWritten > 0) {
+ DEBUG_PRINTF_P(PSTR("Bootloader Update Success - %d bytes written\n"), bootloaderBytesWritten);
bootloaderSHA256Cached = false; // Invalidate cached bootloader hash
} else {
DEBUG_PRINTLN(F("Bootloader Update Failed"));
- Update.printError(Serial);
strip.resume();
#if WLED_WATCHDOG_TIMEOUT > 0
WLED::instance().enableWatchdog();
From 042ed39464e2ad2e217f0bdcd25b6572eb2d0e00 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:57:24 +0000
Subject: [PATCH 05/66] Fix: Remove static keyword from
getBootloaderSHA256Hex() to match declaration
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
---
wled00/wled_server.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp
index 6eb6d1efe3..7286982fbf 100644
--- a/wled00/wled_server.cpp
+++ b/wled00/wled_server.cpp
@@ -219,7 +219,7 @@ static void calculateBootloaderSHA256() {
}
// Get bootloader SHA256 as hex string
-static String getBootloaderSHA256Hex() {
+String getBootloaderSHA256Hex() {
calculateBootloaderSHA256();
char hex[65];
From f5f3fc338fecf7357854a8608b853b0aefa4c8d4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 15:12:46 +0000
Subject: [PATCH 06/66] Fix: Cast min() arguments to size_t for ESP32-C3
compatibility
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
---
wled00/wled_server.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp
index 7286982fbf..5fa773b83d 100644
--- a/wled00/wled_server.cpp
+++ b/wled00/wled_server.cpp
@@ -207,7 +207,7 @@ static void calculateBootloaderSHA256() {
uint8_t buffer[chunkSize];
for (uint32_t offset = 0; offset < bootloaderSize; offset += chunkSize) {
- size_t readSize = min(chunkSize, bootloaderSize - offset);
+ size_t readSize = min((size_t)(bootloaderSize - offset), chunkSize);
if (esp_flash_read(NULL, buffer, bootloaderOffset + offset, readSize) == ESP_OK) {
mbedtls_sha256_update(&ctx, buffer, readSize);
}
From d79b02379e42517832b17dbedf59e8a21b24854f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 15:39:43 +0000
Subject: [PATCH 07/66] Fix: Move bootloader JavaScript to separate script
block to avoid GetV() injection removal
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
---
wled00/data/update.htm | 2 ++
1 file changed, 2 insertions(+)
diff --git a/wled00/data/update.htm b/wled00/data/update.htm
index 60c4f4163d..8c360c7809 100644
--- a/wled00/data/update.htm
+++ b/wled00/data/update.htm
@@ -18,6 +18,8 @@
window.open(getURL("/update?revert"),"_self");
}
function GetV() {/*injected values here*/}
+
+
@@ -26,30 +27,13 @@
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) {
- 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";
+ var ws = connectWs(()=>{
+ 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
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 3a97459fee..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') {
@@ -71,8 +77,29 @@ 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) {
+ // 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 eb80fdf733c006c4610a57f3386bf88907a86a26 Mon Sep 17 00:00:00 2001
From: Brandon502 <105077712+Brandon502@users.noreply.github.com>
Date: Wed, 8 Oct 2025 22:48:53 -0400
Subject: [PATCH 19/66] Game of Life Rework
RAM and speed optimizations. Better repeat detection. Mutation toggle. Blur option added.
---
wled00/FX.cpp | 243 ++++++++++++++++++++++++++++++--------------------
1 file changed, 147 insertions(+), 96 deletions(-)
diff --git a/wled00/FX.cpp b/wled00/FX.cpp
index 5b29b48ddd..4bdef3539e 100644
--- a/wled00/FX.cpp
+++ b/wled00/FX.cpp
@@ -5196,112 +5196,163 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f
///////////////////////////////////////////
// 2D Cellular Automata Game of life //
///////////////////////////////////////////
-typedef struct ColorCount {
- CRGB color;
- int8_t count;
-} colorCount;
+typedef struct Cell {
+ uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2;
+} Cell;
-uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color
+uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/
+ // and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up
+ const int cols = SEG_W, rows = SEG_H;
+ const unsigned maxIndex = cols * rows;
- const int cols = SEG_W;
- const int rows = SEG_H;
- const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
- const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled
- const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi)
-
- if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed
- CRGB *prevLeds = reinterpret_cast(SEGENV.data);
- uint16_t *crcBuffer = reinterpret_cast(SEGENV.data + dataSize);
-
- CRGB backgroundColor = SEGCOLOR(1);
-
- if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) {
- SEGENV.step = strip.now;
- SEGENV.aux0 = 0;
-
- //give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen)
- for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
- unsigned state = hw_random8()%2;
- if (state == 0)
- SEGMENT.setPixelColorXY(x,y, backgroundColor);
- else
- SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
+ if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed
+
+ Cell *cells = reinterpret_cast (SEGENV.data);
+
+ uint16_t& generation = SEGENV.aux0, &gliderLength = SEGENV.aux1; // rename aux variables for clarity
+ bool mutate = SEGMENT.check3;
+ uint8_t blur = map(SEGMENT.custom1, 0, 255, 255, 4);
+
+ uint32_t bgColor = SEGCOLOR(1);
+ uint32_t birthColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 255);
+
+ bool setup = SEGENV.call == 0;
+ if (setup) {
+ // Calculate glider length LCM(rows,cols)*4 once
+ unsigned a = rows, b = cols;
+ while (b) { unsigned t = b; b = a % b; a = t; }
+ gliderLength = (cols * rows / a) << 2;
+ }
+
+ if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix
+ bool paused = SEGENV.step > strip.now;
+
+ // Setup New Game of Life
+ if ((!paused && generation == 0) || setup) {
+ SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds
+ generation = 1;
+ paused = true;
+ //Setup Grid
+ memset(cells, 0, maxIndex * sizeof(Cell));
+
+ for (unsigned i = maxIndex; i--; ) {
+ bool isAlive = !hw_random8(3); // ~33%
+ cells[i].alive = isAlive;
+ cells[i].faded = !isAlive;
+ unsigned x = i % cols, y = i / cols;
+ cells[i].edgeCell = (x == 0 || x == cols-1 || y == 0 || y == rows-1);
+
+ SEGMENT.setPixelColorXY(x, y, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor);
+ }
+ }
+
+ if (paused || (strip.now - SEGENV.step < 1000 / map(SEGMENT.speed,0,255,1,42))) {
+ // Redraw if paused or between updates to remove blur
+ for (unsigned i = maxIndex; i--; ) {
+ if (!cells[i].alive) {
+ uint32_t cellColor = SEGMENT.getPixelColorXY(i % cols, i / cols);
+ if (cellColor != bgColor) {
+ uint32_t newColor;
+ bool needsColor = false;
+ if (cells[i].faded) { newColor = bgColor; needsColor = true; }
+ else {
+ uint32_t blended = color_blend(cellColor, bgColor, 2);
+ if (blended == cellColor) { blended = bgColor; cells[i].faded = 1; }
+ newColor = blended; needsColor = true;
+ }
+ if (needsColor) SEGMENT.setPixelColorXY(i % cols, i / cols, newColor);
+ }
+ }
}
-
- for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black;
- memset(crcBuffer, 0, sizeof(uint16_t)*crcBufferLen);
- } else if (strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,4)) {
- // update only when appropriate time passes (in 42 FPS slots)
return FRAMETIME;
- }
+
+ }
+
+ // Repeat detection
+ bool updateOscillator = generation % 16 == 0;
+ bool updateSpaceship = gliderLength && generation % gliderLength == 0;
+ bool repeatingOscillator = true, repeatingSpaceship = true, emptyGrid = true;
+
+ unsigned cIndex = maxIndex-1;
+ for (unsigned y = rows; y--; ) for (unsigned x = cols; x--; cIndex--) {
+ Cell& cell = cells[cIndex];
+
+ if (cell.alive) emptyGrid = false;
+ if (cell.oscillatorCheck != cell.alive) repeatingOscillator = false;
+ if (cell.spaceshipCheck != cell.alive) repeatingSpaceship = false;
+ if (updateOscillator) cell.oscillatorCheck = cell.alive;
+ if (updateSpaceship) cell.spaceshipCheck = cell.alive;
+
+ unsigned neighbors = 0, aliveParents = 0, parentIdx[3];
+ // Count alive neighbors
+ for (int i = 1; i >= -1; i--) for (int j = 1; j >= -1; j--) if (i || j) {
+ int nX = x + j, nY = y + i;
+ if (cell.edgeCell) {
+ nX = (nX + cols) % cols;
+ nY = (nY + rows) % rows;
+ }
+ unsigned nIndex = nX + nY * cols;
+ Cell& neighbor = cells[nIndex];
+ if (neighbor.alive) {
+ if (++neighbors > 3) break;
+ if (!neighbor.toggleStatus) { // Alive and not dying
+ parentIdx[aliveParents++] = nIndex;
+ }
+ }
+ }
- //copy previous leds (save previous generation)
- //NOTE: using lossy getPixelColor() is a benefit as endlessly repeating patterns will eventually fade out causing a reset
- for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) prevLeds[XY(x,y)] = SEGMENT.getPixelColorXY(x,y);
-
- //calculate new leds
- for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
-
- colorCount colorsCount[9]; // count the different colors in the 3*3 matrix
- for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; // init colorsCount
-
- // iterate through neighbors and count them and their different colors
- int neighbors = 0;
- for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix
- if (i==0 && j==0) continue; // ignore itself
- // wrap around segment
- int xx = x+i, yy = y+j;
- if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0;
- if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0;
-
- unsigned xy = XY(xx, yy); // previous cell xy to check
- // count different neighbours and colors
- if (prevLeds[xy] != backgroundColor) {
- neighbors++;
- bool colorFound = false;
- int k;
- for (k=0; k<9 && colorsCount[k].count != 0; k++)
- if (colorsCount[k].color == prevLeds[xy]) {
- colorsCount[k].count++;
- colorFound = true;
- }
- if (!colorFound) colorsCount[k] = {prevLeds[xy], 1}; //add new color found in the array
+ uint32_t newColor;
+ bool needsColor = false;
+
+ if (cell.alive && (neighbors < 2 || neighbors > 3)) { // Loneliness or Overpopulation
+ cell.toggleStatus = 1;
+ if (blur == 255) cell.faded = 1;
+ newColor = cell.faded ? bgColor : color_blend(SEGMENT.getPixelColorXY(x, y), bgColor, blur);
+ needsColor = true;
+ }
+ else if (!cell.alive) {
+ if (neighbors == 3 && (!mutate || hw_random8(128)) || // Normal birth with 1/128 failure chance if mutate
+ (mutate && neighbors == 2 && !hw_random8(128))) { // Mutation birth with 2 neighbors with 1/128 chance if mutate
+ cell.toggleStatus = 1;
+ cell.faded = 0;
+
+ if (aliveParents) {
+ // Set color based on random neighbor
+ unsigned parentIndex = parentIdx[random8(aliveParents)];
+ birthColor = SEGMENT.getPixelColorXY(parentIndex % cols, parentIndex / cols);
+ }
+ newColor = birthColor;
+ needsColor = true;
+ }
+ else if (!cell.faded) {// No change, fade dead cells
+ uint32_t cellColor = SEGMENT.getPixelColorXY(x, y);
+ uint32_t blended = color_blend(cellColor, bgColor, blur);
+ if (blended == cellColor) { blended = bgColor; cell.faded = 1; }
+ newColor = blended;
+ needsColor = true;
}
- } // i,j
-
- // Rules of Life
- uint32_t col = uint32_t(prevLeds[XY(x,y)]) & 0x00FFFFFF; // uint32_t operator returns RGBA, we want RGBW -> cut off "alpha" byte
- uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0);
- if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness
- else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation
- else if ((col == bgc) && (neighbors == 3)) { // Reproduction
- // find dominant color and assign it to a cell
- colorCount dominantColorCount = {backgroundColor, 0};
- for (int i=0; i<9 && colorsCount[i].count != 0; i++)
- if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i];
- // assign the dominant color w/ a bit of randomness to avoid "gliders"
- if (dominantColorCount.count > 0 && hw_random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color);
- } else if ((col == bgc) && (neighbors == 2) && !hw_random8(128)) { // Mutation
- SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
- }
- // else do nothing!
- } //x,y
-
- // calculate CRC16 of leds
- uint16_t crc = crc16((const unsigned char*)prevLeds, dataSize);
- // check if we had same CRC and reset if needed
- bool repetition = false;
- for (int i=0; i
Date: Tue, 14 Oct 2025 22:58:22 -0400
Subject: [PATCH 20/66] Game of Life Optimizations
Adjust mutation logic. Use 1D get/set. Reduce code size.
---
wled00/FX.cpp | 45 ++++++++++++++++++++++-----------------------
1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/wled00/FX.cpp b/wled00/FX.cpp
index 4bdef3539e..f0f4276f27 100644
--- a/wled00/FX.cpp
+++ b/wled00/FX.cpp
@@ -5197,7 +5197,7 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f
// 2D Cellular Automata Game of life //
///////////////////////////////////////////
typedef struct Cell {
- uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2;
+ uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2;
} Cell;
uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/
@@ -5207,7 +5207,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
const unsigned maxIndex = cols * rows;
if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed
-
+
Cell *cells = reinterpret_cast (SEGENV.data);
uint16_t& generation = SEGENV.aux0, &gliderLength = SEGENV.aux1; // rename aux variables for clarity
@@ -5230,20 +5230,20 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
// Setup New Game of Life
if ((!paused && generation == 0) || setup) {
- SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds
+ SEGENV.step = strip.now + 1280; // show initial state for 1.28 seconds
generation = 1;
paused = true;
//Setup Grid
memset(cells, 0, maxIndex * sizeof(Cell));
- for (unsigned i = maxIndex; i--; ) {
+ for (unsigned i = 0; i < maxIndex; i++) {
bool isAlive = !hw_random8(3); // ~33%
cells[i].alive = isAlive;
cells[i].faded = !isAlive;
unsigned x = i % cols, y = i / cols;
cells[i].edgeCell = (x == 0 || x == cols-1 || y == 0 || y == rows-1);
- SEGMENT.setPixelColorXY(x, y, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor);
+ SEGMENT.setPixelColor(i, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor);
}
}
@@ -5251,22 +5251,21 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
// Redraw if paused or between updates to remove blur
for (unsigned i = maxIndex; i--; ) {
if (!cells[i].alive) {
- uint32_t cellColor = SEGMENT.getPixelColorXY(i % cols, i / cols);
+ uint32_t cellColor = SEGMENT.getPixelColor(i);
if (cellColor != bgColor) {
uint32_t newColor;
bool needsColor = false;
- if (cells[i].faded) { newColor = bgColor; needsColor = true; }
+ if (cells[i].faded) { newColor = bgColor; needsColor = true; }
else {
uint32_t blended = color_blend(cellColor, bgColor, 2);
if (blended == cellColor) { blended = bgColor; cells[i].faded = 1; }
newColor = blended; needsColor = true;
}
- if (needsColor) SEGMENT.setPixelColorXY(i % cols, i / cols, newColor);
+ if (needsColor) SEGMENT.setPixelColor(i, newColor);
}
}
}
return FRAMETIME;
-
}
// Repeat detection
@@ -5286,8 +5285,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
unsigned neighbors = 0, aliveParents = 0, parentIdx[3];
// Count alive neighbors
- for (int i = 1; i >= -1; i--) for (int j = 1; j >= -1; j--) if (i || j) {
- int nX = x + j, nY = y + i;
+ for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) if (i || j) {
+ int nX = x + j, nY = y + i;
if (cell.edgeCell) {
nX = (nX + cols) % cols;
nY = (nY + rows) % rows;
@@ -5295,8 +5294,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
unsigned nIndex = nX + nY * cols;
Cell& neighbor = cells[nIndex];
if (neighbor.alive) {
- if (++neighbors > 3) break;
- if (!neighbor.toggleStatus) { // Alive and not dying
+ neighbors++;
+ if (!neighbor.toggleStatus && neighbors < 4) { // Alive and not dying
parentIdx[aliveParents++] = nIndex;
}
}
@@ -5304,29 +5303,29 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
uint32_t newColor;
bool needsColor = false;
-
+
if (cell.alive && (neighbors < 2 || neighbors > 3)) { // Loneliness or Overpopulation
cell.toggleStatus = 1;
if (blur == 255) cell.faded = 1;
- newColor = cell.faded ? bgColor : color_blend(SEGMENT.getPixelColorXY(x, y), bgColor, blur);
+ newColor = cell.faded ? bgColor : color_blend(SEGMENT.getPixelColor(cIndex), bgColor, blur);
needsColor = true;
}
else if (!cell.alive) {
- if (neighbors == 3 && (!mutate || hw_random8(128)) || // Normal birth with 1/128 failure chance if mutate
- (mutate && neighbors == 2 && !hw_random8(128))) { // Mutation birth with 2 neighbors with 1/128 chance if mutate
+ byte mutationRoll = mutate ? hw_random8(128) : 1; // if 0: 3 neighbor births fail and 2 neighbor births mutate
+ if ((neighbors == 3 && mutationRoll) || (mutate && neighbors == 2 && !mutationRoll)) { // Reproduction or Mutation
cell.toggleStatus = 1;
cell.faded = 0;
-
+
if (aliveParents) {
// Set color based on random neighbor
unsigned parentIndex = parentIdx[random8(aliveParents)];
- birthColor = SEGMENT.getPixelColorXY(parentIndex % cols, parentIndex / cols);
+ birthColor = SEGMENT.getPixelColor(parentIndex);
}
newColor = birthColor;
needsColor = true;
}
else if (!cell.faded) {// No change, fade dead cells
- uint32_t cellColor = SEGMENT.getPixelColorXY(x, y);
+ uint32_t cellColor = SEGMENT.getPixelColor(cIndex);
uint32_t blended = color_blend(cellColor, bgColor, blur);
if (blended == cellColor) { blended = bgColor; cell.faded = 1; }
newColor = blended;
@@ -5334,7 +5333,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
}
}
- if (needsColor) SEGMENT.setPixelColorXY(x, y, newColor);
+ if (needsColor) SEGMENT.setPixelColor(cIndex, newColor);
}
// Loop through cells, if toggle, swap alive status
for (unsigned i = maxIndex; i--; ) {
@@ -5344,8 +5343,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
if (repeatingOscillator || repeatingSpaceship || emptyGrid) {
generation = 0; // reset on next call
- SEGENV.step += 1000; // pause final generation for 1 second
- }
+ SEGENV.step += 1024; // pause final generation for ~1 second
+ }
else {
++generation;
SEGENV.step = strip.now;
From 7e1992fc5c2e792463c3ad127aaff4f58615383f Mon Sep 17 00:00:00 2001
From: Damian Schneider
Date: Sun, 19 Oct 2025 07:08:18 +0200
Subject: [PATCH 21/66] adding function to check if a backup exists
---
wled00/cfg.cpp | 4 ++++
wled00/fcn_declare.h | 2 ++
wled00/file.cpp | 6 ++++++
3 files changed, 12 insertions(+)
diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp
index 840afb51ba..b5a0574403 100644
--- a/wled00/cfg.cpp
+++ b/wled00/cfg.cpp
@@ -777,6 +777,10 @@ bool verifyConfig() {
return validateJsonFile(s_cfg_json);
}
+bool configBackupExists() {
+ return checkBackupExists(s_cfg_json);
+}
+
// rename config file and reboot
// if the cfg file doesn't exist, such as after a reset, do nothing
void resetConfig() {
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h
index ecd65b7018..e78cf048a7 100644
--- a/wled00/fcn_declare.h
+++ b/wled00/fcn_declare.h
@@ -27,6 +27,7 @@ void IRAM_ATTR touchButtonISR();
bool backupConfig();
bool restoreConfig();
bool verifyConfig();
+bool configBackupExists();
void resetConfig();
bool deserializeConfig(JsonObject doc, bool fromFS = false);
bool deserializeConfigFromFS();
@@ -103,6 +104,7 @@ inline bool readObjectFromFile(const String &file, const char* key, JsonDocument
bool copyFile(const char* src_path, const char* dst_path);
bool backupFile(const char* filename);
bool restoreFile(const char* filename);
+bool checkBackupExists(const char* filename);
bool validateJsonFile(const char* filename);
void dumpFilesToSerial();
diff --git a/wled00/file.cpp b/wled00/file.cpp
index 9f1dd62256..ba406ba3b5 100644
--- a/wled00/file.cpp
+++ b/wled00/file.cpp
@@ -557,6 +557,12 @@ bool restoreFile(const char* filename) {
return false;
}
+bool checkBackupExists(const char* filename) {
+ char backupname[32];
+ snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
+ return WLED_FS.exists(backupname);
+}
+
bool validateJsonFile(const char* filename) {
if (!WLED_FS.exists(filename)) return false;
File file = WLED_FS.open(filename, "r");
From 46ff43889b4246091c0af7239a391f1fea6bf7b2 Mon Sep 17 00:00:00 2001
From: Damian Schneider
Date: Sun, 19 Oct 2025 07:10:05 +0200
Subject: [PATCH 22/66] check config backup as welcome page gate
---
wled00/wled.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/wled00/wled.cpp b/wled00/wled.cpp
index 923688106d..cd21d8d84e 100644
--- a/wled00/wled.cpp
+++ b/wled00/wled.cpp
@@ -474,7 +474,7 @@ void WLED::setup()
if (needsCfgSave) serializeConfigToFS(); // usermods required new parameters; need to wait for strip to be initialised #4752
- if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0)
+ if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0 && !configBackupExists())
showWelcomePage = true;
WiFi.persistent(false);
WiFi.onEvent(WiFiEvent);
From 0f06535932007fc09cc01a42b642c3c28ef99ac2 Mon Sep 17 00:00:00 2001
From: Will Tatam
Date: Thu, 25 Sep 2025 10:54:58 +0100
Subject: [PATCH 23/66] Include audioreactive for hub75 examples
---
platformio_override.sample.ini | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini
index d897a494de..8419aba8c7 100644
--- a/platformio_override.sample.ini
+++ b/platformio_override.sample.ini
@@ -541,12 +541,15 @@ build_flags = ${common.build_flags}
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D WLED_DEBUG_BUS
; -D WLED_DEBUG
+ -D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
+
lib_deps = ${esp32_idf_V4.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
+custom_usermods = audioreactive
[env:esp32dev_hub75_forum_pinout]
extends = env:esp32dev_hub75
@@ -555,10 +558,10 @@ build_flags = ${common.build_flags}
-D WLED_ENABLE_HUB75MATRIX -D NO_GFX
-D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins
-D WLED_DEBUG_BUS
+ -D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
; -D WLED_DEBUG
-
[env:adafruit_matrixportal_esp32s3]
; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75
board = adafruit_matrixportal_esp32s3
@@ -575,6 +578,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
-D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3
-D WLED_DEBUG_BUS
+ -D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
lib_deps = ${esp32s3.lib_deps}
@@ -584,6 +588,7 @@ board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
+custom_usermods = audioreactive
[env:esp32S3_PSRAM_HUB75]
;; MOONHUB HUB75 adapter board
@@ -601,6 +606,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
-D WLED_DEBUG_BUS
+ -D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
lib_deps = ${esp32s3.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
@@ -609,3 +615,4 @@ board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = qio
monitor_filters = esp32_exception_decoder
+custom_usermods = audioreactive
\ No newline at end of file
From 8e00e7175c70df449b02b7b41d69974235ee24a1 Mon Sep 17 00:00:00 2001
From: Will Tatam
Date: Fri, 26 Sep 2025 19:34:18 +0100
Subject: [PATCH 24/66] Include audioreactive for hub75 examples - MOONHUB
audio
---
platformio_override.sample.ini | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini
index 8419aba8c7..249b101059 100644
--- a/platformio_override.sample.ini
+++ b/platformio_override.sample.ini
@@ -606,7 +606,8 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=
-D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips
-D MOONHUB_S3_PINOUT ;; HUB75 pinout
-D WLED_DEBUG_BUS
- -D SR_DMTYPE=-1 -D I2S_SDPIN=-1 -D I2S_CKPIN=-1 -D I2S_WSPIN=-1 -D MCLK_PIN=-1 ;; Disable to prevent pin clash
+ -D LEDPIN=14 -D BTNPIN=0 -D RLYPIN=15 -D IRPIN=-1 -D AUDIOPIN=-1 ;; defaults that avoid pin conflicts with HUB75
+ -D SR_DMTYPE=1 -D I2S_SDPIN=10 -D I2S_CKPIN=11 -D I2S_WSPIN=12 -D MCLK_PIN=-1 ;; I2S mic
lib_deps = ${esp32s3.lib_deps}
https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#aa28e2a ;; S3_LCD_DIV_NUM fix
From 5fb37130f825b53802d0e49683e1d5a0d6f72d29 Mon Sep 17 00:00:00 2001
From: Will Tatam
Date: Fri, 7 Nov 2025 15:42:47 +0000
Subject: [PATCH 25/66] Include esp32 debug build
---
platformio.ini | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/platformio.ini b/platformio.ini
index c354de2698..6cb401e841 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -10,7 +10,25 @@
# ------------------------------------------------------------------------------
# CI/release binaries
-default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
+default_envs = nodemcuv2
+ esp8266_2m
+ esp01_1m_full
+ nodemcuv2_160
+ esp8266_2m_160
+ esp01_1m_full_160
+ nodemcuv2_compat
+ esp8266_2m_compat
+ esp01_1m_full_compat
+ esp32dev
+ esp32dev_debug
+ esp32_eth
+ esp32_wrover
+ lolin_s2_mini
+ esp32c3dev
+ esp32s3dev_16MB_opi
+ esp32s3dev_8MB_opi
+ esp32s3_4M_qspi
+ usermods
src_dir = ./wled00
data_dir = ./wled00/data
@@ -438,6 +456,13 @@ monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio
+[env:esp32dev_debug]
+extends = env:esp32dev
+build_unflags = -D WLED_RELEASE_NAME=\"ESP32_V4\"
+build_flags = ${env:esp32dev.build_flags}
+ -D WLED_DEBUG
+ -D WLED_RELEASE_NAME=\"ESP32_DEBUG\"
+
[env:esp32dev_8M]
board = esp32dev
platform = ${esp32_idf_V4.platform}
From acd415c522205364897bca46e467ac806538f5be Mon Sep 17 00:00:00 2001
From: Will Tatam
Date: Fri, 7 Nov 2025 18:29:03 +0000
Subject: [PATCH 26/66] fix release name for esp32
---
platformio.ini | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platformio.ini b/platformio.ini
index 6cb401e841..e0f03e9355 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -449,7 +449,7 @@ board = esp32dev
platform = ${esp32_idf_V4.platform}
build_unflags = ${common.build_unflags}
custom_usermods = audioreactive
-build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-D WLED_DISABLE_BROWNOUT_DET
+build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
lib_deps = ${esp32_idf_V4.lib_deps}
monitor_filters = esp32_exception_decoder
From c623b826989b243a69c995efe17edfacc64af6e6 Mon Sep 17 00:00:00 2001
From: Will Tatam
Date: Sat, 8 Nov 2025 15:17:21 +0000
Subject: [PATCH 27/66] improve esp32_dev env
---
platformio.ini | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/platformio.ini b/platformio.ini
index e0f03e9355..cdf4c2bd32 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -458,7 +458,8 @@ board_build.flash_mode = dio
[env:esp32dev_debug]
extends = env:esp32dev
-build_unflags = -D WLED_RELEASE_NAME=\"ESP32_V4\"
+upload_speed = 921600
+build_unflags = -D WLED_RELEASE_NAME=\"ESP32\"
build_flags = ${env:esp32dev.build_flags}
-D WLED_DEBUG
-D WLED_RELEASE_NAME=\"ESP32_DEBUG\"
From 1afd72cb832f7ddb7537c7cc252ae0d6b5cfeaeb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:09:10 +0000
Subject: [PATCH 28/66] Initial plan
From 151acb249e97f24d5b4280b0e7b61c3a64fe9bd8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:54:28 +0000
Subject: [PATCH 29/66] Initial plan
From 07e26d31f45cc28be0a62231b6fd551054f5088f Mon Sep 17 00:00:00 2001
From: Will Tatam
Date: Sat, 8 Nov 2025 21:21:32 +0000
Subject: [PATCH 30/66] fix ESP32_DEBUG
---
platformio.ini | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/platformio.ini b/platformio.ini
index cdf4c2bd32..ec0ed5ce9b 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -459,8 +459,7 @@ board_build.flash_mode = dio
[env:esp32dev_debug]
extends = env:esp32dev
upload_speed = 921600
-build_unflags = -D WLED_RELEASE_NAME=\"ESP32\"
-build_flags = ${env:esp32dev.build_flags}
+build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags}
-D WLED_DEBUG
-D WLED_RELEASE_NAME=\"ESP32_DEBUG\"
From 601bb6f0ca5a514027b9b020ffea898e39756cc5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:09:10 +0000
Subject: [PATCH 31/66] Initial plan
From 91349234a0435dd11b76cfb4146d679a98d02dd7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:54:28 +0000
Subject: [PATCH 32/66] Initial plan
From d475d21a79b20f7aa63fce64277a4fc33245453c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:09:10 +0000
Subject: [PATCH 33/66] Initial plan
From 670f74d589206049e3f805e1a951e9a5e68d093c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 5 Oct 2025 14:09:10 +0000
Subject: [PATCH 34/66] Initial plan
From 013ecfb1896228172dc4f05384da3ae4e7aac8ea Mon Sep 17 00:00:00 2001
From: Will Tatam
Date: Sun, 9 Nov 2025 09:15:23 +0000
Subject: [PATCH 35/66] Add ESP32 bootloader upgrade functionality with JSON
API support
Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com>
---
wled00/data/update.htm | 18 ++++++
wled00/fcn_declare.h | 3 +
wled00/json.cpp | 3 +
wled00/wled_server.cpp | 141 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 165 insertions(+)
diff --git a/wled00/data/update.htm b/wled00/data/update.htm
index d8b8876ef2..e93ecde5c0 100644
--- a/wled00/data/update.htm
+++ b/wled00/data/update.htm
@@ -29,6 +29,13 @@
if (data.arch == "esp8266") {
toggle('rev');
}
+ isESP32 = data.arch && data.arch.startsWith('esp32');
+ if (isESP32) {
+ gId('bootloader-section').style.display = 'block';
+ if (data.bootloaderSHA256) {
+ gId('bootloader-hash').innerText = 'Current bootloader SHA256: ' + data.bootloaderSHA256;
+ }
+ }
})
.catch(error => {
console.log('Could not fetch device info:', error);
@@ -37,6 +44,7 @@
document.querySelector('.release-name').textContent = 'Unknown';
});
}
+ function GetV() {/*injected values here*/}
-
-
+
WLED Software Update
| |