diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index 81b9ec3469..f49a224f60 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -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); @@ -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); } @@ -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; + 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) { @@ -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) { @@ -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); } 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 @@

Alexa Voice Assistant

Emulate Alexa device:
Alexa invocation name:
-Also emulate devices to call the first presets

+Also emulate devices to call the first presets
+Also emulate a device for each active segment:

diff --git a/wled00/set.cpp b/wled00/set.cpp index 925f273aee..48afe275b5 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -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 diff --git a/wled00/wled.h b/wled00/wled.h index adc5c3d4e7..45d93aa330 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -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" @@ -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 diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 0d3468f9ea..c44c746900 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -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