Skip to content

Conversation

@netmindz
Copy link
Member

@netmindz netmindz commented Aug 14, 2025

Update the code to handle at least being able to compile against V5


Edit (softhack007): espressif has "silently" changed a lot of APIs, and - in many cases - replacements are not behaving the same way as before. We are lucky that a few "legacy APIs" are still available, like the "V4" I2S driver. So even when "it compiles", there may be a lot more adaptations needed before "it really works".


Helpful information and migration guidance

coding guides

We should make sure that the "V5" branch will still compile for "V4" as a backup solution.
When adding code that only works in the new framework, it should be conditionally compiled, like in the examples below

  • "V5" code that is not specific to new boards
      #if defined(ESP_IDF_VERSION) && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
      ledcAttach(pwmPin, 25000, 8);  // New API: ledcAttach(pin, freq, resolution)
      #else
      ledcSetup(pwmChannel, 25000, 8);
      // attach the channel to the GPIO to be controlled
      ledcAttachPin(pwmPin, pwmChannel);
      #endif
  • "V5" code only for esp32-P4 or esp32-C6:
 #if defined(CONFIG_IDF_TARGET_ESP32C6)
    // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/gpio.html
    // strapping pins: 4, 5, 8, 9
    if (gpio > 11 && gpio < 14) return false;     // 12-13 USB-JTAG
    if (gpio > 23 && gpio < 31) return false;     // 24-30 SPI FLASH
#elif defined(CONFIG_IDF_TARGET_ESP32P4)
    // strapping pins: 34,35,36,37,38
    if (             gpio <   2) return false;     // NC
    if (gpio > 13 && gpio <  20) return false;     // ESP-Hosted WiFi pins
    if (gpio > 23 && gpio <  26) return false;     // USB Pins
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
  // .... existing code
#else
  // .... existing code
#endif

related

Summary by CodeRabbit

  • New Features

    • Expanded ESP32 toolchain (IDF v5+) support and compatibility improvements for PWM, RMT and Ethernet.
  • Bug Fixes

    • More consistent network/IP reporting across UI, discovery, services and displays.
    • Safer DMX/RDM handling with improved stability and error checks.
    • Updated display/usermod network checks to use the consolidated network interface.
  • Chores

    • Build manifest and Python dependency updates; project ignore list extended.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 14, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Refactors build and platform compatibility for ESP32/ESP-IDF v5: gates NeoEsp32RmtHI compilation by WLED_USE_SHARED_RMT, renames Network → WLEDNetwork across the codebase, updates LEDC/ETH/UDP/MDNS/NTP APIs for IDF v5, adds SHA-1 shim, DMX RDM personality changes, and updates platformio/requirements.

Changes

Cohort / File(s) Summary
NeoEsp32RmtHI guard
lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h, lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp, lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S
Wrapped RMT driver headers/source/ASM with #if !defined(WLED_USE_SHARED_RMT) to exclude when shared RMT is used.
Network core rename
wled00/src/dependencies/network/Network.h, wled00/src/dependencies/network/Network.cpp
Renamed NetworkClassWLEDNetworkClass and global NetworkWLEDNetwork; adjusted localMAC handling for ESP32 Ethernet.
Network API propagation
wled00/*, wled00/src/dependencies/e131/ESPAsyncE131.cpp, wled00/src/dependencies/espalexa/Espalexa.h, usermods/*, lib/*, wled00/src/dependencies/network/*
Replaced Network.* references with WLEDNetwork.* across many modules (json, udp, improv, e131, espalexa, usermods, wled.cpp/h, xml, bus_manager, etc.) and updated WLED_CONNECTED macro.
Build/Platform manifest
platformio.ini, requirements.txt
Added ESP-IDF v5 manifest/envs, introduced -D WLED_USE_SHARED_RMT and other build flags, updated library versions and Python/dev deps.
LEDC / PWM modernization
wled00/bus_manager.cpp, usermods/PWM_fan/PWM_fan.cpp
Conditionalized LEDC API for ESP-IDF ≥5: switch ledcSetup/ledcAttachPinledcAttach and ledcDetachPinledcDetach; changed semaphore type to SemaphoreHandle_t.
DMX / RDM updates
wled00/dmx_input.h, wled00/dmx_input.cpp
RDM callbacks changed to receive separate request/response headers; added personalities array and createPersonalities(), switched driver install to use personalities count, added null checks and DMX_CONFIG_DEFAULT usage.
SHA-1 shim
wled00/mbedtls_sha1_shim.cpp, wled00/fcn_declare.h, wled00/util.cpp
Added mbedtls SHA-1 wrappers mapping to Arduino SHA1Builder for ESP32 (IDF ≥5.2.0); guarded computeSHA1/getDeviceId/device fingerprint code for IDF v5+.
Ethernet / ETH API changes
wled00/network.cpp
Reordered ETH.begin() parameters to match updated API (PHY type first, then address, mdc/mdio, power, clock mode) and minor formatting fixes.
NeoPixelBus / bus wrapper shim
wled00/bus_wrapper.h
Added macro remapping for NeoPixelBus RMT/I2S method names on ESP32 and defined new 32-bit host pin constants for ESP32.
NTP / UDP flush handling
wled00/ntp.cpp, wled00/src/dependencies/espalexa/Espalexa.h
Use clear() instead of flush() on ESP-IDF ≥5 where appropriate; adjusted UDP handling and multicast IP sourcing to use WLEDNetwork.localIP().
Misc. API/type modernizations & constants
wled00/const.h, wled00/bus_manager.cpp, wled00/wled.h
Converted macro to constexpr for WLED_MAX_ANALOG_CHANNELS; replaced xSemaphoreHandle with SemaphoreHandle_t; updated WLED_CONNECTED to use WLEDNetwork.isConnected().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • willmmiles
  • netmindz
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '🚀 ESP-IDF V5' directly and clearly identifies the main objective of the PR: upgrading the codebase to support ESP-IDF v5.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch V5

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

❤️ Share

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

@netmindz netmindz changed the title V5 ESP-IDF V5 Aug 14, 2025
@netmindz netmindz mentioned this pull request Aug 15, 2025
@netmindz netmindz requested a review from willmmiles August 16, 2025 11:30
@netmindz
Copy link
Member Author

I'm not sure if renaming Network is the right approach to fix the conflict @willmmiles

@netmindz
Copy link
Member Author

I am also unsure why we are seeing linker errors for things like mbedtls_sha1_starts

@netmindz netmindz added this to the 0.17.0 milestone Aug 16, 2025
@willmmiles
Copy link
Member

I'm not sure if renaming Network is the right approach to fix the conflict @willmmiles

Hm, it looks like it's just a bag of stateless utility functions. Probably it should be a namespace instead of a class. I don't think there's a practical solution to avoid the collision other than renaming, unless the newer core offers these utilities itself. (In fact I'd also suggest pulling them in to wled00/network.cpp , given the use of WLED-specific #defines.)

(Best pratice would've been that the upstream libraries put everything they do in a namespace, eg. Arduino::Network or somesuch, but here we are.)

I am also unsure why we are seeing linker errors for things like mbedtls_sha1_starts

I believe that the Tasmota platform omits mbedtls to save flash space, as they've gone over to BearSSL. Looks like they're still exposing the headers though - I don't know if that's an IDF bug or something wrong with their build process.

AsyncWebServer needs an SHA1 implementation as it is required for WebSockets. It looks like the upstream AWS folks have switched over to IDFv5 functions and vendored in Espressif's implementation when building on IDFv4. I'll see if I can pull those patches.

@sombree
Copy link

sombree commented Aug 25, 2025

With few fixes it builds.
image

MQTT
With the change below I was able to build it (I don’t have a way to test runtime behavior):
In AsyncMqttClient.hpp line 41, change:
#if ESP32
to
#ifdef ESP32

mbedTLS
Upstream ESPAsyncWebServer made the change here: 57244d47444a3281ba30f4c9da60fb23d05eb495
Applying similar changes to Aircoookie/ESPAsyncWebServer.git#v2.4.2 made it buildable.

WiFi
I had crash related to memcpy and MAC adress was shown as 000000000000. Quick fixes are below.

wled.cpp

Compare IPAddress to a value, not to 0 (prevents pointer overload → memcmp on nullptr) and clamp index (prevents out-of-range access cascading into bad compares).

@@ void WLED::initConnection()
-  if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) {
-    WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress);
-  } else {
-    WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0));
-  }
+ if (multiWiFi.empty()) {                       // guard: handle empty WiFi list safely
+  WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0));
+ } else {
+  if (selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // guard: ensure valid index
+  if (multiWiFi[selectedWiFi].staticIP != IPAddress((uint32_t)0) &&
+    multiWiFi[selectedWiFi].staticGW != IPAddress((uint32_t)0)) { // guard: compare as IPAddress to avoid pointer overload
+    WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress);
+  } else {
+    WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0));
+  }
+ }

ESP32 MAC fallback (fix Info tab showing 000000000000)

@@ (top of file)
+#ifdef ARDUINO_ARCH_ESP32
+#include <esp_mac.h>  // fallback MAC read from efuse
+#endif
@@ // generate module IDs must be done before AP setup
-escapedMac = WiFi.macAddress();
+escapedMac = WiFi.macAddress();
+#ifdef ARDUINO_ARCH_ESP32
+if (escapedMac == "00:00:00:00:00:00") { // fix: fallback to efuse MAC if WiFi not started
+  uint8_t m[6] = {0};
+  if (esp_read_mac(m, ESP_MAC_WIFI_STA) == ESP_OK) {
+    char buf[18];
+    sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", m[0], m[1], m[2], m[3], m[4], m[5]);
+    escapedMac = buf;
+  }
+}
+#endif
 escapedMac.replace(":", "");
 escapedMac.toLowerCase();

network.cpp

  1. Guard empty Wi-Fi list before accessing multiWiFi[0]
@@ int findWiFi(...)
-  if (multiWiFi.size() <= 1) {
-    DEBUG_PRINTF_P(PSTR("WiFi: Defaulf SSID (%s) used.\n"), multiWiFi[0].clientSSID);
-    return 0;
-  }
+  if (multiWiFi.size() <= 1) {
+    if (multiWiFi.empty()) return 0; // guard: handle empty list safely
+    DEBUG_PRINTF_P(PSTR("WiFi: Defaulf SSID (%s) used.\n"), multiWiFi[0].clientSSID);
+    return 0;
+  }
  1. Safe BSSID compare (both sides must exist & configured BSSID non-zero)
@@ for (size_t n = 0; n < multiWiFi.size(); n++)
-    bool foundBSSID = memcmp(multiWiFi[n].bssid, WiFi.BSSID(o), 6) == 0;
+    const uint8_t* _scanBSSID = WiFi.BSSID(o);
+    bool _cfgHasBSSID = false; for (int _i = 0; _i < 6; _i++) _cfgHasBSSID |= multiWiFi[n].bssid[_i];
+    bool foundBSSID = (_scanBSSID && _cfgHasBSSID) && memcmp(multiWiFi[n].bssid, _scanBSSID, 6) == 0; // guard: safe memcmp against nullptr/empty BSSID

Usermods
I had more issues with usermods—the new toolchain’s linker was GC’ing/orphaning the old section-based registration. I switched registration to a ctor-based dynamic hook (no new sections) and kept legacy dtors for backward compatibility.
I used the RF433 usermod as the example.

Modified um_manager.cpp:

#include "wled.h"
#include <assert.h>

/*
 * Registration and management utility for v2 usermods
 *
 * Discovery paths, in this order:
 *  1) Legacy dtors window: .dtors.tbl.usermods.0 ... .99 (older toolchains)
 *  2) Dynamic fallback: usermods call registerUsermod(Usermod*) from a ctor
 */

