New UserMod: Voice Control via DF2301Q #329
Conversation
WalkthroughAdds a DF2301Q I2C driver header and a DF2301QUsermod that detects and polls a DF2301Q voice module, maps CMDIDs to WLED actions, persists configuration and UI entries, and wires the usermod into the build (usermod ID, conditional registration, and platformio build flag). ChangesVoice Control
Sequence Diagram(s)sequenceDiagram
participant Sys as "System"
participant UM as "DF2301QUsermod"
participant Driver as "DF2301Q Driver"
participant I2C as "Wire (I2C Bus)"
participant Mod as "DF2301Q Module"
participant WLED as "WLED Core"
Sys->>UM: setup()
UM->>Driver: new DF2301Q(addr)
UM->>Driver: detect()
Driver->>I2C: beginTransmission / ping @ addr
I2C->>Mod: probe/ping
Mod-->>I2C: response
I2C-->>Driver: ack/result
Driver-->>UM: detection result
loop every pollInterval
UM->>Driver: poll()
Driver->>I2C: readReg(CMDID)
I2C->>Mod: read CMDID
Mod-->>I2C: cmdID
I2C-->>Driver: cmdID
Driver-->>UM: new cmdID (if changed)
UM->>UM: handleVoiceCommand(cmdID)
UM->>WLED: apply action (power/brightness/preset/effect/color)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Infer (1.2.0)wled00/usermods_list.cppIn file included from wled00/usermods_list.cpp:1: ... [truncated 1094 characters] ... ng/install/lib/clang/18/include" 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@usermods/usermod_v2_voice_control/DF2301Q.hpp`:
- Around line 19-85: The task is being pinned to core 1 unconditionally in
DF2301Q::startTask via xTaskCreatePinnedToCore which fails on single‑core ESP32
variants; update the core selection so single‑core builds use core 0 by using
conditional compilation (check CONFIG_FREERTOS_UNICORE) and set
DF2301Q_TASK_CORE to 0 for unicore and 1 otherwise, or compute the core argument
inline in startTask before calling xTaskCreatePinnedToCore; modify the symbol
DF2301Q_TASK_CORE (or the call site in startTask that passes DF2301Q_TASK_CORE)
accordingly so the task is pinned to PRO_CPU (core 0) on single‑core targets and
to core 1 on multicore targets.
In `@usermods/usermod_v2_voice_control/usermod_v2_voice_control.h`:
- Around line 403-410: The local variable that stores the current effect/mode
(currently declared as uint8_t in the block handling cmdPrevEffect and used with
strip.getMainSegment().mode and strip.setMode) must be widened to uint16_t to
avoid truncation for mode IDs >255; update the declaration of mode in this
handler (and any other mode-index locals in this usermod) from uint8_t to
uint16_t, keep palette-related types as uint8_t, and ensure the wrap logic uses
strip.getModeCount() unchanged while still calling
strip.setMode(strip.getMainSegmentId(), ...), and leave
stateUpdated(CALL_MODE_BUTTON) and cmdID/cmdPrevEffect checks as-is.
- Around line 394-400: The branch handling cmdPrevPreset can underflow when
currentPreset is 0; update the conditional in the cmdPrevPreset block (where
cmdPrevPreset, cmdNextPreset, cmdID and applyPreset are used) to prevent calling
applyPreset(currentPreset - 1, CALL_MODE_BUTTON) with currentPreset == 0 —
either skip the action when currentPreset is 0 or compute a safe previous index
(e.g., wrap to last preset by using (currentPreset == 0 ? maxPresetIndex :
currentPreset - 1)) before calling applyPreset and keep the USER_PRINTLN message
unchanged.
- Around line 255-307: The volume range is inconsistent with DF2301Q (should be
1–7): update the dropdown generation in the UI (the SET_F("for(var
i=0;i<=20;i++)addOption(dd,i,i);") call created by addDropdown for 'Volume') to
only emit options 1 through 7, change the default moduleVolume value to a valid
in-range default (replace the current 10 with a value between 1 and 7), and
tighten the bounds in readFromConfig by changing the constrain(moduleVolume, 0,
20) call to constrain(moduleVolume, 1, 7) so moduleVolume is always clamped to
the hardware-supported range. Ensure all references to moduleVolume (including
addToConfig/readFromConfig) rely on that corrected range.
In `@wled00/usermods_list.cpp`:
- Around line 209-212: Remove the redundant self-define inside the conditional
block: in the section guarded by `#ifdef` USERMOD_VOICE_CONTROL, delete the line
that redefines USERMOD_VOICE_CONTROL and leave only the `#include` of
"../usermods/usermod_v2_voice_control/usermod_v2_voice_control.h" (keep the
surrounding `#ifdef/`#endif intact) so that external -DUSERMOD_VOICE_CONTROL=1
values are not clobbered and `#if` USERMOD_VOICE_CONTROL checks continue to work.
🧹 Nitpick comments (1)
usermods/usermod_v2_voice_control/DF2301Q.hpp (1)
151-192: Protect Wire I2C access from concurrent background task execution.The
readReg()andwriteReg()methods at lines 173–191 are called from the background FreeRTOS task (taskFunction), but multiple other usermods also access Wire from the main loop without mutual exclusion. Wrap these methods withesp32SemTake()andesp32SemGive()(or create a dedicated I2C semaphore) to prevent concurrent transactions that corrupt the bus.
…ED by default, but this will do it as usermods are callled one by one.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@usermods/usermod_v2_voice_control/usermod_v2_voice_control.h`:
- Around line 96-116: When DF2301Q detection fails in setup(), do not delete the
allocated voiceModule or set it to nullptr; remove the delete voiceModule and
voiceModule = nullptr statements in the else branch so the DF2301Q instance
remains allocated (voiceModule, DF2301Q::detect()) and the existing recovery
logic in loop() can retry initialization; optionally keep the USER_PRINTLN log
but leave the object in place to allow hot‑plug reconnection.
… removed from the else branch. Now when initial detection fails in setup(), the DF2301Q instance remains allocated, allowing the recovery logic in loop() to retry detection every 5 seconds for hot-plug reconnection.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
usermods/usermod_v2_voice_control/usermod_v2_voice_control.h (1)
373-376:⚠️ Potential issue | 🟡 MinorKeep previous-preset navigation 1-based.
currentPreset == 1still reachesapplyPreset(0, ...)here. If preset0is not a real target in this mapping, the first preset should no-op or wrap instead of stepping to zero.Proposed fix
} else if (cmdPrevPreset > 0 && cmdID == cmdPrevPreset) { - if (currentPreset > 0) { + if (currentPreset > 1) { applyPreset(currentPreset - 1, CALL_MODE_BUTTON); } USER_PRINTLN(F("DF2301Q: Previous Preset"));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@usermods/usermod_v2_voice_control/usermod_v2_voice_control.h` around lines 373 - 376, The previous-preset branch allows currentPreset==1 to call applyPreset(0,...), which moves to an invalid 0 index; update the guard in the block that checks cmdPrevPreset/cmdID so it only calls applyPreset(currentPreset - 1, CALL_MODE_BUTTON) when currentPreset > 1 (or implement explicit wrap behavior if wrapping is desired); locate the variables cmdPrevPreset, cmdID, currentPreset and the call applyPreset(..., CALL_MODE_BUTTON) and change the condition from currentPreset > 0 to currentPreset > 1 (or add wrap logic) so preset navigation remains 1-based.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@usermods/usermod_v2_voice_control/usermod_v2_voice_control.h`:
- Around line 297-300: The loaded pollInterval must be clamped away from zero to
avoid a tight I2C polling loop; after reading pollInterval (the assignment
pollInterval = top[FPSTR(_pollInterval)] | pollInterval;) enforce a minimum
(e.g., pollInterval = max(1, pollInterval); or pollInterval =
constrain(pollInterval, 1, <upper_bound>);) so saved 0 values become a small
positive interval; update the same config-loading block that sets moduleVolume
and wakeTime to apply this clamp to pollInterval.
- Around line 129-137: The recovery path in loop() uses voiceModule->detect(3,
100) which performs multiple blocking probes and can sleep ~200ms on each failed
attempt causing hitches; change it to perform a single non-blocking probe (e.g.,
call detect with a single attempt or otherwise invoke a one-shot check) so the
lastRecoveryAttempt rate-limit remains but each retry only does one quick probe;
keep the existing lastRecoveryAttempt logic and, on a successful single probe,
call voiceModule->setVolume(moduleVolume) and voiceModule->setWakeTime(wakeTime)
as before.
- Around line 353-356: The code handling the power-off command saves briLast
even when the strip is already off (bri == 0), causing a subsequent power-on to
restore 0; change the logic in the cmdPowerOff branch (where cmdPowerOff, cmdID,
briLast, bri and stateUpdated(CALL_MODE_BUTTON) are used) so briLast is only
assigned when bri > 0 (i.e., snapshot previous brightness only if non-zero)
before setting bri = 0 and calling stateUpdated(CALL_MODE_BUTTON).
---
Duplicate comments:
In `@usermods/usermod_v2_voice_control/usermod_v2_voice_control.h`:
- Around line 373-376: The previous-preset branch allows currentPreset==1 to
call applyPreset(0,...), which moves to an invalid 0 index; update the guard in
the block that checks cmdPrevPreset/cmdID so it only calls
applyPreset(currentPreset - 1, CALL_MODE_BUTTON) when currentPreset > 1 (or
implement explicit wrap behavior if wrapping is desired); locate the variables
cmdPrevPreset, cmdID, currentPreset and the call applyPreset(...,
CALL_MODE_BUTTON) and change the condition from currentPreset > 0 to
currentPreset > 1 (or add wrap logic) so preset navigation remains 1-based.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 53092d7b-4b99-480e-9dd8-88b48409b2c2
📒 Files selected for processing (5)
platformio.iniusermods/usermod_v2_voice_control/DF2301Q.hppusermods/usermod_v2_voice_control/usermod_v2_voice_control.hwled00/const.hwled00/usermods_list.cpp
| static unsigned long lastRecoveryAttempt = 0; | ||
| if (millis() - lastRecoveryAttempt > 5000) { | ||
| lastRecoveryAttempt = millis(); | ||
| USER_PRINTLN(F("DF2301Q: Attempting to reconnect...")); | ||
| if (voiceModule->detect(3, 100)) { | ||
| USER_PRINTLN(F("DF2301Q: Module reconnected!")); | ||
| voiceModule->setVolume(moduleVolume); | ||
| voiceModule->setWakeTime(wakeTime); | ||
| } |
There was a problem hiding this comment.
Use a single probe in the recovery loop.
Lines 133-137 call detect(3, 100), which can sleep for roughly 200 ms on every failed reconnect attempt. Because this runs from loop(), a missing module will periodically hitch animations and network work. Keep the retries in setup(), but make the recovery path a single probe.
Proposed fix
- if (voiceModule->detect(3, 100)) {
+ if (voiceModule->detect(1, 0)) {
USER_PRINTLN(F("DF2301Q: Module reconnected!"));
voiceModule->setVolume(moduleVolume);
voiceModule->setWakeTime(wakeTime);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@usermods/usermod_v2_voice_control/usermod_v2_voice_control.h` around lines
129 - 137, The recovery path in loop() uses voiceModule->detect(3, 100) which
performs multiple blocking probes and can sleep ~200ms on each failed attempt
causing hitches; change it to perform a single non-blocking probe (e.g., call
detect with a single attempt or otherwise invoke a one-shot check) so the
lastRecoveryAttempt rate-limit remains but each retry only does one quick probe;
keep the existing lastRecoveryAttempt logic and, on a successful single probe,
call voiceModule->setVolume(moduleVolume) and voiceModule->setWakeTime(wakeTime)
as before.
There was a problem hiding this comment.
oops - no delay() here, please. Or make sure the recovery detect(3, 100) will only run a few times, then disable the usermod.
There was a problem hiding this comment.
@softhack007 — agreed on both counts. Updated proposal below: use detect(1, 0) (single probe, zero delay) so each recovery attempt is a fast single I2C transaction, and add a give-up counter so the usermod disables itself after a configurable number of consecutive recovery failures instead of retrying forever:
void loop() {
if (!enabled || !voiceModule) return;
if (!voiceModule->isDetected()) {
static unsigned long lastRecoveryAttempt = 0;
+ static uint8_t recoveryAttempts = 0;
+ static const uint8_t MAX_RECOVERY_ATTEMPTS = 5;
+
+ if (recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {
+ USER_PRINTLN(F("DF2301Q: Module not found after max retries - disabling usermod"));
+ enabled = false;
+ return;
+ }
+
if (millis() - lastRecoveryAttempt > 5000) {
lastRecoveryAttempt = millis();
USER_PRINTLN(F("DF2301Q: Attempting to reconnect..."));
- if (voiceModule->detect(3, 100)) {
+ if (voiceModule->detect(1, 0)) { // single non-blocking probe
USER_PRINTLN(F("DF2301Q: Module reconnected!"));
voiceModule->setVolume(moduleVolume);
voiceModule->setWakeTime(wakeTime);
+ recoveryAttempts = 0; // reset on success
+ } else {
+ recoveryAttempts++;
}
}
return;
}This way:
- Each recovery probe is a single, no-delay I2C read — no blocking in
loop(). - After 5 failed recovery attempts (≈ 25 s),
enabledis set tofalseandloop()becomes a no-op, matching the "disable the usermod" expectation. - A successful reconnect resets the counter so temporary disconnects don't permanently disable the module.
🧠 Learnings used
Learnt from: troyhacks
Repo: MoonModules/WLED-MM PR: 329
File: usermods/usermod_v2_voice_control/usermod_v2_voice_control.h:0-0
Timestamp: 2026-01-29T16:25:11.957Z
Learning: For the DF2301Q voice recognition module usermod in WLED-MM: The volume range should be 0-20 (not limited to the officially documented 1-7). Testing confirms the hardware supports values up to 20, and the extended range is needed because the onboard speaker is very quiet at volume 7, and users may connect external speakers that require higher volume levels.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Add usermod IDs to `wled00/const.h` when creating new usermods
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-17T19:40:08.064Z
Learning: In MoonModules/WLED-MM, defining `static const char[] PROGMEM` members for usermod config-key strings in the header file (rather than a companion `.cpp`) is the intentional and repo-normal pattern. Each usermod header is included exactly once via `#ifdef USERMOD_<NAME>` in `usermods_list.cpp`, so there is no ODR risk. Do NOT suggest marking these `inline` (C++17), as that risks duplicating the strings and defeats the flash-saving purpose of `PROGMEM`, especially on ESP8266. Do not flag this pattern as an issue in code reviews.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `delay(1)` in custom FreeRTOS tasks in C++, NOT `yield()` — feeds IDLE watchdog
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Wrap platform-conditional C++ includes in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Replace `inoise8` / `inoise16` with `perlin8` / `perlin16` in C++
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.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `WLED_DISABLE_*` and `WLED_ENABLE_*` feature flags for preprocessor-based feature toggling in C++
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Mark AI-generated C++ code blocks with `// AI: below section was generated by an AI` / `// AI: end` comments
| moduleVolume = top[FPSTR(_moduleVolume)] | moduleVolume; | ||
| moduleVolume = constrain(moduleVolume, 0, 20); // 0-20, higher values may distort | ||
| wakeTime = top[FPSTR(_wakeTime)] | wakeTime; | ||
| pollInterval = top[FPSTR(_pollInterval)] | pollInterval; |
There was a problem hiding this comment.
Clamp pollInterval away from zero.
A saved value of 0 makes the check at Lines 144-149 true on every pass through loop(), turning this into a tight I2C polling loop. Enforce a small positive minimum when loading config.
Proposed fix
moduleVolume = top[FPSTR(_moduleVolume)] | moduleVolume;
moduleVolume = constrain(moduleVolume, 0, 20); // 0-20, higher values may distort
wakeTime = top[FPSTR(_wakeTime)] | wakeTime;
pollInterval = top[FPSTR(_pollInterval)] | pollInterval;
+ if (pollInterval < 10) pollInterval = 10;
cmdPowerOn = top[FPSTR(_cmdPowerOn)] | cmdPowerOn;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@usermods/usermod_v2_voice_control/usermod_v2_voice_control.h` around lines
297 - 300, The loaded pollInterval must be clamped away from zero to avoid a
tight I2C polling loop; after reading pollInterval (the assignment pollInterval
= top[FPSTR(_pollInterval)] | pollInterval;) enforce a minimum (e.g.,
pollInterval = max(1, pollInterval); or pollInterval = constrain(pollInterval,
1, <upper_bound>);) so saved 0 values become a small positive interval; update
the same config-loading block that sets moduleVolume and wakeTime to apply this
clamp to pollInterval.
There was a problem hiding this comment.
fix: add one line pollInterval = max(pollInterval, 1); (replace "1" with whatever the minimal interval really is).
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "DF2301Q_POLL_INTERVAL_MS" --type cpp
rg -n "DF2301Q_POLL_INTERVAL_MS"Length of output: 529
@softhack007 — DF2301Q_POLL_INTERVAL_MS is defined as 100 ms, so a sensible floor is somewhere between 50–100 ms. Polling faster than ~50 ms gives no benefit for a voice recognition module and just wastes I2C bandwidth. I'd suggest using 50 ms:
pollInterval = top[FPSTR(_pollInterval)] | pollInterval;
pollInterval = max(pollInterval, (uint32_t)50); // prevent tight I2C polling loopUsing DF2301Q_POLL_INTERVAL_MS (100 ms) as the minimum is also defensible if you don't want users going below the driver default.
🧠 Learnings used
Learnt from: troyhacks
Repo: MoonModules/WLED-MM PR: 329
File: usermods/usermod_v2_voice_control/usermod_v2_voice_control.h:0-0
Timestamp: 2026-01-29T16:25:11.957Z
Learning: For the DF2301Q voice recognition module usermod in WLED-MM: The volume range should be 0-20 (not limited to the officially documented 1-7). Testing confirms the hardware supports values up to 20, and the extended range is needed because the onboard speaker is very quiet at volume 7, and users may connect external speakers that require higher volume levels.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Add usermod IDs to `wled00/const.h` when creating new usermods
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 357
File: .github/esp-idf.instructions.md:504-504
Timestamp: 2026-04-06T17:17:46.406Z
Learning: In MoonModules/WLED-MM audioreactive (audio_source.h), the PDM S3 low-amplitude symptom is caused by configuring `bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT` in PDM mode (IDF internally treats PDM data as always 16-bit). However, switching to `I2S_BITS_PER_SAMPLE_16BIT` or casting sample buffers to `int16_t` is NOT a ready solution: 16BIT mode does not work at all when building audioreactive with ESP-IDF v4.4.8. Deeper investigation is needed (tracked in issue `#333`) before recommending any fix. Do not suggest `BITS_PER_SAMPLE_16BIT` or `int16_t` casting as a PDM S3 fix.
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-17T19:40:08.064Z
Learning: In MoonModules/WLED-MM, defining `static const char[] PROGMEM` members for usermod config-key strings in the header file (rather than a companion `.cpp`) is the intentional and repo-normal pattern. Each usermod header is included exactly once via `#ifdef USERMOD_<NAME>` in `usermods_list.cpp`, so there is no ODR risk. Do NOT suggest marking these `inline` (C++17), as that risks duplicating the strings and defeats the flash-saving purpose of `PROGMEM`, especially on ESP8266. Do not flag this pattern as an issue in code reviews.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Wrap platform-conditional C++ includes in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Replace `inoise8` / `inoise16` with `perlin8` / `perlin16` in C++
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.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `WLED_DISABLE_*` and `WLED_ENABLE_*` feature flags for preprocessor-based feature toggling in C++
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Mark AI-generated C++ code blocks with `// AI: below section was generated by an AI` / `// AI: end` comments
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a880f64c-213a-4db0-8698-6a7a114a5964
📒 Files selected for processing (5)
platformio.iniusermods/usermod_v2_voice_control/DF2301Q.hppusermods/usermod_v2_voice_control/usermod_v2_voice_control.hwled00/const.hwled00/usermods_list.cpp
| } | ||
|
|
||
| void appendConfigData() { | ||
| oappend(SET_F("addHB('DF2301Q');")); |
There was a problem hiding this comment.
@coderabbitai please estimate the "oappend" buffer usage of this function.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Get the full appendConfigData() function from the usermod header
sed -n '191,265p' usermods/usermod_v2_voice_control/usermod_v2_voice_control.hLength of output: 3873
🏁 Script executed:
#!/bin/bash
python3 << 'EOF'
strings = [
# 1
"addHB('DF2301Q');",
# 2 - Big dfo array + dfD function (adjacent string literals concatenated)
"var dfo=["
"[0,'Disabled'],"
"[1,'Hello Robot (Wake)'],"
"[2,'Custom Wake Word'],"
"[5,'Custom Cmd 1'],[6,'Custom Cmd 2'],[7,'Custom Cmd 3'],[8,'Custom Cmd 4'],"
"[9,'Custom Cmd 5'],[10,'Custom Cmd 6'],[11,'Custom Cmd 7'],[12,'Custom Cmd 8'],"
"[13,'Custom Cmd 9'],[14,'Custom Cmd 10'],[15,'Custom Cmd 11'],[16,'Custom Cmd 12'],"
"[17,'Custom Cmd 13'],[18,'Custom Cmd 14'],[19,'Custom Cmd 15'],[20,'Custom Cmd 16'],[21,'Custom Cmd 17'],"
"[52,'Display number zero'],[53,'Display number one'],[54,'Display number two'],"
"[55,'Display number three'],[56,'Display number four'],[57,'Display number five'],"
"[58,'Display number six'],[59,'Display number seven'],[60,'Display number eight'],[61,'Display number nine'],"
"[82,'Reset'],"
"[92,'Play music'],[93,'Stop playing'],[94,'The last track'],[95,'The next track'],"
"[103,'Turn on the light'],"
"[104,'Turn off the light'],"
"[105,'Brighten the light'],"
"[106,'Dim the light'],"
"[107,'Adjust brightness to maximum'],"
"[108,'Adjust brightness to minimum'],"
"[109,'Increase color temperature'],"
"[110,'Decrease color temperature'],"
"[113,'Daylight mode'],"
"[114,'Moonlight mode'],"
"[115,'Color mode'],"
"[116,'Set to Red'],"
"[117,'Set to Orange'],"
"[118,'Set to Yellow'],"
"[119,'Set to Green'],"
"[120,'Set to Cyan'],"
"[121,'Set to Blue'],"
"[122,'Set to Purple'],"
"[123,'Set to White']"
"];"
"function dfD(f){"
"var dd=addDropdown('DF2301Q',f);"
"for(var i=0;i<dfo.length;i++)addOption(dd,dfo[i][1],dfo[i][0]);"
"}",
# 3-10: eight dfD calls
"dfD('cmd_Power_On');",
"dfD('cmd_Power_Off');",
"dfD('cmd_Brightness_Up');",
"dfD('cmd_Brightness_Down');",
"dfD('cmd_Next_Preset');",
"dfD('cmd_Previous_Preset');",
"dfD('cmd_Next_Effect');",
"dfD('cmd_Previous_Effect');",
# Volume dropdown
"dd=addDropdown('DF2301Q','Volume');",
"for(var i=0;i<=20;i++)addOption(dd,i,i);",
# addInfo calls
"addInfo('DF2301Q:Poll_Interval',1,'ms between voice checks');",
"addInfo('DF2301Q:Wake_Time',1,'seconds module listens after wake word');",
"addInfo('DF2301Q:Startup_Sound',1,'phrase spoken when WLED starts');",
"addInfo('DF2301Q:Numbered_Presets',1,'\"Display number [1-9]\" \u2192 Preset 1-9, \"zero\" \u2192 Preset 10');",
"addInfo('DF2301Q:Color_Commands',1,'\"Set to Red/Orange/Yellow/Green/Cyan/Blue/Purple/White\"');",
# Last big addInfo with HTML
"addInfo('DF2301Q:Voice_Feedback',1,"
"'<br><br><b>Usage:</b> Say \"Hello Robot\" (or your custom wake-phrase) to wake, then give commands.<br />"
"Module listens for Wake Time seconds.<br />Each command resets timer so you can say multiple commands.');",
]
total = 0
for i, s in enumerate(strings):
length = len(s)
total += length
print(f" [{i+1:2d}] {length:5d} bytes | {s[:60]}{'...' if len(s)>60 else ''}")
print(f"\nTotal: {total} bytes")
# WLED typical buffer sizes
print(f"\nCommon WLED oappend buffer sizes:")
print(f" SETTINGS_STACK_BUF_SIZE = 2048 bytes (typical)")
print(f" Used: {total} / 2048 = {total/2048*100:.1f}%")
print(f" Used: {total} / 4096 = {total/4096*100:.1f}% (if 4096)")
EOFLength of output: 4937
🏁 Script executed:
#!/bin/bash
# Find the oappend buffer size constant
rg -n "SETTINGS_STACK_BUF_SIZE|oappend|knownDevices|CFG_BUFFER" wled00/ --type=cpp -m 5 -g '*.h' | grep -i "buf\|size\|2048\|4096\|oappend" | head -20
# Also check the wled.h or settings.h for the buffer
rg -n "char.*buf\|STACK_BUF\|oappend" wled00/set.cpp | head -20Length of output: 1065
@softhack007 Here's the breakdown:
appendConfigData() — oappend buffer usage estimate
| Call | Bytes | Content |
|---|---|---|
addHB(...) |
17 | heartbeat |
var dfo=[...];function dfD(f){...} |
1,370 | entire options array + helper function |
8× dfD('cmd_...') |
193 | one per command dropdown |
| volume dropdown + loop | 75 | addDropdown + addOption loop |
5× addInfo(...) |
391 | hint strings |
addInfo('Voice_Feedback',...) with HTML |
244 | usage instructions block |
| Total | ~2,290 | (≈2,294 counting the → U+2192 as 3 UTF-8 bytes) |
Against actual buffer sizes (from wled00/const.h):
`#define` SETTINGS_STACK_BUF_SIZE 2048 // default / ESP8266
`#define` SETTINGS_STACK_BUF_SIZE 3834 // WLEDMM ESP32 base
`#define` SETTINGS_STACK_BUF_SIZE 4000 // WLEDMM ESP32 extended| Buffer | Size | This usermod alone | Headroom |
|---|---|---|---|
| ESP8266 default | 2,048 B | 111.8% — OVERFLOW | −242 B |
| ESP32 WLEDMM base | 3,834 B | 59.8% | +1,544 B |
| ESP32 WLEDMM extended | 4,000 B | 57.4% | +1,710 B |
Key observations
-
ESP8266 overflows. If someone ever enables this on an ESP8266, the settings page will be silently truncated. Since the DF2301Q is an I2C device and the usermod uses
pinManager.joinWire(), it may be ESP32-only in practice — but it's worth a#ifndef ESP8266guard or a note in the README. -
The buffer is shared across all
appendConfigData()calls from every active usermod plus core WLED. With AudioReactive also enabled (which has a large config UI), the 3,834 B budget could get tight. -
The biggest single call is 1,370 bytes — the
var dfo=[...];function dfD(f){...}block. The shared-array/function design (dfD) is already a good optimisation (avoids repeating all 54 options inline for each of the 8 dropdowns). If trimming is needed, the most impactful change would be shortening the label strings indfo(e.g.,'Hello Robot (Wake)'→'Hello Robot', etc.). -
The
→arrow characters (×2) in theNumbered_Presetshint are UTF-8 3-byte sequences — minor, but confirms the true byte total is slightly above what a char-count would suggest.
🧠 Learnings used
Learnt from: troyhacks
Repo: MoonModules/WLED-MM PR: 329
File: usermods/usermod_v2_voice_control/usermod_v2_voice_control.h:0-0
Timestamp: 2026-01-29T16:25:11.957Z
Learning: For the DF2301Q voice recognition module usermod in WLED-MM: The volume range should be 0-20 (not limited to the officially documented 1-7). Testing confirms the hardware supports values up to 20, and the extended range is needed because the onboard speaker is very quiet at volume 7, and users may connect external speakers that require higher volume levels.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Add usermod IDs to `wled00/const.h` when creating new usermods
Learnt from: softhack007
Repo: MoonModules/WLED-MM PR: 0
File: :0-0
Timestamp: 2026-04-17T19:40:08.064Z
Learning: In MoonModules/WLED-MM, defining `static const char[] PROGMEM` members for usermod config-key strings in the header file (rather than a companion `.cpp`) is the intentional and repo-normal pattern. Each usermod header is included exactly once via `#ifdef USERMOD_<NAME>` in `usermods_list.cpp`, so there is no ODR risk. Do NOT suggest marking these `inline` (C++17), as that risks duplicating the strings and defeats the flash-saving purpose of `PROGMEM`, especially on ESP8266. Do not flag this pattern as an issue in code reviews.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Wrap platform-conditional C++ includes in `#ifdef ARDUINO_ARCH_ESP32` / `#ifdef ESP8266`
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.cpp usermods/**/*.cpp : Include `wled.h` as the primary project header in C++ files
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.
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `WLED_DISABLE_*` and `WLED_ENABLE_*` feature flags for preprocessor-based feature toggling in C++
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Replace `inoise8` / `inoise16` with `perlin8` / `perlin16` in C++
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Mark AI-generated C++ code blocks with `// AI: below section was generated by an AI` / `// AI: end` comments
Learnt from: CR
Repo: MoonModules/WLED-MM PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-14T23:40:02.031Z
Learning: Applies to wled00/**/*.{cpp,h,hpp,c} usermods/**/*.{cpp,h,hpp,c} : Use `DEBUG_PRINTF()` / `DEBUG_PRINTLN()` for debug output in C++ (compiled out unless `-D WLED_DEBUG`)
There was a problem hiding this comment.
8266 overflow is acceptable in this case (8266 platform is deprecated).
There was a problem hiding this comment.
addHB(...)17 heartbeat
nitpick: "HB" in addHB(...) stands for "Help Button" not for "heartbeat" 😉
The DF2301Q is a very inexpensive offline voice control board that you can also train for a custom wake-phrase as well as 17 other custom commands - and it understands over 120 generic commands without training.
It takes almost zero resources as everything is done on the board.
Product link: https://www.dfrobot.com/product-2665.html
List of the pre-trained phrases
To keep the data small, I only included a subset of the words it can recognize that are related to lighting or similar things.
Summary by CodeRabbit
New Features
Chores