Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions usermods/audioreactive/audio_reactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2071,6 +2071,13 @@ class AudioReactive : public Usermod {
bool configComplete = !top.isNull();
bool oldEnabled = enabled;
bool oldAddPalettes = addPalettes;
#ifdef ARDUINO_ARCH_ESP32
auto oldDMType = dmType;
auto oldI2SsdPin = i2ssdPin;
auto oldI2swsPin = i2swsPin;
auto oldI2SckPin = i2sckPin;
auto oldI2SmclkPin = mclkPin;
#endif

configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes);
Expand Down Expand Up @@ -2112,6 +2119,15 @@ class AudioReactive : public Usermod {
// add/remove custom/audioreactive palettes
if ((oldAddPalettes && !addPalettes) || (oldAddPalettes && !enabled)) removeAudioPalettes();
if ((addPalettes && !oldAddPalettes && enabled) || (addPalettes && !oldEnabled && enabled)) createAudioPalettes();
#ifdef ARDUINO_ARCH_ESP32
// notify user when a reboot is necessary
if ((audioSource != nullptr) && (oldDMType != dmType)) errorFlag = ERR_REBOOT_NEEDED; // changing mic type requires reboot
if ( (audioSource != nullptr) && (enabled==true)
&& ((oldI2SsdPin != i2ssdPin) || (oldI2swsPin != i2swsPin) || (oldI2SckPin != i2sckPin)) ) errorFlag = ERR_REBOOT_NEEDED; // changing mic pins requires reboot
if ((audioSource != nullptr) && (oldI2SmclkPin != mclkPin)) errorFlag = ERR_REBOOT_NEEDED; // changing MCLK pin requires reboot
if ((oldDMType != dmType) && (oldDMType == 0)) errorFlag = ERR_POWEROFF_NEEDED; // changing from analog mic requires power cycle
if ((oldDMType != dmType) && (dmType == 0)) errorFlag = ERR_POWEROFF_NEEDED; // changing to analog mic requires power cycle
#endif
} // else setup() will create palettes
return configComplete;
}
Expand Down
2 changes: 2 additions & 0 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,8 @@ void BusHub75Matrix::cleanup() {
if (display != nullptr) delete display;
display = nullptr;
virtualDisp = nullptr; // note: when not using "NO_GFX" this causes a memory leak
#else // runtime reconfiguration is not working on -S3, request reboot from user instead
errorFlag = ERR_REBOOT_NEEDED;
#endif
if (_ledBuffer != nullptr) d_free(_ledBuffer); _ledBuffer = nullptr;
if (_ledsDirty != nullptr) d_free(_ledsDirty); _ledsDirty = nullptr;
Expand Down
13 changes: 11 additions & 2 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,17 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
#define ERR_REBOOT_NEEDED 98 // reboot needed after changing hardware setting
#define ERR_POWEROFF_NEEDED 99 // power-cycle needed after changing hardware setting
#define ERR_LOW_MEM 33 // low memory (RAM)
#define ERR_LOW_SEG_MEM 34 // low memory (effect data RAM)
#define ERR_LOW_WS_MEM 35 // low memory (ws)
//#define ERR_LOW_AJAX_MEM 36 // (not used any more) low memory (oappend)
#define ERR_LOW_BUF 37 // low memory (LED pixels buffer)
#define ERR_SYS_REBOOT 90 // reboot after error, trying to roll back
#define ERR_SYS_BROWNOUT 91 // reboot after brownout alert
#define ERR_PERSISTENT_THRESHOLD 100 // ToDO: errors below this value are non-persistent; persistent errors stay in the UI until restart
// ERR_PERSISTENT_THRESHOLD is a threshold value only - never assign directly to errorFlag
#define ERR_REBOOT_NEEDED 100 // reboot needed after changing hardware setting
#define ERR_POWEROFF_NEEDED 101 // power-cycle needed after changing hardware setting
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// JSON buffer lock owners
#define JSON_LOCK_UNKNOWN 255
Expand Down
32 changes: 30 additions & 2 deletions wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1567,8 +1567,36 @@ function readState(s,command=false)
case 19:
errstr = "A filesystem error has occured.";
break;
// error code from WLEDMM - not supported yet
// case 33:
// errstr = "Low Memory (generic RAM).";
// break;
// case 34:
// errstr = "Low Memory (effect data).";
// break;
// case 35:
// errstr = "Low Memory (WS data).";
// break;
// case 36:
// errstr = "Low Memory (oappend buffer).";
// break;
// case 37:
// errstr = "no memory for LEDs buffer.";
// break;
case 90:
errstr = "Unexpected Restart.";
break;
case 91:
errstr = "Brownout Restart.";
break;
case 100:
errstr = "Please reboot WLED to activate changed settings.";
break;
case 101:
errstr = "Please switch your device off and back on.";
break;
}
showToast('Error ' + s.error + ": " + errstr, true);
showToast((s.error<100) ? 'Error ': 'Note ' + s.error + ": " + errstr, true); // show "please restart" as a note, all others as errors
}