// -------- Legacy dtors window (keep for backward compatibility) --------
static Usermod * const _usermod_table_begin[0]
  __attribute__((section(".dtors.tbl.usermods.0"), unused)) = {};
static Usermod * const _usermod_table_end[0]
  __attribute__((section(".dtors.tbl.usermods.99"), unused)) = {};

struct UMSpan {
  Usermod* const* begin;
  Usermod* const* end;
};

static inline UMSpan reg_dtors_span() {
  UMSpan s{_usermod_table_begin, _usermod_table_end};
  return s;
}

// ----- Dynamic fallback: usermods can push themselves here at boot -----
static Usermod* g_dynMods[16];
static size_t   g_dynCnt = 0;

// Weak, so a stronger definition elsewhere (if any) wins.
// Usermods may call this from a constructor to guarantee registration.
extern "C" void registerUsermod(Usermod* m) __attribute__((weak));
extern "C" void registerUsermod(Usermod* m) {
  if (m && g_dynCnt < (sizeof(g_dynMods)/sizeof(g_dynMods[0]))) {
    g_dynMods[g_dynCnt++] = m;
  }
}

// ----- Common iteration helpers -----

// Accept any callable (incl. capturing lambdas/functors)
template<typename Fn>
static inline void forEachMod(Fn&& fn) {
  // 1) legacy dtors table
  {
    UMSpan s = reg_dtors_span();
    for (auto p = s.begin; p < s.end; ++p) {
      Usermod* m = *p;
      if (m) fn(m);
    }
  }
  // 2) dynamic list
  for (size_t i = 0; i < g_dynCnt; ++i) {
    if (g_dynMods[i]) fn(g_dynMods[i]);
  }
}

static inline size_t countMods() {
  size_t count = 0;
  // dtors window
  {
    UMSpan s = reg_dtors_span();
    count += (size_t)(s.end - s.begin);
  }
  // dynamic
  count += g_dynCnt;
  return count;
}

// --------- Usermod Manager methods (iterate via forEachMod) ---------

void UsermodManager::setup()             { forEachMod([](Usermod* m){ m->setup(); }); }
void UsermodManager::connected()         { forEachMod([](Usermod* m){ m->connected(); }); }
void UsermodManager::loop()              { forEachMod([](Usermod* m){ m->loop(); }); }
void UsermodManager::handleOverlayDraw() { forEachMod([](Usermod* m){ m->handleOverlayDraw(); }); }

void UsermodManager::appendConfigData(Print& dest) {
  forEachMod([&](Usermod* m){ m->appendConfigData(dest); });
}

bool UsermodManager::handleButton(uint8_t b) {
  bool overrideIO = false;
  forEachMod([&](Usermod* m){
    if (m->handleButton(b)) overrideIO = true;
  });
  return overrideIO;
}

bool UsermodManager::getUMData(um_data_t **data, uint8_t mod_id) {
  bool got = false;
  forEachMod([&](Usermod* m){
    if (got) return;
    if (mod_id > 0 && m->getId() != mod_id) return;
    if (m->getUMData(data)) got = true;
  });
  return got;
}

