Skip to content
Closed
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
717 changes: 717 additions & 0 deletions lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,8 @@ lib_deps =
ESPAsyncTCP @ 1.2.2
ESPAsyncUDP
;; makuna/NeoPixelBus @ 2.6.9 ;; WLEDMM use if you have problems with 2.7.5
makuna/NeoPixelBus @ 2.7.5
makuna/NeoPixelBus @ 2.7.9 ;; 2.7.9 has improved stability of bitbang outputs on 8266
ESP8266PWM
${env.lib_deps}

;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48
Expand Down
37 changes: 37 additions & 0 deletions tools/stress_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
# Some web server stress tests
#
# Perform a large number of parallel requests, stress testing the web server
# TODO: some kind of performance metrics

# Accepts three command line arguments:
# - first argument - mandatory - IP or hostname of target server
# - second argument - target type (optional)
# - third argument - xfer count (for replicated targets) (optional)
HOST=$1
declare -n TARGET_STR="${2:-JSON_LARGER}_TARGETS"
REPLICATE_COUNT=$(("${3:-10}"))

PARALLEL_MAX=${PARALLEL_MAX:-50}

CURL_ARGS="--compressed --parallel --parallel-immediate --parallel-max ${PARALLEL_MAX}"
CURL_PRINT_RESPONSE_ARGS="-w %{http_code}\n"

