Skip to content
Draft
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
114 changes: 109 additions & 5 deletions wled00/alexa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,29 @@
#ifndef WLED_DISABLE_ALEXA
void onAlexaChange(EspalexaDevice* dev);

// index of the first segment device in the Espalexa device list (0 = no segment devices)
static unsigned alexaSegmentDeviceStart = 0;

// map a segment device offset to an actual segment index, skipping inactive segments
// returns -1 if the offset is out of range
static int mapSegDevToSegIndex(unsigned segDevIdx)
{
unsigned activeCount = 0;
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
if (!strip.getSegment(i).isActive()) continue;
if (activeCount == segDevIdx) return i;
activeCount++;
}
return -1;
}

void alexaInit()
{
if (!alexaEnabled || !WLED_CONNECTED) return;

espalexa.removeAllDevices();
alexaSegmentDeviceStart = 0;

// the original configured device for on/off or macros (added first, i.e. index 0)
espalexaDevice = new EspalexaDevice(alexaInvocationName, onAlexaChange, EspalexaDeviceType::extendedcolor);
espalexa.addDevice(espalexaDevice);
Expand All @@ -32,6 +50,21 @@ void alexaInit()
espalexa.addDevice(dev);
}
}

// segment devices are added after the main device and preset devices
// device IDs: 0 = main, 1..N = presets, (N+1).. = segments
if (alexaExposeSegments) {
alexaSegmentDeviceStart = espalexa.getDeviceCount(); // first segment device index
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
String segName(seg.name ? seg.name : "");
if (!segName.length()) segName = String(F("Segment ")) + String(i);
EspalexaDevice* dev = new EspalexaDevice(segName.c_str(), onAlexaChange, EspalexaDeviceType::extendedcolor);
espalexa.addDevice(dev);
}
}

espalexa.begin(&server);
}