void UsermodManager::addToJsonState(JsonObject& obj) {
  forEachMod([&](Usermod* m){ m->addToJsonState(obj); });
}

void UsermodManager::addToJsonInfo(JsonObject& obj) {
  auto um_id_list = obj.createNestedArray("um");
  forEachMod([&](Usermod* m){
    um_id_list.add(m->getId());
    m->addToJsonInfo(obj);
  });
}

void UsermodManager::readFromJsonState(JsonObject& obj) {
  forEachMod([&](Usermod* m){ m->readFromJsonState(obj); });
}

void UsermodManager::addToConfig(JsonObject& obj) {
  forEachMod([&](Usermod* m){ m->addToConfig(obj); });
}

bool UsermodManager::readFromConfig(JsonObject& obj) {
  bool allComplete = true;
  forEachMod([&](Usermod* m){
    if (!m->readFromConfig(obj)) allComplete = false;
  });
  return allComplete;
}

#ifndef WLED_DISABLE_MQTT
void UsermodManager::onMqttConnect(bool sessionPresent) {
  forEachMod([&](Usermod* m){ m->onMqttConnect(sessionPresent); });
}

bool UsermodManager::onMqttMessage(char* topic, char* payload) {
  bool handled = false;
  forEachMod([&](Usermod* m){
    if (!handled && m->onMqttMessage(topic, payload)) handled = true;
  });
  return handled;
}
#endif // WLED_DISABLE_MQTT

#ifndef WLED_DISABLE_ESPNOW
bool UsermodManager::onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) {
  bool handled = false;
  forEachMod([&](Usermod* m){
    if (!handled && m->onEspNowMessage(sender, payload, len)) handled = true;
  });
  return handled;
}
#endif // WLED_DISABLE_ESPNOW

void UsermodManager::onUpdateBegin(bool init) {
  forEachMod([&](Usermod* m){ m->onUpdateBegin(init); });
}

void UsermodManager::onStateChange(uint8_t mode) {
  forEachMod([&](Usermod* m){ m->onStateChange(mode); });
}

Usermod* UsermodManager::lookup(uint16_t mod_id) {
  Usermod* found = nullptr;
  forEachMod([&](Usermod* m){
    if (!found && m->getId() == mod_id) found = m;
  });
  return found;
}

size_t UsermodManager::getModCount() {
  return countMods();
}

/* Usermod v2 interface shim for oappend */
Print* Usermod::oappend_shim = nullptr;
void Usermod::appendConfigData(Print& settingsScript) {
  assert(!oappend_shim);
  oappend_shim = &settingsScript;
  this->appendConfigData();
  oappend_shim = nullptr;
}

At the end of usermod_v2_RF433.cpp I added:

// --- Registration via dynamic fallback (no custom sections) ---
static RF433Usermod usermod_v2_RF433;
REGISTER_USERMOD(usermod_v2_RF433);   // OK to keep for legacy dtors builds

extern "C" void registerUsermod(Usermod*) __attribute__((weak));

__attribute__((constructor, used))
static void __rf433_ctor_register(void) {
  if (registerUsermod) registerUsermod(&usermod_v2_RF433);
}

NeoPixelBus
I also had issues with NeoPixelBus. At boot, IDF runs check_rmt_legacy_driver_conflict() and aborts if both the old (legacy) RMT driver and the new (“driver_ng” / v2) RMT driver are linked into firmware.
I switched to Makuna/NeoPixelBus.git#CORE3 and made a small change:

--- a/src/internal/methods/ESP/ESP32/NeoEsp32RmtXMethod.h
+++ b/src/internal/methods/ESP/ESP32/NeoEsp32RmtXMethod.h
@@ -1,5 +1,7 @@
 #pragma once
+// Needed for ESP_* macros that take a log_tag
+#include "esp_log.h"
+static const char* TAG = "NeoEsp32RmtX";