JSON_TARGETS=('json/state' 'json/info' 'json/si', 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Comma in Bash array splits an element; remove it

This makes 'json/si,' a literal entry.

-JSON_TARGETS=('json/state' 'json/info' 'json/si', 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
+JSON_TARGETS=('json/state' 'json/info' 'json/si' 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
📝 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
JSON_TARGETS=('json/state' 'json/info' 'json/si', 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
JSON_TARGETS=('json/state' 'json/info' 'json/si' 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 20-20: JSON_TARGETS appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 20-20: Use spaces, not commas, to separate array elements.

(SC2054)

🤖 Prompt for AI Agents
In tools/stress_test.sh around line 20, the Bash array contains a stray comma
causing the element 'json/si,' to include the comma; remove the comma so entries
are space-separated (e.g., 'json/si' 'json/palettes') ensuring no punctuation
inside the quoted elements and then save the file.

FILE_TARGETS=('' 'iro.js' 'rangetouch.js' 'settings' 'settings/wifi')
# Replicate one target many times
function replicate() {
printf "${1}?%d " $(seq 1 ${REPLICATE_COUNT})
}
read -a JSON_TINY_TARGETS <<< $(replicate "json/nodes")
read -a JSON_SMALL_TARGETS <<< $(replicate "json/info")
read -a JSON_LARGE_TARGETS <<< $(replicate "json/si")
read -a JSON_LARGER_TARGETS <<< $(replicate "json/fxdata")

# Expand target URLS to full arguments for curl
TARGETS=(${TARGET_STR[@]})
#echo "${TARGETS[@]}"
FULL_TGT_OPTIONS=$(printf "http://${HOST}/%s -o /dev/null " "${TARGETS[@]}")
#echo ${FULL_TGT_OPTIONS}

time curl ${CURL_ARGS} ${FULL_TGT_OPTIONS}
46 changes: 46 additions & 0 deletions tools/udp_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import numpy as np
import socket

class WledRealtimeClient:
def __init__(self, wled_controller_ip, num_pixels, udp_port=21324, max_pixels_per_packet=126):
self.wled_controller_ip = wled_controller_ip
self.num_pixels = num_pixels
self.udp_port = udp_port
self.max_pixels_per_packet = max_pixels_per_packet
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._prev_pixels = np.full((3, self.num_pixels), 253, dtype=np.uint8)
self.pixels = np.full((3, self.num_pixels), 1, dtype=np.uint8)

def update(self):
# Truncate values and cast to integer
self.pixels = np.clip(self.pixels, 0, 255).astype(np.uint8)
p = np.copy(self.pixels)

idx = np.where(~np.all(p == self._prev_pixels, axis=0))[0]
num_pixels = len(idx)
n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
idx_split = np.array_split(idx, n_packets)

header = bytes([1, 2]) # WARLS protocol header
for packet_indices in idx_split:
data = bytearray(header)
for i in packet_indices:
data.extend([i, *p[:, i]]) # Index and RGB values
self._sock.sendto(bytes(data), (self.wled_controller_ip, self.udp_port))
Comment on lines +21 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard zero-changes path; array_split(0) raises

When no pixels change, n_packets becomes 0 and np.array_split() throws.

-        n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
-        idx_split = np.array_split(idx, n_packets)
+        if num_pixels == 0:
+            return
+        n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
+        idx_split = np.array_split(idx, n_packets)

Also consider early-return before building header for the no-change case.

📝 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
n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
idx_split = np.array_split(idx, n_packets)
header = bytes([1, 2]) # WARLS protocol header
for packet_indices in idx_split:
data = bytearray(header)
for i in packet_indices:
data.extend([i, *p[:, i]]) # Index and RGB values
self._sock.sendto(bytes(data), (self.wled_controller_ip, self.udp_port))
if num_pixels == 0:
return
n_packets = (num_pixels + self.max_pixels_per_packet - 1) // self.max_pixels_per_packet
idx_split = np.array_split(idx, n_packets)
header = bytes([1, 2]) # WARLS protocol header
for packet_indices in idx_split:
data = bytearray(header)
for i in packet_indices:
data.extend([i, *p[:, i]]) # Index and RGB values
self._sock.sendto(bytes(data), (self.wled_controller_ip, self.udp_port))
🤖 Prompt for AI Agents
In tools/udp_test.py around lines 21 to 29, n_packets can be zero when there are
no changed pixels which makes np.array_split(idx, n_packets) raise; fix this by
guarding the zero-changes path: check if the index array (idx) or num_pixels is
zero and return early before creating the header or splitting (e.g., if idx.size
== 0: return), otherwise compute n_packets and proceed; this prevents the
np.array_split error and avoids building/sending empty packets.


self._prev_pixels = np.copy(p)



################################## LED blink test ##################################
if __name__ == "__main__":
WLED_CONTROLLER_IP = "192.168.1.153"
NUM_PIXELS = 255 # Amount of LEDs on your strip
import time
wled = WledRealtimeClient(WLED_CONTROLLER_IP, NUM_PIXELS)
print('Starting LED blink test')
while True:
for i in range(NUM_PIXELS):
wled.pixels[1, i] = 255 if wled.pixels[1, i] == 0 else 0
wled.update()
time.sleep(.01)
6 changes: 3 additions & 3 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4116,7 +4116,7 @@ uint16_t mode_pacifica()

// Increment the four "color index start" counters, one for each wave layer.
// Each is incremented at a different speed, and the speeds vary over time.
uint16_t sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step, sCIStart4 = SEGENV.step >> 16;
unsigned sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step & 0xFFFF, sCIStart4 = (SEGENV.step >> 16);
uint32_t deltams = (FRAMETIME >> 2) + ((FRAMETIME * SEGMENT.speed) >> 7);
uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7);
strip.now = deltat;
Expand All @@ -4131,7 +4131,7 @@ uint16_t mode_pacifica()
sCIStart3 -= (deltams1 * beatsin88(501,5,7));
sCIStart4 -= (deltams2 * beatsin88(257,4,6));
SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2;
SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3;
SEGENV.step = (sCIStart4 << 16) | (sCIStart3 & 0xFFFF);

// Clear out the LED array to a dim background blue-green
//SEGMENT.fill(132618);
Expand Down Expand Up @@ -4162,7 +4162,7 @@ uint16_t mode_pacifica()
c.green = scale8(c.green, 200);
c |= CRGB( 2, 5, 7);

SEGMENT.setPixelColor(i, c.red, c.green, c.blue);
SEGMENT.setPixelColor(i, c);
}

strip.now = nowOld;
Expand Down
2 changes: 1 addition & 1 deletion wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@

// string temp buffer (now stored in stack locally) // WLEDMM ...which is actually not the greatest design choice on ESP32
#ifdef ESP8266
#define SETTINGS_STACK_BUF_SIZE 2048
#define SETTINGS_STACK_BUF_SIZE 2560
#else
#if !defined(USERMOD_AUDIOREACTIVE)
#define SETTINGS_STACK_BUF_SIZE 3834 // WLEDMM added 696+32 bytes of margin (was 3096)
Expand Down
3 changes: 2 additions & 1 deletion wled00/data/update.htm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
<h2>MoonMod WLED Software Update</h2>
<form method='POST' action='/update' id='uf' enctype='multipart/form-data' onsubmit="U()">
<span class="sip">##VERSION##</span><br> <!--WLEDMM: show bin name-->
Download the latest release: <a href="https://github.com/MoonModules/WLED/releases" target="_blank">
Download the latest binary:&nbsp;<a href="https://github.com/MoonModules/WLED/releases" target="_blank"
style="vertical-align: text-bottom; display: inline-flex;">
<img src="https://img.shields.io/github/release/MoonModules/WLED.svg?style=flat-square"></a><br>
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
<button type="submit">Update!</button><br>
Expand Down
5 changes: 2 additions & 3 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
// lx parser
#ifdef WLED_ENABLE_LOXONE
int lx = elem[F("lx")] | -1;
if (lx > 0) {
if (lx >= 0) {
parseLxJson(lx, id, false);
}
int ly = elem[F("ly")] | -1;
if (ly > 0) {
if (ly >= 0) {
parseLxJson(ly, id, true);
}
#endif
Expand Down Expand Up @@ -573,7 +573,6 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
ps = presetCycCurr;
if (root["win"].isNull() && getVal(root["ps"], &ps, 0, 0) && ps > 0 && ps < 251 && ps != currentPreset) {
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
presetCycCurr = ps;
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(ps, callMode); // async load from file system (only preset ID was specified)
if (iAmGroot) suspendStripService = false; // WLEDMM release lock
Expand Down
12 changes: 11 additions & 1 deletion wled00/presets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,21 @@ void initPresetsFile()
f.close();
}

#if 0 // not used in WLEDMM
bool applyPresetFromPlaylist(byte index)
{
DEBUG_PRINTF_P(PSTR("Request to apply preset: %d\n"), index);
presetToApply = presetCycCurr = index;
callModeToApply = CALL_MODE_DIRECT_CHANGE;
return true;
}
#endif

bool applyPreset(byte index, byte callMode)
{
DEBUG_PRINT(F("Request to apply preset: "));
DEBUG_PRINTLN(index);
presetToApply = index;
presetToApply = presetCycCurr = index;
callModeToApply = callMode;
return true;
}
Expand Down
39 changes: 26 additions & 13 deletions wled00/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,33 @@ bool isAsterisksOnly(const char* str, byte maxLen)
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t module)
{
unsigned long now = millis();

while (jsonBufferLock && millis()-now < 1100) delay(1); // wait for fraction for buffer lock

#if defined(ARDUINO_ARCH_ESP32)
// Use a recursive mutex type in case our task is the one holding the JSON buffer.
// This can happen during large JSON web transactions. In this case, we continue immediately
// and then will return out below if the lock is still held.
if (xSemaphoreTakeRecursive(jsonBufferLockMutex, 1100) == pdFALSE) return false; // timed out waiting
#elif defined(ARDUINO_ARCH_ESP8266)
// If we're in system context, delay() won't return control to the user context, so there's
// no point in waiting.
if (can_yield()) {
unsigned long now = millis();
while (jsonBufferLock && (millis()-now < 1100)) delay(1); // wait for fraction for buffer lock
}
#else
#error Unsupported task framework - fix requestJSONBufferLock
#endif
// If the lock is still held - by us, or by another task
if (jsonBufferLock) {
USER_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by "));
USER_PRINT(jsonBufferLock);
USER_PRINTLN(")");
return false; // waiting time-outed
DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), module, jsonBufferLock);
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
return false;
}

jsonBufferLock = module ? module : 255;
DEBUG_PRINT(F("JSON buffer locked. ("));
DEBUG_PRINT(jsonBufferLock);
DEBUG_PRINTLN(")");
DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock);
fileDoc = &doc; // used for applying presets (presets.cpp)
doc.clear();
return true;
Expand All @@ -238,11 +250,12 @@ bool requestJSONBufferLock(uint8_t module)

void releaseJSONBufferLock()
{
DEBUG_PRINT(F("JSON buffer released. ("));
DEBUG_PRINT(jsonBufferLock);
DEBUG_PRINTLN(")");
fileDoc = nullptr;
DEBUG_PRINTF_P(PSTR("JSON buffer released. (%d)\n"), jsonBufferLock);
jsonBufferLock = 0;
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
}


Expand Down
8 changes: 8 additions & 0 deletions wled00/wled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ static int wledmm_get_tcp_stacksize(void) {
}
#endif


extern "C" void usePWMFixedNMI(); // PWM bugfix for 8266

/*
* Main WLED class implementation. Mostly initialization and connection logic
*/
Expand Down Expand Up @@ -634,6 +637,11 @@ void WLED::setup()
#else
DEBUG_PRINTLN(F("PSRAM not used."));
#endif

#ifdef ESP8266
usePWMFixedNMI(); // link the 8266 NMI fix
#endif

#endif
#if defined(ARDUINO_ARCH_ESP32)
if (strncmp("ESP32-PICO", ESP.getChipModel(), 10) == 0) { // WLEDMM detect pico board at runtime
Expand Down
10 changes: 8 additions & 2 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,6 @@ WLED_GLOBAL int8_t irPin _INIT(IRPIN);
constexpr uint8_t hardwareTX = 1;
#endif

//WLED_GLOBAL byte presetToApply _INIT(0);

WLED_GLOBAL char ntpServerName[33] _INIT("0.wled.pool.ntp.org"); // NTP server to use

// WiFi CONFIG (all these can be changed via web UI, no need to set them here)
Expand Down Expand Up @@ -374,7 +372,11 @@ WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load
//if true, a segment per bus will be created on boot and LED settings save
//if false, only one segment spanning the total LEDs is created,
//but not on LED settings save if there is more than one segment currently
#ifdef WLED_AUTOSEGMENTS
WLED_GLOBAL bool autoSegments _INIT(true);
#else
WLED_GLOBAL bool autoSegments _INIT(false);
#endif
WLED_GLOBAL bool correctWB _INIT(false); // CCT color correction of RGB color
WLED_GLOBAL bool cctFromRgb _INIT(false); // CCT is calculated from RGB instead of using seg.cct
WLED_GLOBAL bool gammaCorrectCol _INIT(true ); // use gamma correction on colors // WLEDMM that's what you would think, but the code tells a different story.
Expand Down Expand Up @@ -822,6 +824,10 @@ WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> doc;
#endif // WLEDMM end
WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0);

#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(xSemaphoreCreateRecursiveMutex());
#endif
Comment on lines +827 to +829
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Oct 21, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential initialization order issue with mutex creation.

Creating the recursive mutex with xSemaphoreCreateRecursiveMutex() at global scope (static initialization time) may occur before FreeRTOS is fully initialized on ESP32. This could lead to:

  • Mutex creation failure (returns NULL)
  • Undefined behavior if the scheduler isn't ready
  • Race conditions during early initialization

Consider initializing the mutex explicitly in setup() or using a lazy initialization pattern (create on first use with a check).

// Suggested approach 1: Initialize in setup()
#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(NULL);
#endif

// In setup() or initInterfaces():
void WLED::setup() {
  #if defined(ARDUINO_ARCH_ESP32)
  if (jsonBufferLockMutex == NULL) {
    jsonBufferLockMutex = xSemaphoreCreateRecursiveMutex();
  }
  #endif
  // ... rest of setup
}
// Suggested approach 2: Lazy initialization with helper
#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(NULL);

inline void ensureJsonMutexInit() {
  if (jsonBufferLockMutex == NULL) {
    jsonBufferLockMutex = xSemaphoreCreateRecursiveMutex();
  }
}
#endif
🤖 Prompt for AI Agents
In wled00/wled.h around lines 827-829, the global creation of the recursive
mutex via xSemaphoreCreateRecursiveMutex() can run before FreeRTOS is ready;
change the global initialization to NULL (do not call
xSemaphoreCreateRecursiveMutex() at static init time) and create the mutex
explicitly during WLED setup (or use a small helper for lazy init that checks
for NULL and calls xSemaphoreCreateRecursiveMutex() on first use), ensuring you
guard creation with a NULL check and store the returned handle back into
jsonBufferLockMutex.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think the bunny is right here, calling xSemaphoreCreateRecursiveMutex(); before we have entered main() and setup() may or may not work.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@softhack007, thanks for confirming! You're absolutely right—calling xSemaphoreCreateRecursiveMutex() during static initialization is risky since FreeRTOS may not be ready yet.

The safest fix is to initialize the mutex handle to NULL at global scope and then create it explicitly in WLED::setup():

In wled00/wled.h (around line 827):

#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(NULL);
#endif

In wled00/wled.cpp (in the setup() function):

void WLED::setup() {
  #if defined(ARDUINO_ARCH_ESP32)
  if (jsonBufferLockMutex == NULL) {
    jsonBufferLockMutex = xSemaphoreCreateRecursiveMutex();
  }
  #endif
  // ... rest of setup code
}

This ensures the mutex is created after FreeRTOS is fully initialized and avoids any potential race conditions or initialization failures. You should also add a check in the lock/unlock functions (in wled00/util.cpp) to handle the case where mutex creation might fail, though that should be rare.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The things I have learned since I wrote that code!

In practice it works: this is because xSemaphoreCreateRecursiveMutex() happens to need only malloc() to be available, it doesn't interact with any of FreeRTOS's internal data structures. malloc() must be ready to use as part of the C runtime before any static initializers are run.

That said, better practice is to use a pre-allocated StaticSemaphore_t and xSemaphoreCreateRecursiveMutexStatic() in setup(); it can't fail if the memory is valid, so there's no need to consider the failure case at point of usage. (Also saves 8 bytes of heap overhead.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@coderabbitai please keep this as a learning - it may come handy later, and I don't have a notepad at hand atm 😉

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@softhack007, noted! I've saved @willmmiles's insight to long-term memory for future reference. 🐇


✏️ Learnings added
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 152
File: wled00/wled.h:827-829
Timestamp: 2026-04-06T21:23:55.738Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS (wled00/wled.h), calling `xSemaphoreCreateRecursiveMutex()` during C++ static initialization (before `main()`/`setup()`) works in practice because it only requires `malloc()`, which is available before any static initializers run and does not interact with FreeRTOS internal data structures. However, the preferred pattern is to pre-allocate a `StaticSemaphore_t` buffer and call `xSemaphoreCreateRecursiveMutexStatic()` in `setup()` instead — this avoids any heap allocation failure at init, eliminates the need to handle a NULL return at every usage site, and saves 8 bytes of heap overhead compared to the dynamic variant. Insight attributed to willmmiles.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T21:14:08.797Z
Learning: In MoonModules/WLED-MM on ESP32 with FreeRTOS, heap operations (malloc, free, realloc) are thread-safe but must NOT be called inside critical sections (portENTER_CRITICAL/portEXIT_CRITICAL) as they can block and involve the scheduler. Use recursive mutexes (xSemaphoreTakeRecursive/Give) instead to protect buffer reallocations. Critical sections (spinlocks) are only appropriate for very short, non-blocking operations.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:29:14.927Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized and atomic: guard all write paths with a dedicated file-level mutex (presetFileMux) and use temp-file + rename to replace the file. The “immediate write” branch in presets.cpp can race with async doSaveState() and corrupt presets.json; prefer routing all saves through the async path or enforce the same file mutex + atomic write there.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 293
File: wled00/udp.cpp:668-669
Timestamp: 2025-12-21T22:26:03.261Z
Learning: In MoonModules/WLED-MM on 32-bit ESP32 platforms, changing pixel index variables to `int` type to align with the `setPixelColor(int, ...)` API signature is intentional and safe. Maximum pixel addresses are bounded by UINT16_MAX, and `setPixelColor()` performs internal overflow checks.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-22T20:31:17.991Z
Learning: In MoonModules/WLED-MM on ESP32/ESP8266 platforms with limited heap memory, when reallocating buffers (e.g., LED buffers), always free the old buffer before allocating the new one. Allocating the new buffer first would temporarily require double the memory and could cause malloc failures when available RAM is low. The free-then-allocate pattern is preferred for resource-constrained embedded devices, even if it creates a brief nullptr window that must be protected by proper synchronization.

Learnt from: netmindz
Repo: MoonModules/WLED-MM PR: 248
File: platformio.ini:1613-1613
Timestamp: 2025-07-02T23:22:57.175Z
Learning: In WLED platformio.ini, the particle system disable flags (WLED_DISABLE_PARTICLESYSTEM1D and WLED_DISABLE_PARTICLESYSTEM2D) are intentionally varied across different build environments based on platform memory constraints. More memory-limited platforms (like ESP8266, ESP32-C3, ESP32-S2) disable both 1D and 2D particle systems, while platforms with more available memory (like esp32_4MB_V4_S with 4MB flash) may only disable the 1D version to preserve flash space while keeping 2D functionality available.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-23T21:36:41.138Z
Learning: In MoonModules/WLED-MM, presets.json writes must be serialized with a dedicated file-level mutex (presetFileMux) to prevent corruption when the immediate write path in savePreset() races with the async write path in doSaveState(). Use a 2500ms timeout for the mutex to accommodate typical 200KB file writes (~750ms) with safety margin.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 308
File: platformio.ini:560-560
Timestamp: 2026-01-07T23:30:25.779Z
Learning: In platformio.ini, ESP32-S3 environments intentionally have WLED_USE_SHARED_RMT commented out (keeping RMTHI driver enabled) for testing purposes, while ESP32-S2 has it enabled (using shared RMT driver) to avoid boot loops. This inconsistency is temporary while investigating why RMTHI causes interrupt watchdog resets on S3. Once the S3 issue is understood, both S2 and S3 will use the same driver flags.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-02T22:23:41.534Z
Learning: In MoonModules/WLED-MM, the approved VLA guidance for cpp.instructions.md reads: "Avoid Variable Length Arrays (VLAs): GCC/Clang support VLAs as an extension (not part of the C++ standard), so they look like a legitimate feature — but they are allocated on the stack at runtime. On ESP32/ESP8266, FreeRTOS task stacks are typically only 2–8 KB; a VLA whose size depends on a runtime parameter (segment dimensions, pixel counts, etc.) can silently exhaust the stack and cause the program to behave in unexpected ways or crash. Prefer a fixed-size array with a compile-time bound, or heap allocation (d_malloc / p_malloc) for dynamically sized buffers. Any VLA must be explicitly justified in the PR."

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 343
File: fs_data/README.md:12-14
Timestamp: 2026-04-05T13:09:31.720Z
Learning: In MoonModules/WLED-MM, empty JSON files (containing only `{}`) in fs_data (e.g., cfg.json, presets.json, wsec.json) are intended to be treated the same as non-existing files by the deserialization code in wled00/cfg.cpp and related files. The bugfix for this robustness issue (avoiding null/empty-object dereference) is tracked in PR `#355`. After the fix, `{}` is a safe placeholder for these config files.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2025-12-25T00:46:23.516Z
Learning: In MoonModules/WLED-MM file operations using the global file handle `f` in file.cpp, writeObjectToFile() must check `if (doCloseFile) closeFile();` before opening a new file, following the same pattern as readObjectFromFile(). Without this check, opening a new file while a previous file operation has set doCloseFile=true will overwrite the global file handle without flushing buffers, causing data corruption. This is critical for preventing presets.json corruption when async and immediate write paths race.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: wled00/fcn_declare.h:403-406
Timestamp: 2025-10-22T21:59:24.331Z
Learning: In MoonModules/WLED-MM PR `#270`, the out-of-bounds null-terminator write in wled00/util.cpp (extractModeSlider) is deferred and tracked in Issue `#272`; do not address it within PR `#270`.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-04T00:29:49.113Z
Learning: In MoonModules/WLED-MM, function-level doc comment blocks (e.g. /* ***** */ style headers) are NOT required during code reviews, especially not for previously existing functions. Do not flag missing function-level comments as review issues in this repository.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-04T14:32:02.598Z
Learning: In MoonModules/WLED-MM, clang-format is intentionally NOT enforced via CI or .clang-format config because it would cause merge conflicts when cherry-picking features from upstream WLED (which uses different formatting). Instead, "soft" formatting rules are documented in .github/cpp.instructions.md. Do not recommend adding clang-format to CI as a follow-up to missing automated enforcement — the decision is deliberate.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 355
File: wled00/presets.cpp:130-130
Timestamp: 2026-04-05T13:41:48.199Z
Learning: In MoonModules/WLED-MM presets.cpp, the minimal JSON for a single real preset entry in presets.json format is `{"1":{}}` = 8 bytes (1-digit index with empty body). The `f.size() < 3` threshold in initPresetsFile() and appendObjectToFile() safely identifies only empty/uninitialized files (like `{}` = 2 bytes) well below this 8-byte minimum. Changing the threshold to `< 4` would also catch `{ }` (3 bytes) but would create an inconsistency with appendObjectToFile() which uses the same `< 3` threshold.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 355
File: wled00/presets.cpp:130-130
Timestamp: 2026-04-05T13:46:54.272Z
Learning: In MoonModules/WLED-MM presets.cpp, the preferred threshold for detecting an uninitialized/empty presets.json file is `f.size() < 4` (not `< 3`), to also catch `{ }` (3 bytes) and `{\n}` (3 bytes) which users commonly write as visually-padded empty JSON placeholders in custom filesystems. Both `initPresetsFile()` in wled00/presets.cpp and `appendObjectToFile()` in wled00/file.cpp should use the same `< 4` threshold for consistency.

Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-06T17:32:30.529Z
Learning: Applies to wled00/{html_*,js_*}.h : Never edit or commit auto-generated `wled00/html_*.h` and `wled00/js_*.h` files

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 248
File: usermods/audioreactive/audio_reactive.h:199-200
Timestamp: 2025-06-30T17:53:37.232Z
Learning: In WLED AudioReactive usermod, when using ArduinoFFT (enabled via UM_AUDIOREACTIVE_USE_ARDUINO_FFT), it's acceptable and necessary to redefine the global sqrt macro to sqrtf within the conditional compilation block for performance optimization. This is a specific requirement for ArduinoFFT optimization that can provide 10-50% performance improvement on ESP32.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 270
File: usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h:155-157
Timestamp: 2025-10-22T21:50:25.839Z
Learning: In WLED-MM PR `#270`, only Effect (Mode) IDs are migrated to 16-bit; Palette IDs and counts remain 8-bit. Usermod code should widen mode-related indices/loops to uint16_t while keeping palette-related indices/loops as uint8_t.

Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 308
File: platformio.ini:436-436
Timestamp: 2026-01-05T21:35:57.030Z
Learning: In MoonModules/WLED-MM platformio.ini, [esp32_legacy] section intentionally uses NeoPixelBus 2.7.5 while V4 platform builds use 2.7.9 or later. The older V3 Platform builds don't have the same flickering problems that newer versions address, and NPB 2.7.9 provides no added value over 2.7.5 for these legacy builds.

Learnt from: DedeHai
Repo: MoonModules/WLED-MM PR: 253
File: usermods/audioreactive/audio_source.h:1256-1261
Timestamp: 2025-07-12T04:20:14.546Z
Learning: In WLED AudioReactive usermod, DC offset removal optimization in DMAadcSource::getSamples() should be implemented after PR `#248` merges, which will change the sample buffer from float to int16. This eliminates the need for temporary integer buffers and allows direct integer arithmetic on the native buffer type for better memory efficiency on resource-constrained MCUs like C3 and S2.


// enable additional debug output
//WLEDMM: switch between netdebug and serial
// cannot do this on -S2, due to buggy USBCDC serial driver: canUseSerial
Expand Down
5 changes: 4 additions & 1 deletion wled00/wled00.ino → wled00/wled_main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include <Arduino.h>
/*
* WLED Arduino IDE compatibility file.
* (this is the former wled00.ino)
*
* Where has everything gone?
*
* In April 2020, the project's structure underwent a major change.
* In April 2020, the project's structure underwent a major change.
* We now use the platformIO build system, and building WLED in Arduino IDE is not supported any more.
* Global variables are now found in file "wled.h"
* Global function declarations are found in "fcn_declare.h"
*
Expand Down