Expand All @@ -44,10 +77,80 @@ void handleAlexa()
void onAlexaChange(EspalexaDevice* dev)
{
EspalexaDeviceProperty m = dev->getLastChangedProperty();
unsigned devId = dev->getId();

// determine if this is a segment device
bool isSegmentDevice = alexaExposeSegments && alexaSegmentDeviceStart > 0 && devId >= alexaSegmentDeviceStart;

if (isSegmentDevice)
{
int segIdx = mapSegDevToSegIndex(devId - alexaSegmentDeviceStart);
if (segIdx < 0) return; // segment was deactivated after discovery
Segment& seg = strip.getSegment(segIdx);

if (m == EspalexaDeviceProperty::on)
{
seg.setOption(SEG_OPTION_ON, true);
if (!seg.opacity) seg.setOpacity(255); // ensure segment is visible when turned on
stateUpdated(CALL_MODE_ALEXA);
}
else if (m == EspalexaDeviceProperty::off)
{
seg.setOption(SEG_OPTION_ON, false);
dev->setValue(0);
stateUpdated(CALL_MODE_ALEXA);
}
else if (m == EspalexaDeviceProperty::bri)
{
seg.setOption(SEG_OPTION_ON, true);
seg.setOpacity(dev->getValue());
stateUpdated(CALL_MODE_ALEXA);
}
else // color
{
if (dev->getColorMode() == EspalexaColorMode::ct)
{
byte rgbw[4];
uint16_t ct = dev->getCt();
if (!ct) return;
uint16_t k = 1000000 / ct;
if (seg.isCCT()) {
seg.setCCT(k);
if (seg.hasWhite()) {
rgbw[0] = 0; rgbw[1] = 0; rgbw[2] = 0; rgbw[3] = 255;
} else {
rgbw[0] = 255; rgbw[1] = 255; rgbw[2] = 255; rgbw[3] = 0;
dev->setValue(255);
}
} else if (seg.hasWhite()) {
switch (ct) {
case 199: rgbw[0]=255; rgbw[1]=255; rgbw[2]=255; rgbw[3]=255; break;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@coderabbitai 🤔 why are some values treated differently? optimization for common values?

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.

@softhack007 These specific ct values (199, 234, 284, 350, 383 mireds) are not an optimization for speed — they are empirically tuned RGBW channel mappings for Alexa's fixed color temperature presets. Alexa (emulating a Philips Hue bridge) uses a small, fixed set of named color temperature steps (e.g., "warm white", "soft white", "white", "daylight", "cool white") that always map to exactly these mired values. The generic colorKtoRGB() conversion is used as the fallback for arbitrary temperatures, but for these well-known Alexa presets the RGBW values were hand-tuned to look better on RGBW strips.

The existing main-device handler already has this exact same switch (ct) block with the comment "these values empirically look good on RGBW" — this new segment-device code is simply a copy of that established pattern. Converting mireds to kelvin:

ct (mireds) ≈ Kelvin Alexa preset
199 ~5025 K Cool white / Daylight
234 ~4274 K White
284 ~3521 K Soft white
350 ~2857 K Warm white
383 ~2610 K Incandescent

So in short: empirical calibration for Alexa's known fixed presets, not a performance optimization.


🧠 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.

case 234: rgbw[0]=127; rgbw[1]=127; rgbw[2]=127; rgbw[3]=255; break;
case 284: rgbw[0]= 0; rgbw[1]= 0; rgbw[2]= 0; rgbw[3]=255; break;
case 350: rgbw[0]=130; rgbw[1]= 90; rgbw[2]= 0; rgbw[3]=255; break;
case 383: rgbw[0]=255; rgbw[1]=153; rgbw[2]= 0; rgbw[3]=255; break;
default : colorKtoRGB(k, rgbw);
}
} else {
colorKtoRGB(k, rgbw);
}
seg.setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
} else {
uint32_t color = dev->getRGB();
seg.setColor(0, color);
}
stateUpdated(CALL_MODE_ALEXA);
}
return;
}

// boundary for preset device range (excludes segment devices)
unsigned presetEnd = alexaSegmentDeviceStart ? alexaSegmentDeviceStart : espalexa.getDeviceCount();

// original behavior: main device and preset devices
if (m == EspalexaDeviceProperty::on)
{
if (dev->getId() == 0) // Device 0 is for on/off or macros
if (devId == 0) // Device 0 is for on/off or macros
{
if (!macroAlexaOn)
{
Expand All @@ -64,13 +167,13 @@ void onAlexaChange(EspalexaDevice* dev)
} else // switch-on behavior for preset devices
{
// turn off other preset devices
for (unsigned i = 1; i < espalexa.getDeviceCount(); i++)
for (unsigned i = 1; i < presetEnd; i++)
{
if (i == dev->getId()) continue;
if (i == devId) continue;
espalexa.getDevice(i)->setValue(0); // turn off other presets
}

applyPreset(dev->getId(), CALL_MODE_ALEXA); // in alexaInit() preset 1 device was added second (index 1), preset 2 third (index 2) etc.
applyPreset(devId, CALL_MODE_ALEXA); // in alexaInit() preset 1 device was added second (index 1), preset 2 third (index 2) etc.
}
} else if (m == EspalexaDeviceProperty::off)
{
Expand All @@ -87,7 +190,8 @@ void onAlexaChange(EspalexaDevice* dev)
applyPreset(macroAlexaOff, CALL_MODE_ALEXA);
// below for loop stops Alexa from complaining if macroAlexaOff does not actually turn off
}
for (unsigned i = 0; i < espalexa.getDeviceCount(); i++)
// set main and preset devices to off, but not segment devices
for (unsigned i = 0; i < presetEnd; i++)
{
espalexa.getDevice(i)->setValue(0);
}
Expand Down
2 changes: 2 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(macroAlexaOn, interfaces["va"]["macros"][0]);
CJSON(macroAlexaOff, interfaces["va"]["macros"][1]);
CJSON(alexaNumPresets, interfaces["va"]["p"]);
CJSON(alexaExposeSegments, interfaces["va"]["seg"]);
#endif

#ifndef WLED_DISABLE_MQTT
Expand Down Expand Up @@ -1145,6 +1146,7 @@ void serializeConfig(JsonObject root) {
if_va_macros.add(macroAlexaOff);

if_va["p"] = alexaNumPresets;
if_va["seg"] = alexaExposeSegments;
#endif

#ifndef WLED_DISABLE_MQTT
Expand Down
3 changes: 2 additions & 1 deletion wled00/data/settings_sync.htm
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ <h3>Alexa Voice Assistant</h3>
<div id="Alexa">
Emulate Alexa device: <input type="checkbox" name="AL"><br>
Alexa invocation name: <input type="text" name="AI" maxlength="32"><br>
Also emulate devices to call the first <input name="AP" type="number" class="s" min="0" max="9"> presets<br><br>
Also emulate devices to call the first <input name="AP" type="number" class="s" min="0" max="9"> presets<br>
Also emulate a device for each active segment: <input type="checkbox" name="AS"><br><br>
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33);
t = request->arg(F("AP")).toInt();
if (t >= 0 && t <= 9) alexaNumPresets = t;
alexaExposeSegments = request->hasArg(F("AS"));
#endif

#ifndef WLED_DISABLE_MQTT
Expand Down
3 changes: 2 additions & 1 deletion wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
#ifndef WLED_DISABLE_ALEXA
#define ESPALEXA_ASYNC
#define ESPALEXA_NO_SUBPAGE
#define ESPALEXA_MAXDEVICES 10
#define ESPALEXA_MAXDEVICES 20 // 1 main + up to 9 presets + up to 10 segment devices
// #define ESPALEXA_DEBUG
#include "src/dependencies/espalexa/Espalexa.h"
#include "src/dependencies/espalexa/EspalexaDevice.h"
Expand Down Expand Up @@ -446,6 +446,7 @@ WLED_GLOBAL bool irApplyToAllSelected _INIT(true); //apply IR or ESP-NOW to all
WLED_GLOBAL bool alexaEnabled _INIT(false); // enable device discovery by Amazon Echo
WLED_GLOBAL char alexaInvocationName[33] _INIT("Light"); // speech control name of device. Choose something voice-to-text can understand
WLED_GLOBAL byte alexaNumPresets _INIT(0); // number of presets to expose to Alexa, starting from preset 1, up to 9
WLED_GLOBAL bool alexaExposeSegments _INIT(false); // expose each segment as a separate Alexa device
#endif

WLED_GLOBAL uint16_t realtimeTimeoutMs _INIT(2500); // ms timeout of realtime mode before returning to normal mode
Expand Down
1 change: 1 addition & 0 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("AI"),alexaInvocationName);
printSetFormCheckbox(settingsScript,PSTR("SA"),notifyAlexa);
printSetFormValue(settingsScript,PSTR("AP"),alexaNumPresets);
printSetFormCheckbox(settingsScript,PSTR("AS"),alexaExposeSegments);
#else
settingsScript.print(F("toggle('Alexa');")); // hide Alexa settings
#endif
Expand Down
Loading