And I added a temporary shim in wled00/bus_wrapper.h. Since ESP-IDF 5 switched to the new RMT “driver_v2,” Makuna renamed all of the “N” (legacy/driver_v1) variants to “X” (v2) under internal/methods/ESP/ESP32/*

// --- temporary shim for NeoPixelBus CORE3 / RMT driver_v2 ------------------
#if __has_include(<NeoPixelBus.h>)
  #define NeoEsp32RmtNWs2812xMethod  NeoEsp32RmtXWs2812xMethod
  #define NeoEsp32RmtNSk6812Method   NeoEsp32RmtXSk6812Method
  #define NeoEsp32RmtN400KbpsMethod  NeoEsp32RmtX400KbpsMethod
  #define NeoEsp32RmtNTm1814Method   NeoEsp32RmtXTm1814Method
  #define NeoEsp32RmtNTm1829Method   NeoEsp32RmtXTm1829Method
  #define NeoEsp32RmtNTm1914Method   NeoEsp32RmtXTm1914Method
  #define NeoEsp32RmtNApa106Method   NeoEsp32RmtXApa106Method
  #define NeoEsp32RmtNWs2805Method   NeoEsp32RmtXWs2805Method
#endif
// ---------------------------------------------------------------------------

@netmindz
Copy link
Member Author

Thanks for helping with this @sombree

Could you please open a PR against the V5 branch and we can then pull in your fixes that way, maintaining attribution for your work

sombree added a commit to sombree/WLED that referenced this pull request Sep 29, 2025
…(ESP-IDF 5.3.4 / arduino-esp32 v3.1.4).

Based on wled#4838.
sombree added a commit to sombree/WLED that referenced this pull request Sep 29, 2025
…(ESP-IDF 5.3.4 / arduino-esp32 v3.1.4).

Based on wled#4838.
@wled wled deleted a comment from coderabbitai bot Feb 8, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
wled00/util.cpp (1)

1135-1229: ⚠️ Potential issue | 🟠 Major

Blanket IDF v5 exclusion disables all three functions unnecessarily — computeSHA1 and getDeviceId can work with targeted API guards.

The #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) guard removes computeSHA1, generateDeviceFingerprint, and getDeviceId on IDF v5, but only two compatibility issues exist:

  1. mbedtls API rename (lines 1147–1149): In IDF v5's mbedtls 3.x, the _ret suffix was removed. Use conditional compilation to select the correct function names:

    • Old (IDF <5): mbedtls_sha1_starts_ret(), mbedtls_sha1_update_ret(), mbedtls_sha1_finish_ret()
    • New (IDF ≥5): mbedtls_sha1_starts(), mbedtls_sha1_update(), mbedtls_sha1_finish() (still return int)
  2. esp_adc_cal.h removal (line 1164): Replaced by esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h with a redesigned API; only affects generateDeviceFingerprint.

A targeted approach allows computeSHA1 and getDeviceId to work on IDF v5 immediately:

Suggested fix for computeSHA1
 String computeSHA1(const String& input) {
   `#ifdef` ESP8266
     return sha1(input);
   `#else`
     unsigned char shaResult[20];
     mbedtls_sha1_context ctx;
     mbedtls_sha1_init(&ctx);
+  `#if` ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+    mbedtls_sha1_starts(&ctx);
+    mbedtls_sha1_update(&ctx, (const unsigned char*)input.c_str(), input.length());
+    mbedtls_sha1_finish(&ctx, shaResult);
+  `#else`
     mbedtls_sha1_starts_ret(&ctx);
     mbedtls_sha1_update_ret(&ctx, (const unsigned char*)input.c_str(), input.length());
     mbedtls_sha1_finish_ret(&ctx, shaResult);
+  `#endif`
     mbedtls_sha1_free(&ctx);
     // ... hex conversion unchanged
   `#endif`
 }
🤖 Fix all issues with AI agents
In `@platformio.ini`:
- Around line 278-283: The build_unflags assignment is wrong: it uses
${esp32_idf_V5.build_flags} which cancels flags; change the value to
${esp32_idf_V5.build_unflags} so build_unflags references the unflag list from
the esp32_idf_V5 section; update the platformio.ini entry that defines
build_unflags (near the lines with platform, build_flags, lib_deps) to use
${esp32_idf_V5.build_unflags} instead of ${esp32_idf_V5.build_flags}.
- Line 130: The build flag WLED_USE_SHARED_RMT is defined multiple times; remove
the definitions from the common build_flags and keep a single definition in the
esp32_idf_V5 build_flags block (or if you want it to apply everywhere, remove
the duplicates in esp32_idf_V5 and keep it only in common). Edit the
platformio.ini to ensure WLED_USE_SHARED_RMT appears exactly once and only
within the intended scope by deleting the extra entries in the
[common].build_flags or the duplicate(s) in [esp32_idf_V5].build_flags.
- Around line 387-404: The [esp32s3] environment is missing the proper unflags
and lib exclusion: replace the incorrect reference to ${common.build_unflags}
with ${esp32_idf_V5.build_unflags} (so the -Wno-volatile unflag is applied to C
files) and add the same lib_ignore entry used in other ESP32 envs (e.g.
${esp32_idf_V5.lib_ignore}) to exclude NeoESP32RmtHI from S3 builds to avoid RMT
driver conflicts; update the build_unflags and add lib_ignore under the
[esp32s3] block.

In `@wled00/dmx_input.cpp`:
- Around line 95-129: Define a named constant like DMX_PERSONALITY_COUNT and
replace the hardcoded 10 in the static array declaration (currently static
dmx_personality_t personalities[10];), in createPersonalities usage, and in
calls to dmx_driver_install and rdm_register_dmx_personality so all three sites
use DMX_PERSONALITY_COUNT; update any loops or bounds that assume 10 to
reference DMX_PERSONALITY_COUNT and ensure the constant is visible where
dmx_driver_install and rdm_register_dmx_personality are invoked.
- Around line 86-93: In createConfig(), config.software_version_label is set to
DmxVersionString.c_str() which leaves a dangling pointer when the local
std::string DmxVersionString is destroyed; fix by copying the string into the
config's fixed-size char buffer (e.g., use
strncpy(config.software_version_label, DmxVersionString.c_str(),
sizeof(config.software_version_label)-1) and set the final byte to '\0') so the
label remains valid after createConfig() returns; alternatively, if
config.software_version_label is a const char* and you prefer a static lifetime,
replace DmxVersionString with a static std::string or a static char array, but
prefer the strncpy approach to avoid global state.

In `@wled00/network.cpp`:
- Around line 234-241: The ETH.begin(...) call uses the v5/Arduino-ESP32
parameter order and needs a conditional to preserve v4 compatibility; wrap the
call to ETH.begin in a version/build guard (e.g., check CONFIG_IDF_VERSION_MAJOR
or the esp32dev_V4 define) and call ETH.begin with the v5 signature when IDF v5
is detected (using es.eth_type, es.eth_address, es.eth_mdc, es.eth_mdio,
es.eth_power, es.eth_clk_mode) but use the v4 signature when building for
esp32dev_V4/IDF v4 (placing phy_addr/eth_address as the first parameter and
ordering the rest per the v4 API) so compilation succeeds on both IDF versions.

In `@wled00/src/dependencies/espalexa/Espalexa.h`:
- Line 273: The lambda passed to serverAsync->on that handles HTTP_GET still
uses an implicit-capture `[=]`; change its capture to `[this]` to match the
other handlers and avoid implicit `this` capture warnings — i.e. update the
lambda used in the serverAsync->on("/espalexa", HTTP_GET, ...) callback that
assigns to `server` and calls `servePage()` so it captures `this` explicitly
(use `[this]`), leaving the parameter `AsyncWebServerRequest *request`
unchanged.
🧹 Nitpick comments (5)
wled00/src/dependencies/espalexa/Espalexa.h (1)

21-26: Dummy macro value is misleading but currently harmless.

The real ESP_IDF_VERSION_VAL encodes as (major << 16) | (minor << 8) | patch, so ESP_IDF_VERSION_VAL(5,0,0) = 327680, not 500. Since the only comparison site (line 389) is guarded by defined(ARDUINO_ARCH_ESP32), this value is never evaluated on ESP8266, so it won't cause a bug today. But if anyone later adds a version check without the ESP32 guard, the result will be silently wrong.

Suggested fix
-#define ESP_IDF_VERSION_VAL(n1,n2,n3) 500
+#define ESP_IDF_VERSION_VAL(n1,n2,n3) ((n1 << 16) | (n2 << 8) | (n3))
platformio.ini (2)

504-522: esp32dev_V4 unflags WLED_ENABLE_DMX_INPUT which may not exist at that point.

Line 510 uses build_unflags with -D WLED_ENABLE_DMX_INPUT, but build_unflags on line 509 inherits ${common.build_unflags} which is empty. The DMX flag comes from ${esp32_idf_V4.build_flags} (line 313), so it's being added and unflagged in the same env. This works in PlatformIO but the intent would be clearer by simply not including the flag in the first place via a V4-specific override.

Otherwise the V4 regression test environment looks reasonable.


13-39: Several entries in default_envs are both commented and uncommented.

esp32s3dev_8MB_opi appears uncommented on line 36 and commented on line 31. esp32s3dev_16MB_opi and esp32s3_4M_qspi similarly appear commented in two places (lines 30+35 and 32+38). This makes it hard to tell which S3 builds are actually active. Consider cleaning up to have each env listed once.

wled00/fcn_declare.h (1)

5-10: Dummy ESP_IDF_VERSION_VAL returns a misleading magic number.

The shim defines ESP_IDF_VERSION_VAL(n1,n2,n3) as 500, which doesn't match the real macro's formula ((n1<<16)|(n2<<8)|n3). While it currently works because ESP8266 guards short-circuit before evaluating IDF version comparisons, any future direct use on ESP8266 (e.g., ESP_IDF_VERSION_VAL(1,0,0)) would yield 500 instead of 65536, leading to silently wrong comparisons.

Consider matching the real formula or using a clearly-below-everything sentinel like 0:

Suggested fix
 // dummy macro for 8266
 `#ifndef` ARDUINO_ARCH_ESP32
 `#ifndef` ESP_IDF_VERSION_VAL
-#define ESP_IDF_VERSION_VAL(n1,n2,n3) 500
+#define ESP_IDF_VERSION_VAL(n1,n2,n3) ((n1 << 16) | (n2 << 8) | n3)
+#endif
+#ifndef ESP_IDF_VERSION
+#define ESP_IDF_VERSION 0
 `#endif`
 `#endif`
wled00/dmx_input.cpp (1)

100-128: Use sizeof(personalities[0].description) instead of hardcoded 32 for strncpy buffer size.

The description field in dmx_personality_t is 33 bytes (32 chars + null terminator). While the current code happens to work because personalities is zero-initialized and all strings are shorter than 32 characters, using sizeof(personalities[0].description) would be more defensive and prevent potential issues if string lengths change in the future.

Comment on lines +86 to 93
#else
// fallback code
const std::string DmxVersionString = "WLED_V" + std::to_string(VERSION);
config.software_version_label = DmxVersionString.c_str();
#endif

return config;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Dangling pointer: config.software_version_label points to destroyed local std::string.

DmxVersionString is a local std::string inside createConfig(). Assigning .c_str() to config.software_version_label and then returning config leaves the pointer dangling — the string's buffer is freed when the function returns. This is undefined behavior and will likely cause garbage or a crash when the label is read.

Proposed fix — use `strncpy` into a fixed-size field, or make the string `static`
   `#else`
     // fallback code
-    const std::string DmxVersionString = "WLED_V" + std::to_string(VERSION);
-    config.software_version_label = DmxVersionString.c_str();
+    static std::string DmxVersionString = "WLED_V" + std::to_string(VERSION);
+    config.software_version_label = DmxVersionString.c_str();
   `#endif`
🤖 Prompt for AI Agents
In `@wled00/dmx_input.cpp` around lines 86 - 93, In createConfig(),
config.software_version_label is set to DmxVersionString.c_str() which leaves a
dangling pointer when the local std::string DmxVersionString is destroyed; fix
by copying the string into the config's fixed-size char buffer (e.g., use
strncpy(config.software_version_label, DmxVersionString.c_str(),
sizeof(config.software_version_label)-1) and set the final byte to '\0') so the
label remains valid after createConfig() returns; alternatively, if
config.software_version_label is a const char* and you prefer a static lifetime,
replace DmxVersionString with a static std::string or a static char array, but
prefer the strncpy approach to avoid global state.

Comment on lines +95 to +129
static dmx_personality_t personalities[10];

static void createPersonalities()
{
// Initialize personalities array
strncpy(personalities[0].description, "SINGLE_RGB", 32);
personalities[0].footprint = 3;

strncpy(personalities[1].description, "SINGLE_DRGB", 32);
personalities[1].footprint = 4;

strncpy(personalities[2].description, "EFFECT", 32);
personalities[2].footprint = 15;

strncpy(personalities[3].description, "MULTIPLE_RGB", 32);
personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3);

strncpy(personalities[4].description, "MULTIPLE_DRGB", 32);
personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1);

strncpy(personalities[5].description, "MULTIPLE_RGBW", 32);
personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4);

strncpy(personalities[6].description, "EFFECT_W", 32);
personalities[6].footprint = 18;

strncpy(personalities[7].description, "EFFECT_SEGMENT", 32);
personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15);

strncpy(personalities[8].description, "EFFECT_SEGMENT_W", 32);
personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18);

strncpy(personalities[9].description, "PRESET", 32);
personalities[9].footprint = 1;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Magic number 10 repeated for personality count — extract a named constant.

The literal 10 appears in the static array size (Line 95), dmx_driver_install (Line 151), and rdm_register_dmx_personality (Line 167). If one site is updated but not the others, it silently breaks. A single constant keeps them in sync.

Proposed refactor
+static constexpr size_t DMX_PERSONALITY_COUNT = 10;
+
-static dmx_personality_t personalities[10];
+static dmx_personality_t personalities[DMX_PERSONALITY_COUNT];

Then use DMX_PERSONALITY_COUNT in dmx_driver_install and rdm_register_dmx_personality calls as well.

Also applies to: 148-151, 167-167

🤖 Prompt for AI Agents
In `@wled00/dmx_input.cpp` around lines 95 - 129, Define a named constant like
DMX_PERSONALITY_COUNT and replace the hardcoded 10 in the static array
declaration (currently static dmx_personality_t personalities[10];), in
createPersonalities usage, and in calls to dmx_driver_install and
rdm_register_dmx_personality so all three sites use DMX_PERSONALITY_COUNT;
update any loops or bounds that assume 10 to reference DMX_PERSONALITY_COUNT and
ensure the constant is visible where dmx_driver_install and
rdm_register_dmx_personality are invoked.

@softhack007
Copy link
Member

@coderabbitai i need a concise summary of open points in this PR.
Please scan all unresolved comments, and all discussions with open ends. Create a summary list with major open points, grouped by WLED features or files. For each entry in the list, add a link the relevant comment for more information.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 8, 2026

Tip

For best results, initiate chat on the files or code changes.

@softhack007: Here's a concise summary of open points in this PR, organized by area:


🔧 Core WLED Code Issues

1. util.cpp - Device Identification Functions

Status: ❌ Broken in V5 builds (conditionally compiled out)

  • Missing headers for esp_chip_info(), esp_efuse_mac_get_default()
  • computeSHA1() and getDeviceId() disabled for ESP-IDF ≥5.0
  • Impact: Device fingerprinting not working in V5
  • Reference: Build error comment | My analysis

2. ADC Deprecated Constants

Status: ⚠️ Generates compiler warnings

  • ADC_ATTEN_DB_11 used in util.cpp:1177 and audioreactive
  • Should be replaced with ADC_ATTEN_DB_12
  • Reference: My analysis - Priority 2

🏗️ Build System & Tooling

3. PlatformIO Instability

Status: ⚠️ Intermittent build failures

  • pioarduino packages sneaking into builds
  • Short SHA library references randomly failing (#bc3af18)
  • Workaround: Delete ~/.platformio and restart
  • Action needed: Replace short hashes with long hashes or .zip references
  • Reference: PlatformIO issues comment | Jason2866's recommendation

4. Usermod CI Builds

Status: ⏸️ Generating ~100 extra builds

5. Platform Package Decision

Status: 🤔 Strategy discussion needed

  • Current: Using Tasmota platform package
  • Alternative: Switch to pioarduino for better V5 support
  • Action needed: Decide on primary platform package
  • Reference: pioarduino discussion

🔌 Usermods Compatibility

6. pwm_outputs Usermod

Status: ❌ Missing LEDC API guards

  • Uses old ledcSetup()/ledcAttachPin() without ESP-IDF version checks
  • Needs same fix as PWM_fan usermod
  • Reference: My analysis - Priority 1

7. deep_sleep Usermod

Status: ⚠️ Unguarded RTC GPIO APIs

  • Multiple rtc_gpio_*() calls without version protection
  • May break on future ESP-IDF updates
  • Reference: My analysis - Priority 2

8. audioreactive Usermod

Status: 🚧 Disabled for V5

  • Requires I2S driver migration (major API changes in IDF 5)
  • ADC_ATTEN_DB_11 deprecation warning
  • Reference: Mentioned in usermod CI analysis

9. Usermod Registration Mechanism

Status: ✅ Fixed with hybrid approach


📚 External Dependencies

10. NeoPixelBus/RMT Driver Conflicts

Status: ✅ Mitigated with build flag

11. mbedtls SHA1 Functions

Status: ✅ Fixed with shim


🎯 Action Items Summary

Critical (breaks builds):

  1. Fix pwm_outputs usermod LEDC API usage
  2. Fix util.cpp device ID functions for V5
  3. Replace short SHA hashes in dependencies

Important (warnings/future-proofing):
4. Replace ADC_ATTEN_DB_11 with ADC_ATTEN_DB_12
5. Add ESP-IDF guards to deep_sleep RTC GPIO calls
6. Disable Usermod CI for V5 branch

Strategic decisions needed:
7. Confirm platform package strategy (Tasmota vs pioarduino)
8. Plan audioreactive I2S driver migration


Would you like me to create GitHub issues for the critical action items, or generate patch proposals for items #1 and #2?


🧠 Learnings used
Learnt from: Jason2866
Repo: wled/WLED PR: 4838
File: requirements.txt:2-2
Timestamp: 2025-12-01T16:13:06.613Z
Learning: Python 3.13 is supported by ESP-IDF 5.x and PlatformIO (any version including 6.1.18+), contrary to some incomplete documentation. This was confirmed by project maintainer Jason2866 in the WLED repository.

Learnt from: softhack007
Repo: wled/WLED PR: 5107
File: boards/adafruit_matrixportal_esp32s3.json:1-66
Timestamp: 2025-11-22T20:33:14.840Z
Learning: In WLED, board JSON files in the boards/ directory may reference partition files that don't exist or have slightly different names than actual files in tools/. This is intentional because platformio.ini or platformio_override.sample.ini explicitly override the partition file using the board_build.partitions setting, which takes precedence over the board JSON partition reference. This pattern exists in multiple local board definitions and should not be flagged as an issue.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.

Learnt from: mval-sg
Repo: wled/WLED PR: 4876
File: wled00/xml.cpp:0-0
Timestamp: 2025-08-28T08:09:20.630Z
Learning: The WLED codebase has opportunities for refactoring hardcoded array bounds (like the "15" used for DMX channels) to use sizeof(array)/sizeof(array[0]) for more maintainable code, but such changes should be done consistently across the entire codebase in a dedicated refactoring effort.

Learnt from: blazoncek
Repo: wled/WLED PR: 4482
File: wled00/udp.cpp:147-149
Timestamp: 2025-02-19T12:43:34.199Z
Learning: ESP8266 and ESP32 platforms have different maximum segment name lengths in WLED, which can cause truncation when syncing segment names between devices. This platform difference affects the user experience when using the segment name sync feature.

Learnt from: willmmiles
Repo: wled/WLED PR: 4890
File: lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h:31-36
Timestamp: 2025-09-02T01:45:58.047Z
Learning: For the NeoEsp32RmtHI driver, RISC-V ESP32-C3 support is currently disabled via bus_wrapper.h rather than compile-time guards, as the maintainer willmmiles is working on resolving underlying nested interrupt issues and prefers to centralize the workaround in one location.

Learnt from: DedeHai
Repo: wled/WLED PR: 4939
File: wled00/FX_fcn.cpp:1176-1187
Timestamp: 2025-09-16T18:08:42.848Z
Learning: In WLED finalizeInit() bus creation (wled00/FX_fcn.cpp), intentionally allowing memory overruns when bus configurations exceed MAX_LED_MEMORY is a deliberate design choice. The trade-off prioritizes creating buses with reduced LED counts over completely failing to create buses, which would cause no LED output and UI failures. This approach forces users to update configurations after migrating to version 0.16 while maintaining basic functionality.

Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.

@softhack007 softhack007 changed the title ESP-IDF V5 🚀 ESP-IDF V5 Feb 8, 2026
@softhack007 softhack007 added enhancement major This is a non-trivial major feature and will take some time to implement keep This issue will never become stale/closed automatically magic labels Feb 8, 2026
@netmindz
Copy link
Member Author

netmindz commented Feb 9, 2026

Would be great to get your input on this @troyhacks

@softhack007
Copy link
Member

Status: ⚠️ Deprecated but still functional

heap_caps_get_free_size()          // Deprecated in IDF 5.x
heap_caps_get_largest_free_block()  // Deprecated in IDF 5.x

ESP-IDF V5.3 Recommendation: These still work but may emit deprecation warnings. Consider future migration to:

  • heap_caps_get_info() (returns struct with all heap stats)
  • Or accept the warnings for now (no runtime impact)

@DedeHai do you think we can replace deprecated functions as the rabbit recommends?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@platformio.ini`:
- Around line 540-547: The debug env `[env:esp32dev_debug]` currently overrides
build_unflags and drops inherited unflags from `${esp32_idf_V5.build_unflags}`;
update the `build_unflags` setting in `env:esp32dev_debug` to preserve inherited
unflags by appending or merging `${esp32_idf_V5.build_unflags}` (e.g., include
both the existing `-D WLED_RELEASE_NAME=...` unflag and
`${esp32_idf_V5.build_unflags}`) so you don't re-enable flags previously removed
by the esp32_idf_V5 configuration.

In `@wled00/util.cpp`:
- Around line 1139-1140: The preprocessor guard around computeSHA1() and
getDeviceId() in util.cpp must match the established pattern used in
fcn_declare.h and json.cpp; replace the current simplistic ESP_IDF_VERSION check
with the explicit guard used elsewhere: check for either non-ESP32 builds or
ESP-IDF < 5.0 (i.e., the pattern !defined(ARDUINO_ARCH_ESP32) ||
(ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) ) so the
declarations/definitions of computeSHA1 and getDeviceId are consistently
enabled/disabled; keep the TODO comment about getDeviceId unavailability on
ESP-IDF v5 intact.
🧹 Nitpick comments (1)
wled00/util.cpp (1)

16-19: Indentation is inconsistent with surrounding includes.

Lines 8-15 have no leading whitespace for #include directives, but lines 16-19 have varying indentation. This creates visual inconsistency within the same #else block.

🧹 Suggested fix to align with surrounding code style
 `#include` "mbedtls/sha1.h"   // for SHA1 on ESP32
 `#include` "esp_efuse.h"
-  `#include` "esp_chip_info.h"
-  `#if` ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
-    `#include` <esp_mac.h>      // V5 requirement
-  `#endif`
+#include "esp_chip_info.h"
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+#include <esp_mac.h>        // V5 requirement
+#endif
 `#endif`

Comment on lines +1139 to +1140
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) // ToDO: find a solution to make this work in V5

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for ESP_IDF_VERSION_VAL definitions
rg -n "ESP_IDF_VERSION_VAL" --type-add 'header:*.h' -t header

# Check how ESP_IDF_VERSION is used in headers
rg -n "ESP_IDF_VERSION" wled00/ -t cpp -t header -B 2 -A 2

# Check the actual code around lines 1139-1140
sed -n '1130,1150p' wled00/util.cpp

Repository: wled/WLED

Length of output: 2765


🏁 Script executed:

# Check for version compatibility macros in the codebase
rg -n "defined.*ESP_IDF_VERSION" wled00/

Repository: wled/WLED

Length of output: 730


🏁 Script executed:

# Look for any fallback definitions
rg -n "#define.*ESP_IDF_VERSION" wled00/

Repository: wled/WLED

Length of output: 206


🏁 Script executed:

sed -n '1139,1250p' wled00/util.cpp

Repository: wled/WLED

Length of output: 3556


🏁 Script executed:

# Also check if there's similar code in json.cpp mentioned in results
sed -n '700,715p' wled00/json.cpp

Repository: wled/WLED

Length of output: 858


🏁 Script executed:

# Check fcn_declare.h to see how the same pattern is used there
sed -n '420,440p' wled00/fcn_declare.h

Repository: wled/WLED

Length of output: 1395


🏁 Script executed:

# Check if ESP_IDF_VERSION is defined on ESP8266
rg -n "ESP_IDF_VERSION" --type cpp | grep -i "define\|ifdef" | head -20

# Look at the learning about USB CDC which mentions ARDUINO_USB_CDC_ON_BOOT vs explicit CONFIG_IDF_TARGET checks
# This suggests relying on Arduino framework flags rather than IDF checks
# Check what the standard pattern is
rg -n "ARDUINO_ARCH_ESP32" wled00/ -t cpp | head -10

# Verify if ESP8266 includes or defines ESP_IDF_VERSION
fd "board" . | head -5
rg -n "ESP_IDF_VERSION" . --type-add 'board:*.json' | head -5

Repository: wled/WLED

Length of output: 2218


🏁 Script executed:

# Search for where ESP_IDF_VERSION might be set for ESP8266
rg -n "ESP_IDF_VERSION\s*=" wled00/

# Check if there's version checking in network.cpp that shows the correct pattern
sed -n '230,240p' wled00/network.cpp

Repository: wled/WLED

Length of output: 440


🏁 Script executed:

# Check the exact guard pattern in util.cpp at the beginning
head -20 wled00/util.cpp

# Verify what files include or define ESP_IDF_VERSION
rg -n "include.*esp_idf_version" wled00/ -t cpp

# Check if the guard pattern is consistent across files
rg -B1 "computeSHA1\|getDeviceId" wled00/ -t cpp -t header | grep -E "^[^:]*:`#if`"

Repository: wled/WLED

Length of output: 718


🏁 Script executed:

# Let's verify: Check if ESP_IDF_VERSION is used without defined() check in util.cpp
sed -n '1,30p' wled00/util.cpp | grep -A5 -B5 "ESP_IDF_VERSION"

# Count unsafe vs safe patterns in the codebase
echo "=== Unsafe patterns (no defined() check): ==="
rg "#if ESP_IDF_VERSION" wled00/ -t cpp | grep -v "defined(ESP_IDF_VERSION)"

echo ""
echo "=== Safe patterns (with defined() or platform check): ==="
rg "#if.*defined.*ARDUINO_ARCH.*ESP_IDF_VERSION" wled00/ -t cpp
rg "#if defined(ESP_IDF_VERSION)" wled00/ -t cpp

Repository: wled/WLED

Length of output: 2080


Use the established guard pattern for consistency with declarations and other usage sites.

The guard at line 1139 is missing the explicit platform check used elsewhere in the codebase. The same computeSHA1() and getDeviceId() functions are declared in fcn_declare.h:427 and used in json.cpp:703 with an explicit pattern: #if !defined(ARDUINO_ARCH_ESP32) || (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)). The guard here should match that pattern for consistency and clarity.

Additionally, getDeviceId() remains unavailable on ESP-IDF v5 due to mbedTLS API changes—the TODO indicates this is known but unresolved.

🛠️ Suggested fix
-#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)    // ToDO: find a solution to make this work in V5
+#if !defined(ARDUINO_ARCH_ESP32) || (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0))    // TODO: Update mbedTLS SHA1 API for ESP-IDF v5 support
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) // ToDO: find a solution to make this work in V5
`#if` !defined(ARDUINO_ARCH_ESP32) || (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) // TODO: Update mbedTLS SHA1 API for ESP-IDF v5 support
🤖 Prompt for AI Agents
In `@wled00/util.cpp` around lines 1139 - 1140, The preprocessor guard around
computeSHA1() and getDeviceId() in util.cpp must match the established pattern
used in fcn_declare.h and json.cpp; replace the current simplistic
ESP_IDF_VERSION check with the explicit guard used elsewhere: check for either
non-ESP32 builds or ESP-IDF < 5.0 (i.e., the pattern
!defined(ARDUINO_ARCH_ESP32) || (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0))
) so the declarations/definitions of computeSHA1 and getDeviceId are
consistently enabled/disabled; keep the TODO comment about getDeviceId
unavailability on ESP-IDF v5 intact.

@troyhacks
Copy link
Contributor

troyhacks commented Feb 10, 2026

Would be great to get your input on this @troyhacks

First off, move I2C to IDF calls. That will solve a lot of headaches right off that bat.

I'll have more as I think about it.

A good test is to comment out Arduino.h and see what breaks. 😁

@softhack007
Copy link
Member

Flash size comparison:

esp32 "V4" esp32 "V5" increase
Flash 1218637 77.5% 1246240 79.2% 27Kb
Ram 78696 24.0% 84084 25.7% 5Kb

coderabbitai[bot]

This comment was marked as resolved.

* remove duplicated -D WLED_DISABLE_INFRARED
* fix indentation in WLED::initConnection()
coderabbitai[bot]

This comment was marked as resolved.

@troyhacks
Copy link
Contributor

@softhack007 now do it with LTO.

@wled wled deleted a comment from coderabbitai bot Feb 11, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@platformio.ini`:
- Around line 505-525: The esp32dev_V4 regression environment contains test-only
build flags (WLED_USE_ETHERNET and the pin overrides RLYPIN and BTNPIN) which
should be removed from the default_envs to avoid skewing V4 regression results;
remove those flags from the [env:esp32dev_V4] build_flags and create a separate
test-only env (e.g., [env:esp32dev_V4_ethernet_test]) that sets -D
WLED_USE_ETHERNET -D RLYPIN=-1 -D BTNPIN=-1 (and any related lib_ignore entries
such as esp_dmx) so the regression env remains standard while the test env
captures the temporary Ethernet/pin overrides.

In `@wled00/bus_manager.cpp`:
- Around line 421-427: The code currently calls ledcAttach(...) under IDF v5
which auto-allocates channels; change this to explicitly bind the expected
channel index (channel = _ledcStart + i) using ledcAttachChannel (or the v5
equivalent that takes a channel argument) so that the assigned channel matches
the code's later assumptions (references: ledcAttach, channel, _ledcStart,
_pins, and the timer reset and direct register writes around lines noted).
Ensure you compute/assign channel = _ledcStart + i before attaching and replace
the ledcAttach(...) call with ledcAttachChannel(channel, _pins[i],
/*...params...*/) so timer reset and PWM duty writes target the correct channel.

