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/33] 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/33] 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 783a609e..60c4f416 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

Installed version: WLED ##VERSION##
@@ -37,6 +49,16 @@
+
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 1d81655d..ecd65b70 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 d2b771c5..b2f10729 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 75b4ae3f..8233dfd7 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/33] 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 8233dfd7..601162de 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/33] 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 601162de..6eb6d1ef 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/33] 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 6eb6d1ef..7286982f 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/33] 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 7286982f..5fa773b8 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/33] 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 60c4f416..8c360c78 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 4d7c7b66..4309bc9f 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 3a97459f..6a022472 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/33] Game of Life Rework RAM and speed optimizations. Better repeat detection. Mutation toggle. Blur option added. --- wled00/FX.cpp | 231 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 90 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5b29b48d..4bdef353 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(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed + + Cell *cells = reinterpret_cast (SEGENV.data); - 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); + 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); - CRGB backgroundColor = SEGCOLOR(1); + uint32_t bgColor = SEGCOLOR(1); + uint32_t birthColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 255); - 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)); - } - - 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; + 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; } - //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); + if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix + bool paused = SEGENV.step > strip.now; - //calculate new leds - for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { + // 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)); - 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 + 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); - // 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 - } - } // 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)); + SEGMENT.setPixelColorXY(x, y, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor); } - // 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= -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; + } + } + } + + 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; + } + } + + if (needsColor) SEGMENT.setPixelColorXY(x, y, newColor); + } + // Loop through cells, if toggle, swap alive status + for (unsigned i = maxIndex; i--; ) { + cells[i].alive ^= cells[i].toggleStatus; + cells[i].toggleStatus = 0; + } + + if (repeatingOscillator || repeatingSpaceship || emptyGrid) { + generation = 0; // reset on next call + SEGENV.step += 1000; // pause final generation for 1 second + } + else { + ++generation; + SEGENV.step = strip.now; + } return FRAMETIME; } // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2"; +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,,Blur,,,,,Mutation;!,!;!;2;pal=11,sx=128"; ///////////////////////// From 0391488cefdb43dd6c249ca1dae58e22811abb8e Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Tue, 14 Oct 2025 22:58:22 -0400 Subject: [PATCH 20/33] 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 4bdef353..f0f4276f 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/33] 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 840afb51..b5a05744 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 ecd65b70..e78cf048 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 9f1dd622..ba406ba3 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/33] 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 92368810..cd21d8d8 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/33] 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 d897a494..8419aba8 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/33] 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 8419aba8..249b1010 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/33] Include esp32 debug build --- platformio.ini | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c354de26..6cb401e8 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/33] fix release name for esp32 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 6cb401e8..e0f03e93 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/33] improve esp32_dev env --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index e0f03e93..cdf4c2bd 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/33] 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/33] 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/33] fix ESP32_DEBUG --- platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index cdf4c2bd..ec0ed5ce 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/33] 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/33] 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/33] Initial plan