From 84e1ebf1550bbfbaecca0e0de5ec854e8f19288c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:58:29 +0000 Subject: [PATCH 1/3] Initial plan From ed23216b773844f139cc9b9638d19e7e1c28f6ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 07:07:06 +0000 Subject: [PATCH 2/3] Add Alexa per-segment device emulation Extend the Alexa/Hue emulation to optionally expose each active segment as a separate Alexa device. When enabled via the new "Also emulate a device for each active segment" checkbox in Sync settings, users can independently control brightness, color, and on/off state per segment through Alexa voice commands. Device index layout: 0=main, 1..N=presets, (N+1)..=segments. Backwards compatible: disabled by default, existing preset behavior is unchanged. ESPALEXA_MAXDEVICES raised from 10 to 20 to accommodate segment devices alongside presets. Agent-Logs-Url: https://github.com/wled/WLED/sessions/2bb549aa-be26-40ef-870e-20a7379d69e0 Co-authored-by: netmindz <442066+netmindz@users.noreply.github.com> --- wled00/alexa.cpp | 106 ++++++++++++++++++++++++++++++++-- wled00/cfg.cpp | 2 + wled00/data/settings_sync.htm | 3 +- wled00/set.cpp | 1 + wled00/wled.h | 3 +- wled00/xml.cpp | 1 + 6 files changed, 109 insertions(+), 7 deletions(-) diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index 81b9ec3469..06d6fd7be1 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -12,11 +12,16 @@ #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; + 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); @@ -32,6 +37,20 @@ 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 ? String(seg.name) : (String(F("Segment ")) + String(i)); + EspalexaDevice* dev = new EspalexaDevice(segName.c_str(), onAlexaChange, EspalexaDeviceType::extendedcolor); + espalexa.addDevice(dev); + } + } + espalexa.begin(&server); } @@ -44,10 +63,84 @@ 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) + { + // map device index to segment index (skipping inactive segments, same order as in alexaInit) + unsigned segDevIdx = devId - alexaSegmentDeviceStart; + unsigned segIdx = 0; + unsigned activeCount = 0; + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { + if (!strip.getSegment(i).isActive()) continue; + if (activeCount == segDevIdx) { segIdx = i; break; } + activeCount++; + } + Segment& seg = strip.getSegment(segIdx); + + if (m == EspalexaDeviceProperty::on) + { + seg.setOption(SEG_OPTION_ON, true); + seg.setOpacity(seg.opacity ? seg.opacity : 255); + 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; + 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; + } + // 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) { @@ -64,13 +157,14 @@ void onAlexaChange(EspalexaDevice* dev) } else // switch-on behavior for preset devices { // turn off other preset devices - for (unsigned i = 1; i < espalexa.getDeviceCount(); i++) + unsigned presetEnd = alexaSegmentDeviceStart ? alexaSegmentDeviceStart : espalexa.getDeviceCount(); + 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) { @@ -87,7 +181,9 @@ 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 + unsigned presetEnd = alexaSegmentDeviceStart ? alexaSegmentDeviceStart : espalexa.getDeviceCount(); + for (unsigned i = 0; i < presetEnd; i++) { espalexa.getDevice(i)->setValue(0); } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d01f83ad54..917db1d439 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -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 @@ -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 diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 43baaf6718..ce9a272c91 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -195,7 +195,8 @@