Comment on lines +505 to +525
[env:esp32dev_V4]
;; uses V4 framework - for checking that the code still builds in V4
board = esp32dev
platform = ${esp32_idf_V4.platform}
platform_packages = ${esp32_idf_V4.platform_packages}
build_unflags = ${common.build_unflags}
custom_usermods = audioreactive
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
-D WLED_ENABLE_DMX_INPUT ;; TODO: fix lots of compile errors in dmx_input.cpp
custom_usermods = ${common.default_usermods}
build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_V4\" #-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
-D WLED_DISABLE_INFRARED ;; TODO: remove once we have updated for V5
-D WLED_USE_SHARED_RMT ;; TODO: disabled NeoEsp32RmtMethodIsr
-D WLED_USE_ETHERNET -D RLYPIN=-1 -D BTNPIN=-1 ;; TODO: this is just for testing - remove before merging to main
lib_deps = ${esp32_idf_V4.lib_deps}
lib_ignore =
NeoESP32RmtHI ;; TODO: disabled NeoEsp32RmtMethodIsr
esp_dmx ;; TODO: fix lots of compile errors in dmx_input.cpp
monitor_filters = esp32_exception_decoder
board_build.partitions = ${esp32.default_partitions}
board_build.flash_mode = dio

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove the testing-only Ethernet flags before merge.