selectedPal = i.pal;
Expand Down Expand Up @@ -3597,4 +3625,4 @@ _C.addEventListener('touchstart', lock, false);

_C.addEventListener('mouseout', move, false);
_C.addEventListener('mouseup', move, false);
_C.addEventListener('touchend', move, false);
_C.addEventListener('touchend', move, false);
8 changes: 8 additions & 0 deletions wled00/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#else
#include <Update.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
#include "rom/rtc.h" // for rtc_get_reset_reason()
#include "esp32/rtc.h" // for bootloop detection
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
#include "soc/rtc.h"
Expand Down Expand Up @@ -998,6 +999,11 @@ RTC_NOINIT_ATTR static uint32_t bl_crashcounter;
RTC_NOINIT_ATTR static uint32_t bl_actiontracker;

static inline ResetReason rebootReason() {
// check RTC restart reason first - brownout is not reliably reported by esp_reset_reason()
if (rtc_get_reset_reason(0) == RTCWDT_BROWN_OUT_RESET) return ResetReason::Brownout; // core0 brownout

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

were you able to test this? I did never get a brownout in my tests, if I lowered the voltage it either worked or resulted in a "powerup" as a reasaon.
also according to https://esp32.com/viewtopic.php?t=34938 the rtc variant is lower level and I am not sure it has an advantage. Did you investigate?

@softhack007 softhack007 Jun 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes the RTC variant is running in MM for years, and I've seen it report some brownouts over that time (however mainly with the old framework, not tested with tasmota). Also some users have reported they saw the brownout message with MM.

Good point about testing - I can try to dig out one esp32 that sometimes browns out, and see what each crash reason function says.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

just to be sure, i'll ask the rabbit to dig through the esp-idf code ....

@coderabbitai please compare the esp-idf 4.4.8 implementations of rtc_get_reset_reason() and esp_reset_reason(). focus on esp32, esp32-c3 and esp32-s3, and look for the origins where the "brownout" restart reasons codes come from. Apart from rtc_get_reset_reason() allowing to query the status of core0 and core1 separately, are there other differences in esp-idf source code that make detecting a previous "brownout restart" more reliable with one or the other function call?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

esp-idf 4.4.8 source code rtc_get_reset_reason implementation RTCWDT_BROWN_OUT_RESET esp32 esp32-s3 esp32-c3

Length of output: 18039


🌐 Web query:

esp-idf 4.4.8 esp_reset_reason() implementation source code brownout ESP_RST_BROWNOUT how it reads reset reason register

Length of output: 16154


🌐 Web query:

esp-idf esp_reset_reason ESP_RST_BROWNOUT vs rtc_get_reset_reason RTCWDT_BROWN_OUT_RESET difference reliability brownout detection

Length of output: 15098



@softhack007 Here's what the esp-idf 4.4.x source reveals about these two functions.


How each function determines brownout

esp_reset_reason() — two-stage, hint-dependent

esp_reset_reason_init() (a __attribute__((constructor)) called at early startup) does:

  1. Reads the raw RTC reset register via esp_rom_get_reset_reason(PRO_CPU_NUM) — the same register that rtc_get_reset_reason(0) reads.
  2. Reads a software-written hint from RTC_RESET_CAUSE_REG (= RTC_CNTL_STORE6_REG).

For a "normal" brownout the IDF flow is:

BOD threshold crossed
  → brownout ISR fires (brownout.c:66)
    → esp_reset_reason_set_hint(ESP_RST_BROWNOUT)   ← writes hint to RTC_RESET_CAUSE_REG
    → esp_restart_noos() → SW CPU reset
next boot: raw reason = RESET_REASON_CPU0_SW + hint == ESP_RST_BROWNOUT
  → esp_reset_reason() returns ESP_RST_BROWNOUT ✓

The direct hardware path RESET_REASON_SYS_BROWN_OUTESP_RST_BROWNOUT also exists (no hint needed), but this only fires when the RTC BOD triggers autonomously without an IDF ISR in the loop.

Failure mode (confirmed by esp-idf #10834): If the voltage drops fast or deep enough that the brownout ISR cannot complete before the chip dies, the hint is never written. The IDF also arms the RTC WDT as a safety net during the reset sequence — if that WDT fires first, it causes a RTCWDT_RTC_RESET which:

  • Overwrites the raw reset reason in the hardware register, AND
  • Clears RTC_RESET_CAUSE_REG — the hint is gone.

Result: esp_reset_reason() returns ESP_RST_WDT instead of ESP_RST_BROWNOUT.


rtc_get_reset_reason() — raw hardware register, no software involvement

This reads the RTC reset reason register directly from ROM. RTCWDT_BROWN_OUT_RESET is set by the RTC BOD hardware circuit itself — it requires no ISR, no software path, no hint register. It is set before any IDF code runs, including the bootloader.

Failure mode (same issue thread): A very deep voltage drop (~1.3 V and below) can cause a secondary RTCWDT_RTC_RESET that overwrites even the hardware register. Quoted from the issue:

"If supply voltage drops to 2.3V → got RTCWDT_BROWN_OUT_RESET. If supply voltage drops to 1.3V → first got RTCWDT_BROWN_OUT_RESET, then later RTCWDT_RTC_RESET."


Summary of differences

Aspect rtc_get_reset_reason() esp_reset_reason()
Register read Raw RTC HW reset register (direct) Same register + software hint from RTC_RESET_CAUSE_REG
Requires ISR to complete? No — set by HW BOD before any code Yes (for the SW-mediated path)
Survives RTC WDT safety reset? Sometimes (not for deep drops) No — hint cleared by RTCWDT_RTC_RESET
Can be confused with ESP_RST_WDT? No Yes — confirmed bug in v4.4.x
Per-core query possible? Yes (core0 / core1) No — only PRO_CPU_NUM
ESP32-C3 No RTCWDT_BROWN_OUT_RESET enum Works normally

Conclusion

Checking rtc_get_reset_reason() first is clearly the more robust approach:

  1. It catches brownouts where the IDF software hint mechanism failed (fast voltage drop, ISR couldn't run to completion).
  2. It avoids the known esp-idf bug where esp_reset_reason() misreports ESP_RST_WDT after a brownout.
  3. The per-core query (core0 + core1) is an additional benefit for dual-core ESP32/S3.

The PR's strategy is well-founded. The only caveat is very severe drops (~1.3 V) where even the raw hardware register gets overwritten by a secondary RTC WDT reset — but in that scenario neither function is reliable, so there's nothing better to do. For moderate brownouts (voltage briefly dipping into the ~2.3–2.7 V range, which is the common real-world case), rtc_get_reset_reason() will correctly report RTCWDT_BROWN_OUT_RESET even when esp_reset_reason() would have returned ESP_RST_WDT.

🐇 ( >ω< ) digs through ROM tables


🧠 Learnings used
Learnt from: softhack007
Repo: wled/WLED PR: 5355
File: wled00/util.cpp:635-638
Timestamp: 2026-02-07T16:06:08.677Z
Learning: PSRAM-related compilation guards should enable PSRAM code only for ESP32 variants that actually include PSRAM: ESP32-C61, ESP32-C5, and ESP32-P4. Exclude ESP32-C3, ESP32-C6, and ESP8266 from these guards. Apply this rule across the codebase (not just wled00/util.cpp) by reviewing and updating PSRAM guards/macros in all relevant files (C/C++ headers and sources).

Learnt from: softhack007
Repo: wled/WLED PR: 4893
File: wled00/set.cpp:95-95
Timestamp: 2026-03-14T20:56:46.543Z
Learning: Guideline: Ensure WiFi hostname is set after WiFi.mode() but before WiFi.begin() to avoid default esp-XXXXXX hostname being used in DHCP. This ordering only takes effect after the STA interface exists (so avoid placing hostname setting before initConnection). In WLED, place the hostname configuration inside initConnection() (after WiFi.disconnect(true) and before WiFi.begin()) rather than in earlier boot code like deserializeConfig(). This rule should be applied in code reviews for WLED’s network initialization paths in wled00/*.cpp, and note that on ESP8266 the ordering is less strict but still desirable for consistency.

Learnt from: softhack007
Repo: wled/WLED PR: 4838
File: lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp:30-35
Timestamp: 2026-03-27T12:33:48.499Z
Learning: In C/C++ preprocessor conditionals (`#if`, `#elif`) GCC/Clang treat `&&` as short-circuit evaluated during preprocessing. This means guards like `#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)` are safe even if the macro/function-like macro on the RHS (e.g., `ESP_IDF_VERSION_VAL`) is not defined on some targets, because the RHS will not be expanded when the LHS is false (e.g., `defined(...)` evaluates to 0). During code review, avoid flagging such cases as “undefined function-like macro invocation” if they are protected by short-circuiting `defined(...) && ...`/`||` logic; some tools like cppcheck may not model this and can produce false positives. Also, don’t suggest refactoring that moves ESP32-specific includes/headers (e.g., `esp_idf_version.h`) outside of these guarded preprocessor blocks, since that will break targets (e.g., ESP8266) where the headers don’t exist.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/cfg.cpp:673-673
Timestamp: 2026-03-27T21:17:45.980Z
Learning: When calling raw lwIP APIs (e.g., around `ntpUdp.begin()` or any `lwIP`/`tcpip`-layer call) in this codebase on ESP32 Arduino-ESP32 platforms where core locking/checking is enabled, wrap the lwIP call with `LOCK_TCPIP_CORE()` / `UNLOCK_TCPIP_CORE()` from `lwip/tcpip.h`. This prevents thread-safety/core-violation crashes without requiring `sdkconfig` changes.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/cfg.cpp:673-673
Timestamp: 2026-03-27T21:17:45.980Z
Learning: When using lwIP “raw” APIs in WLED on ESP32 (Arduino-ESP32 / IDF 5.5+), don’t rely on LOCK_TCPIP_CORE()/UNLOCK_TCPIP_CORE() unless CONFIG_LWIP_TCPIP_CORE_LOCKING=y is guaranteed. CONFIG_LWIP_CHECK_THREAD_SAFETY=y can trigger the assertion “Required to lock TCPIP core functionality!” when raw lwIP calls occur off the TCPIP thread. If the locking mode isn’t enabled (or can’t be changed via sdkconfig), schedule lwIP work (e.g., ntpUdp.begin() and similar raw lwIP calls) via tcpip_callback() so it runs on the TCPIP thread; this should work regardless of the locking-mode setting. Review any similar raw lwIP usage for correct thread/context handling.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/wled.cpp:698-700
Timestamp: 2026-03-28T01:37:15.541Z
Learning: In this WLED codebase, when using `DEBUG_PRINTLN(F("..."))`, an explicit trailing `\n` inside the `F("...")` string (e.g., `DEBUG_PRINTLN(F("Warning!\n"))`) may be intentional to create a blank line in debug output as a visual separator. During code review, do not automatically flag these as “double newline” or recommend removing the `\n`—first verify with the author/context that the extra newline is deliberate.

Learnt from: willmmiles
Repo: wled/WLED PR: 5462
File: wled00/json.cpp:1189-1198
Timestamp: 2026-03-30T15:32:02.808Z
Learning: When working with WLED strings stored in PROGMEM on ESP8266/ESP32, assume you can’t use `strchr_P` for character scanning. If you need to scan a PROGMEM string for a character (e.g., searching for '@' within effect data), review changes to ensure they perform an explicit RAM copy first (e.g., via `strncpy_P` into a buffer) and then scan in RAM. Avoid proposing incremental/streaming PROGMEM character-by-character scans without a RAM buffer on these targets, since it’s impractical with the available APIs.

Learnt from: DedeHai
Repo: wled/WLED PR: 5464
File: wled00/FX_fcn.cpp:1699-1701
Timestamp: 2026-04-09T07:26:09.816Z
Learning: In WLED’s WLED code, if a pixel/buffer allocation uses `BFRALLOC_NOBYTEACCESS` (and especially on classic ESP32 where byte-level access to IRAM-resident buffers is unsafe), avoid using byte-wise operations like `memset`/`memcpy` on that buffer. Specifically, do not combine `BFRALLOC_CLEAR | BFRALLOC_NOBYTEACCESS` and do not perform `memcpy`/`memset` over `Segment::pixels` (e.g., in `setGeometry()`/`finalizeInit()` or copy ctor/assignment). Instead, use element-wise 32-bit access (loop over `uint32_t*` and assign/copy per element) to ensure 32-bit access only.

Learnt from: DedeHai
Repo: wled/WLED PR: 5464
File: wled00/FX_fcn.cpp:1699-1701
Timestamp: 2026-04-09T09:14:29.218Z
Learning: In WLED, when reviewing ESP32 code that uses pixel buffers allocated with `BFRALLOC_NOBYTEACCESS` (e.g., `WS2812FX::_pixels` and `Segment::pixels`), treat `memset`/`memcpy` on those buffers as an intentionally low-risk, accepted pattern. These buffers are designed to be IRAM-safe for 32-bit-only access, and the Xtensa newlib assembly implementations perform 32-bit word stores for word-aligned, word-multiple-sized buffers (so the IRAM constraint is satisfied in practice). Do not flag `memset`/`memcpy` on `BFRALLOC_NOBYTEACCESS` pixel buffers as a critical bug; if any concern arises, downgrade severity to low and focus on potential future regressions rather than an immediate correctness violation.

Learnt from: softhack007
Repo: wled/WLED PR: 5508
File: wled00/FX.cpp:4137-4142
Timestamp: 2026-04-16T09:49:58.587Z
Learning: In WLED effect/overlay implementations (C++ under wled00), ensure effects operate through the SEGMENT.* read/write APIs for their own pixels/overlay state. Reserve global frame reads/writes like strip.getPixelColor* for copy_* style post-processing only. Do not manually composite/blend across lower layers inside the effect; segment-to-segment blending is handled later by WS2812FX::blendSegment(), so effects should produce their per-segment contributions without trying to composite with underlying layers themselves.

Learnt from: softhack007
Repo: wled/WLED PR: 4843
File: usermods/elastic_collisions/Elastic_Collisions.cpp:1-1
Timestamp: 2026-05-06T22:10:27.517Z
Learning: In C/C++, do not flag precedence issues for expressions that mix additive operators (`+`, `-`) with shift operators (`<<`, `>>`) based on the assumption that shifts bind tighter. Per C/C++ operator precedence, `+`/`-` have higher precedence than `<<`/`>>` (e.g., `x - edge0 << 8` parses as `(x - edge0) << 8`). When reviewing WLED (and other) fixed-point/bit-manipulation code, confirm the intended parse using cppreference before reporting a precedence bug for mixed `-`/`+` and `<<`/`>>` expressions.

Learnt from: softhack007
Repo: wled/WLED PR: 5599
File: usermods/audioreactive/audio_reactive.cpp:1303-1308
Timestamp: 2026-05-11T16:00:13.574Z
Learning: When targeting arduino-esp32 v3.x (ESP-IDF 5.x), treat `WiFiUDP::flush()` as deprecated and ineffective for RX draining; it should not be used to clear/drain the UDP receive buffer. Instead, use `WiFiUDP::clear()`, which was introduced in arduino-esp32 v3.0 and is marked as the deprecated replacement via `NetworkUdp.h`.

If the code must compile and behave correctly across both arduino-esp32 v2.x (ESP-IDF < 5) and v3.x (ESP-IDF >= 5), use conditional compilation:
- for `ESP_IDF_VERSION_MAJOR < 5`: use `udp.flush()`
- for `ESP_IDF_VERSION_MAJOR >= 5`: use `udp.clear()`

Do not flag `WiFiUDP::clear()` usages for ESP-IDF >= 5 as non-existent—this is the documented correct replacement.

Learnt from: softhack007
Repo: wled/WLED PR: 5635
File: wled00/FX.cpp:2680-2683
Timestamp: 2026-05-20T11:34:52.127Z
Learning: In WLED v16+, values returned by SEGMENT.color_from_palette (ColorFromPalette/ColorFromPalette pipeline) are already in linear (gamma-free) space. When using palette-derived colors for brightness calculations, comparisons, or blending (e.g., Twinklefox “twinkle pixels” that come from palette lookup), do not apply gamma32inv() (or other gamma32 inversions) to those palette-derived colors before scaling/brightness logic. Applying gamma32inv() to already-linear palette colors will artificially brighten and can shift hue. Only apply gamma8/32 inversions to inputs that are known to still be gamma-encoded (e.g., certain background/other sources as appropriate); keep palette-derived colors as-is for linear comparisons.

Learnt from: softhack007
Repo: wled/WLED PR: 5649
File: usermods/sht/sht.cpp:146-152
Timestamp: 2026-05-29T13:10:25.470Z
Learning: For WLED C/C++ code targeting both ESP8266 Arduino and ESP32 Arduino cores, do not call `ESP.getChipModel()` unconditionally. In the ESP8266 Arduino framework (`esp8266/Arduino`), `EspClass` has no `getChipModel()` method (it is ESP32-only in `espressif/arduino-esp32`). When you need chip model info, wrap the call with `#if defined(ARDUINO_ARCH_ESP32)` (or `#ifdef ARDUINO_ARCH_ESP32`): use `ESP.getChipModel()` in the ESP32 branch, and in the `#else` branch hardcode `

Learnt from: softhack007
Repo: wled/WLED PR: 5683
File: wled00/wled.cpp:181-181
Timestamp: 2026-06-16T21:59:57.672Z
Learning: In WLED C++ code, when computing per-LED transmission time / wait timeouts for clockless LED strip updates, do not hardcode a universal assumption like 30µs/LED or 24 bits/pixel. Those values are only valid for specific WS281x-style RGB strips (e.g., 800kHz). Instead, derive timing from the actual strip type’s bit/byte counts (see wled00/bus_wrapper.h) or query bytes-per-pixel from BusManager. If you can’t reliably query the real value, use a conservative worst-case (roughly 50–60µs/LED) for all clockless strips so timeouts remain correct across RGBW/SK6812 (~32 bits), RGB-CCT (~40+ bits), and 6-channel strips (~60µs+/LED at 800kHz). SPI-based 2-wire strips (APA102/DotStar) generally use separate SPI clocking and should not be handled with the same RMT/clockless wait assumptions.

Learnt from: softhack007
Repo: wled/WLED PR: 5683
File: wled00/wled.cpp:181-181
Timestamp: 2026-06-16T22:08:01.369Z
Learning: In WLED code that computes per-LED transmission time for strip update wait timeouts (especially for clockless LED protocols), do not hardcode assumptions like 30µs/LED or 24 bits/pixel. Those constants are only valid for WS281x-style 3-channel RGB at 800kHz; other strip types (RGBW/SK6812, RGB-CCT, 6-channel, and “400Kbps” variants such as APA106/TM18xx/UCS890x) can use different bits-per-pixel and/or run at 400kHz/600kHz, changing the per-LED time substantially. The correct approach is to derive timing from the actual bus configuration by querying the strip’s bit clock (e.g., 800kHz vs 600kHz/400kHz) and the true bytes-per-pixel/bits-per-pixel via BusManager/bus_wrapper.h instead of using fixed constants. Use bus_wrapper.h’s per-strip bit/byte counts and clock rates for the protocol; only consider a conservative worst-case (e.g., ~80–100µs/LED) if you truly cannot determine the exact timing from the bus data.

Learnt from: softhack007
Repo: wled/WLED PR: 5691
File: wled00/FX_fcn.cpp:1846-1863
Timestamp: 2026-06-19T20:43:49.505Z
Learning: In this WLED codebase, `WS2812FX` is effectively a singleton and the global instance is named `strip`. Therefore, within a `WS2812FX` member function, calls like `strip.isUpdating()` are functionally equivalent to `this->isUpdating()` / `isUpdating()`. During code review, do not treat such usage as a functional/“critical” bug; if flagged at all, classify it only as a minor style/clean-code concern (e.g., prefer `this->`/`isUpdating()` for clarity).

#if SOC_CPU_CORES_NUM > 1
if (rtc_get_reset_reason(1) == RTCWDT_BROWN_OUT_RESET) return ResetReason::Brownout; // core1 brownout
#endif
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_BROWNOUT) return ResetReason::Brownout;
if (reason == ESP_RST_SW) return ResetReason::Software;
Expand Down Expand Up @@ -1032,6 +1038,7 @@ static bool detectBootLoop() {
case ResetReason::Crash:
{
DEBUG_PRINTLN(F("crash detected!"));
errorFlag = ERR_SYS_REBOOT;
uint32_t rebootinterval = rtctime - bl_last_boottime;
if (rebootinterval < BOOTLOOP_INTERVAL_MILLIS) {
bl_crashcounter++;
Expand All @@ -1052,6 +1059,7 @@ static bool detectBootLoop() {
case ResetReason::Brownout:
// crash due to brownout can't be detected unless using flash memory to store bootloop variables
DEBUG_PRINTLN(F("brownout detected"));
errorFlag = ERR_SYS_BROWNOUT;
//restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
break;
}
Expand Down
Loading