WLED_USE_ETHERNET plus pin overrides are marked TODO-only in the V4 regression env, but this env is in default_envs. That can skew V4 regression results away from the standard build. Consider moving these flags to an override or a dedicated test env.

Do you want me to draft a minimal test-only env block or open a tracking issue for this cleanup?

🤖 Prompt for AI Agents
In `@platformio.ini` around lines 505 - 525, The esp32dev_V4 regression
environment contains test-only build flags (WLED_USE_ETHERNET and the pin
overrides RLYPIN and BTNPIN) which should be removed from the default_envs to
avoid skewing V4 regression results; remove those flags from the
[env:esp32dev_V4] build_flags and create a separate test-only env (e.g.,
[env:esp32dev_V4_ethernet_test]) that sets -D WLED_USE_ETHERNET -D RLYPIN=-1 -D
BTNPIN=-1 (and any related lib_ignore entries such as esp_dmx) so the regression
env remains standard while the test env captures the temporary Ethernet/pin
overrides.

Comment on lines +421 to +427
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit
ledcAttachPin(_pins[i], channel);
#else
ledcAttach(_pins[i], _frequency, _depth - (dithering*4));
// LEDC timer reset credit @dedehai
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Arduino-ESP32 v3 ledcAttach ledcAttachChannel channel allocation API documentation

💡 Result:

In Arduino-ESP32 v3.x (ESP-IDF 5.x–based core), the LEDC API was redesigned so channels can be auto-assigned to pins via the Peripheral Manager (instead of you manually calling ledcSetup() + ledcAttachPin(), which were removed). [2]

API docs (Arduino-ESP32 “latest”)

  • Auto channel allocation

    bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution);

    Sets up PWM on pin and selects an LEDC channel automatically. Returns true on success. [1]

  • Manual channel selection

    bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel);

    Sets up PWM on pin using the specific LEDC channel you provide. Returns true on success. [1]

Notes relevant to “channel allocation”

  • The docs explicitly state: ledcAttach() selects the channel automatically, while ledcAttachChannel() lets you pick the channel. [1]
  • Available LEDC channel count varies by chip (e.g., ESP32: 16; ESP32-S3: 8; ESP32-C3: 6). [1]
  • v3 migration note: ledcSetup() and ledcAttachPin() removed, replaced by ledcAttach(), and many APIs that previously took a channel now take a pin. [2]

Sources: LEDC API reference [1], Arduino-ESP32 v2→v3 migration guide (LEDC section) [2].


🏁 Script executed:

# Find and examine bus_manager.cpp to understand channel usage context
fd bus_manager.cpp -x head -n 50 {}

# Check the specific lines mentioned (421-427) and surrounding code to see how channel is used
fd bus_manager.cpp -x sed -n '410,500p' {}

Repository: wled/WLED

Length of output: 5613


🏁 Script executed:

# Search for how ledcAttach is used elsewhere in the codebase and check if ledcAttachChannel is available
rg "ledcAttach" wled00/ -A 3 -B 1

# Also search for any existing uses of ledcAttachChannel
rg "ledcAttachChannel" wled00/

Repository: wled/WLED

Length of output: 563


🌐 Web query:

Arduino-ESP32 ledcAttachChannel API availability ESP-IDF 5.0 documentation

💡 Result:

ledcAttachChannel() is not an ESP-IDF 5.0 LEDC API function, so you won’t find it in the ESP-IDF v5.0 LEDC documentation. In ESP-IDF v5.0, LEDC setup is done with APIs such as ledc_timer_config() and ledc_channel_config() (and related ledc_set_duty(), ledc_update_duty(), etc.). [1]

ledcAttachChannel() does exist in Arduino-ESP32, as part of Arduino-ESP32’s own LEDC wrapper API (documented under Arduino-ESP32 “LED Control (LEDC)”). [2]

Sources:
[1] ESP-IDF v5.0 LEDC (PWM) API docs (no ledcAttachChannel, uses ledc_timer_config, ledc_channel_config, etc.)
[2] Arduino-ESP32 LEDC API docs (includes ledcAttachChannel(pin, freq, resolution, channel))


🏁 Script executed:

# Get more context around the timer reset code to see if it's executed on both code paths
fd bus_manager.cpp -x sed -n '415,440p' {}

# Check if there are other LEDC-related operations that depend on the channel value
fd bus_manager.cpp -x grep -n "LEDC\|channel\|_ledcStart" {}

Repository: wled/WLED

Length of output: 5098


🌐 Web query:

Arduino-ESP32 v3 ledcAttach auto-allocated channel number how to retrieve which channel was assigned

💡 Result:

In Arduino-ESP32 v3.x, ledcAttach(pin, freq, resolution) auto-selects a channel but only returns bool, and the public Arduino LEDC API does not provide a “get channel for this pin” function. The API is intentionally pin-based (e.g., ledcWrite(pin, duty)). [1]

What you can do instead:

  1. If you need to know/control the channel: use manual assignment

    ledcAttachChannel(pin, freq, resolution, channel);

    [1]

  2. If you just want to see what got auto-assigned (debug-only): enable core debug logging (e.g., Arduino IDE “Core Debug Level: Verbose”). The core prints lines like:
    ledcAttachChannel(): LEDC attached to pin X (channel Y, resolution ...)
    which reveals the chosen channel. [2]


🏁 Script executed:

# Verify ledcAttachChannel signature in Arduino-ESP32 headers to confirm it supports the proposed fix
rg "ledcAttachChannel" --type-list | head -5

# Search for any existing uses or documentation of ledcAttachChannel in WLED
rg "ledcAttachChannel\|ledcAttach" wled00/ -B 2 -A 2

Repository: wled/WLED

Length of output: 442


🌐 Web query:

Arduino-ESP32 ledcAttachChannel function signature parameters pin channel frequency resolution

💡 Result:

ledcAttachChannel (Arduino-ESP32 LEDC API) signature:

bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel);

[1]

Parameters:

  • pin (uint8_t): GPIO pin to output PWM on. [1]
  • freq (uint32_t): PWM frequency (Hz). [1]
  • resolution (uint8_t): PWM duty resolution in bits (docs note 1–14 bits, or 1–20 bits on ESP32). [1]
  • channel (uint8_t): LEDC channel number to use. [1]

Returns true on success, false on failure. [1]

Sources:
[1] (espressif-docs.readthedocs-hosted.com)

Citations:


Use ledcAttachChannel() to ensure deterministic LEDC channel assignment on IDF v5.

ledcAttach() auto-allocates a channel in Arduino-ESP32 v3, but the code assumes channel == _ledcStart + i for timer reset (line 429) and direct LEDC register writes (lines 549–555). If the auto-allocated channel differs from the assumed value, timer reset targets the wrong timer and PWM duty writes corrupt the wrong LEDC channels, breaking phase synchronization and LED output.

Proposed fix
 `#if` ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
       ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit
       ledcAttachPin(_pins[i], channel);
 `#else`
-      ledcAttach(_pins[i], _frequency,  _depth - (dithering*4));
+      ledcAttachChannel(_pins[i], _frequency, _depth - (dithering*4), channel);
       // LEDC timer reset credit `@dedehai`
 `#endif`
🤖 Prompt for AI Agents
In `@wled00/bus_manager.cpp` around lines 421 - 427, The code currently calls
ledcAttach(...) under IDF v5 which auto-allocates channels; change this to
explicitly bind the expected channel index (channel = _ledcStart + i) using
ledcAttachChannel (or the v5 equivalent that takes a channel argument) so that
the assigned channel matches the code's later assumptions (references:
ledcAttach, channel, _ledcStart, _pins, and the timer reset and direct register
writes around lines noted). Ensure you compute/assign channel = _ledcStart + i
before attaching and replace the ledcAttach(...) call with
ledcAttachChannel(channel, _pins[i], /*...params...*/) so timer reset and PWM
duty writes target the correct channel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement keep This issue will never become stale/closed automatically magic major This is a non-trivial major feature and will take some time to implement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants