Files
WLED/wled00/set.cpp
Frank Möhle f830ea498c Clean up global variables namespace, save a few 100 bytes of flash (#5368)
* reduce scope of some variables to "static"

these are not used anywhere else. Making them static avoid name conflicts, cleans up the global scope and in some cases allows for better  optimization by the compiler.

* remove unused reference ``tz``from analog clock usermod

* side-catch: remove two "local var shadows global var" warnings

* reduce scope of functions declared globally, but not used anywhere else
Safe to make static
* declared in fcn_declare.h, only used locally in one file
* not declared in fcn_declare.h, only used locally

* HUB75 small optimization
make bit array functions "static inline"
-> better for optimization, saves some bytes because the compiler does not need to preserve a non-inline function copy for external references.

* a few more static functions
as suggested by the rabbit.
2026-02-11 22:24:06 +01:00

1272 lines
48 KiB
C++

#include "wled.h"
/*
* Receives client input
*/
//called upon POST settings form submit
void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
{
if (subPage == SUBPAGE_PINREQ)
{
checkSettingsPIN(request->arg(F("PIN")).c_str());
return;
}
//0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec 7: DMX 8: usermods 9: N/A 10: 2D
if (subPage < 1 || subPage > 10 || !correctPIN) return;
//WIFI SETTINGS
if (subPage == SUBPAGE_WIFI)
{
unsigned cnt = 0;
for (size_t n = 0; n < WLED_MAX_WIFI_COUNT; n++) {
char cs[4] = "CS"; cs[2] = 48+n; cs[3] = 0; //client SSID
char pw[4] = "PW"; pw[2] = 48+n; pw[3] = 0; //client password
char bs[4] = "BS"; bs[2] = 48+n; bs[3] = 0; //BSSID
char ip[5] = "IP"; ip[2] = 48+n; ip[4] = 0; //IP address
char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address
char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask
#ifdef WLED_ENABLE_WPA_ENTERPRISE
char et[4] = "ET"; et[2] = 48+n; et[3] = 0; //WiFi encryption type
char ea[4] = "EA"; ea[2] = 48+n; ea[3] = 0; //enterprise anonymous identity
char ei[4] = "EI"; ei[2] = 48+n; ei[3] = 0; //enterprise identity
#endif
if (request->hasArg(cs)) {
if (n >= multiWiFi.size()) multiWiFi.emplace_back(); // expand vector by one
char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID);
char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass);
uint8_t oldBSSID[6]; memcpy(oldBSSID, multiWiFi[n].bssid, 6); // save old BSSID
strlcpy(multiWiFi[n].clientSSID, request->arg(cs).c_str(), 33);
if (strlen(oldSSID) == 0 || strncmp(multiWiFi[n].clientSSID, oldSSID, 32) != 0) {
forceReconnect = true;
}
if (!isAsterisksOnly(request->arg(pw).c_str(), 65)) {
strlcpy(multiWiFi[n].clientPass, request->arg(pw).c_str(), 65);
forceReconnect = true;
}
fillStr2MAC(multiWiFi[n].bssid, request->arg(bs).c_str());
if (memcmp(oldBSSID, multiWiFi[n].bssid, 6) != 0) { // check if BSSID changed
forceReconnect = true;
}
for (size_t i = 0; i < 4; i++) {
ip[3] = 48+i;
gw[3] = 48+i;
sn[3] = 48+i;
multiWiFi[n].staticIP[i] = request->arg(ip).toInt();
multiWiFi[n].staticGW[i] = request->arg(gw).toInt();
multiWiFi[n].staticSN[i] = request->arg(sn).toInt();
}
#ifdef WLED_ENABLE_WPA_ENTERPRISE
byte oldType = multiWiFi[n].encryptionType;
char oldAnon[65]; strcpy(oldAnon, multiWiFi[n].enterpriseAnonIdentity);
char oldIden[65]; strcpy(oldIden, multiWiFi[n].enterpriseIdentity);
if (request->hasArg(et) && request->hasArg(ea) && request->hasArg(ei)) {
multiWiFi[n].encryptionType = request->arg(et).toInt();
strlcpy(multiWiFi[n].enterpriseAnonIdentity, request->arg(ea).c_str(), 65);
strlcpy(multiWiFi[n].enterpriseIdentity, request->arg(ei).c_str(), 65);
} else {
// No enterprise settings provided, default to PSK
multiWiFi[n].encryptionType = WIFI_ENCRYPTION_TYPE_PSK;
}
if (multiWiFi[n].encryptionType == WIFI_ENCRYPTION_TYPE_PSK) {
// PSK - Clear the anonymous identity and identity fields
multiWiFi[n].enterpriseAnonIdentity[0] = '\0';
multiWiFi[n].enterpriseIdentity[0] = '\0';
}
forceReconnect |= oldType != multiWiFi[n].encryptionType;
if (strncmp(multiWiFi[n].enterpriseAnonIdentity, oldAnon, 64) != 0) {
forceReconnect = true;
}
if (strncmp(multiWiFi[n].enterpriseIdentity, oldIden, 64) != 0) {
forceReconnect = true;
}
#endif
cnt++;
}
}
// remove unused
if (cnt < multiWiFi.size()) {
cnt = multiWiFi.size() - cnt;
while (cnt--) multiWiFi.pop_back();
multiWiFi.shrink_to_fit(); // release memory
}
if (request->hasArg(F("D0"))) {
dnsAddress = IPAddress(request->arg(F("D0")).toInt(),request->arg(F("D1")).toInt(),request->arg(F("D2")).toInt(),request->arg(F("D3")).toInt());
}
strlcpy(cmDNS, request->arg(F("CM")).c_str(), 33);
apBehavior = request->arg(F("AB")).toInt();
char oldSSID[33]; strcpy(oldSSID, apSSID);
strlcpy(apSSID, request->arg(F("AS")).c_str(), 33);
if (!strcmp(oldSSID, apSSID) && apActive) forceReconnect = true;
apHide = request->hasArg(F("AH"));
int passlen = request->arg(F("AP")).length();
if (passlen == 0 || (passlen > 7 && !isAsterisksOnly(request->arg(F("AP")).c_str(), 65))) {
strlcpy(apPass, request->arg(F("AP")).c_str(), 65);
forceReconnect = true;
}
int t = request->arg(F("AC")).toInt();
if (t != apChannel) forceReconnect = true;
if (t > 0 && t < 14) apChannel = t;
#ifdef ARDUINO_ARCH_ESP32
int tx = request->arg(F("TX")).toInt();
txPower = min(max(tx, (int)WIFI_POWER_2dBm), (int)WIFI_POWER_19_5dBm);
#endif
force802_3g = request->hasArg(F("FG"));
noWifiSleep = request->hasArg(F("WS"));
#ifndef WLED_DISABLE_ESPNOW
bool oldESPNow = enableESPNow;
enableESPNow = request->hasArg(F("RE"));
if (oldESPNow != enableESPNow) forceReconnect = true;
linked_remotes.clear(); // clear old remotes
for (size_t n = 0; n < 10; n++) {
char rm[4];
snprintf(rm, sizeof(rm), "RM%d", n); // "RM0" to "RM9"
if (request->hasArg(rm)) {
const String& arg = request->arg(rm);
if (arg.isEmpty()) continue;
std::array<char, 13> mac{};
strlcpy(mac.data(), request->arg(rm).c_str(), 13);
strlwr(mac.data());
if (mac[0] != '\0') {
linked_remotes.emplace_back(mac);
}
}
}
#endif
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
ethernetType = request->arg(F("ETH")).toInt();
initEthernet();
#endif
}
//LED SETTINGS
if (subPage == SUBPAGE_LEDS)
{
int t = 0;
if (rlyPin>=0 && PinManager::isPinAllocated(rlyPin, PinOwner::Relay)) {
PinManager::deallocatePin(rlyPin, PinOwner::Relay);
}
#ifndef WLED_DISABLE_INFRARED
if (irPin>=0 && PinManager::isPinAllocated(irPin, PinOwner::IR)) {
deInitIR();
PinManager::deallocatePin(irPin, PinOwner::IR);
}
#endif
for (const auto &button : buttons) {
if (button.pin >= 0 && PinManager::isPinAllocated(button.pin, PinOwner::Button)) {
PinManager::deallocatePin(button.pin, PinOwner::Button);
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt
if (digitalPinToTouchChannel(button.pin) >= 0) // if touch capable pin
touchDetachInterrupt(button.pin); // if not assigned previously, this will do nothing
#endif
}
}
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
unsigned length, start, maMax;
uint8_t pins[OUTPUT_MAX_PINS] = {255, 255, 255, 255, 255};
String text;
// this will set global ABL max current used when per-port ABL is not used
unsigned ablMilliampsMax = request->arg(F("MA")).toInt();
BusManager::setMilliampsMax(ablMilliampsMax);
strip.autoSegments = request->hasArg(F("MS"));
strip.correctWB = request->hasArg(F("CCT"));
strip.cctFromRgb = request->hasArg(F("CR"));
cctICused = request->hasArg(F("IC"));
uint8_t cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(cctBlending);
Bus::setGlobalAWMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt());
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
useParallelI2S = request->hasArg(F("PR"));
#endif
bool busesChanged = false;
for (int s = 0; s < 36; s++) { // theoretical limit is 36 : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
int offset = s < 10 ? '0' : 'A' - 10;
char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin
char lc[4] = "LC"; lc[2] = offset+s; lc[3] = 0; //strip length
char co[4] = "CO"; co[2] = offset+s; co[3] = 0; //strip color order
char lt[4] = "LT"; lt[2] = offset+s; lt[3] = 0; //strip type
char ls[4] = "LS"; ls[2] = offset+s; ls[3] = 0; //strip start LED
char cv[4] = "CV"; cv[2] = offset+s; cv[3] = 0; //strip reverse
char sl[4] = "SL"; sl[2] = offset+s; sl[3] = 0; //skip first N LEDs
char rf[4] = "RF"; rf[2] = offset+s; rf[3] = 0; //refresh required
char aw[4] = "AW"; aw[2] = offset+s; aw[3] = 0; //auto white mode
char wo[4] = "WO"; wo[2] = offset+s; wo[3] = 0; //channel swap
char sp[4] = "SP"; sp[2] = offset+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
if (!request->hasArg(lp)) {
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s+1);
break;
}
for (int i = 0; i < 5; i++) {
lp[1] = '0'+i;
if (!request->hasArg(lp)) break;
pins[i] = (request->arg(lp).length() > 0) ? request->arg(lp).toInt() : 255;
}
type = request->arg(lt).toInt();
skip = request->arg(sl).toInt();
colorOrder = request->arg(co).toInt();
start = (request->hasArg(ls)) ? request->arg(ls).toInt() : t;
if (request->hasArg(lc) && request->arg(lc).toInt() > 0) {
t += length = request->arg(lc).toInt();
} else {
break; // no parameter
}
awmode = request->arg(aw).toInt();
uint16_t freq = request->arg(sp).toInt();
if (Bus::isPWM(type)) {
switch (freq) {
case 0 : freq = WLED_PWM_FREQ/2; break;
case 1 : freq = WLED_PWM_FREQ*2/3; break;
default:
case 2 : freq = WLED_PWM_FREQ; break;
case 3 : freq = WLED_PWM_FREQ*2; break;
case 4 : freq = WLED_PWM_FREQ*10/3; break; // uint16_t max (19531 * 3.333)
}
} else if (Bus::is2Pin(type)) {
switch (freq) {
default:
case 0 : freq = 1000; break;
case 1 : freq = 2000; break;
case 2 : freq = 5000; break;
case 3 : freq = 10000; break;
case 4 : freq = 20000; break;
}
} else {
freq = 0;
}
channelSwap = Bus::hasWhite(type) ? request->arg(wo).toInt() : 0;
if (Bus::isOnOff(type) || Bus::isPWM(type) || Bus::isVirtual(type)) { // analog and virtual
maPerLed = 0;
maMax = 0;
} else {
maPerLed = request->arg(la).toInt();
maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0
}
type |= request->hasArg(rf) << 7; // off refresh override
text = request->arg(hs).substring(0,31);
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, text);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
// we will not bother with pre-allocating ColorOrderMappings vector
BusManager::getColorOrderMap().reset();
for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) {
int offset = s < 10 ? '0' : 'A' - 10;
char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED
char xc[4] = "XC"; xc[2] = offset+s; xc[3] = 0; //strip length
char xo[4] = "XO"; xo[2] = offset+s; xo[3] = 0; //color order
char xw[4] = "XW"; xw[2] = offset+s; xw[3] = 0; //W swap
if (request->hasArg(xs)) {
start = request->arg(xs).toInt();
length = request->arg(xc).toInt();
colorOrder = request->arg(xo).toInt() & 0x0F;
colorOrder |= (request->arg(xw).toInt() & 0x0F) << 4; // add W swap information
if (!BusManager::getColorOrderMap().add(start, length, colorOrder)) break;
}
}
// update other pins
#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = request->arg(F("IR")).toInt();
if (PinManager::allocatePin(hw_ir_pin,false, PinOwner::IR)) {
irPin = hw_ir_pin;
} else {
irPin = -1;
}
irEnabled = request->arg(F("IT")).toInt();
initIR();
#endif
irApplyToAllSelected = !request->hasArg(F("MSO"));
int hw_rly_pin = request->arg(F("RL")).toInt();
if (PinManager::allocatePin(hw_rly_pin,true, PinOwner::Relay)) {
rlyPin = hw_rly_pin;
} else {
rlyPin = -1;
}
rlyMde = (bool)request->hasArg(F("RM"));
rlyOpenDrain = (bool)request->hasArg(F("RO"));
disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
int offset = i < 10 ? '0' : 'A' - 10;
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
int hw_btn_pin = request->hasArg(bt) ? request->arg(bt).toInt() : -1;
if (i >= buttons.size()) buttons.emplace_back(hw_btn_pin, request->arg(be).toInt()); // add button to vector
else {
buttons[i].pin = hw_btn_pin;
buttons[i].type = request->arg(be).toInt();
}
if (buttons[i].pin >= 0 && PinManager::allocatePin(buttons[i].pin, false, PinOwner::Button)) {
#ifdef ARDUINO_ARCH_ESP32
// ESP32 only: check that button pin is a valid gpio
if ((buttons[i].type == BTN_TYPE_ANALOG) || (buttons[i].type == BTN_TYPE_ANALOG_INVERTED)) {
if (digitalPinToAnalogChannel(buttons[i].pin) < 0) {
// not an ADC analog pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), buttons[i].pin, i);
PinManager::deallocatePin(buttons[i].pin, PinOwner::Button);
buttons[i].type = BTN_TYPE_NONE;
} else {
analogReadResolution(12); // see #4040
}
} else if ((buttons[i].type == BTN_TYPE_TOUCH || buttons[i].type == BTN_TYPE_TOUCH_SWITCH)) {
if (digitalPinToTouchChannel(buttons[i].pin) < 0) {
// not a touch pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), buttons[i].pin, i);
PinManager::deallocatePin(buttons[i].pin, PinOwner::Button);
buttons[i].type = BTN_TYPE_NONE;
}
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so
else touchAttachInterrupt(buttons[i].pin, touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
#endif
} else
#endif
{
// regular buttons and switches
if (disablePullUp) {
pinMode(buttons[i].pin, INPUT);
} else {
#ifdef ESP32
pinMode(buttons[i].pin, buttons[i].type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(buttons[i].pin, INPUT_PULLUP);
#endif
}
}
} else {
buttons[i].pin = -1;
buttons[i].type = BTN_TYPE_NONE;
}
}
// we should remove all unused buttons from the vector
for (int i = buttons.size()-1; i > 0; i--) {
if (buttons[i].pin < 0 && buttons[i].type == BTN_TYPE_NONE) {
buttons.erase(buttons.begin() + i); // remove button from vector
}
}
briS = request->arg(F("CA")).toInt();
turnOnAtBoot = request->hasArg(F("BO"));
t = request->arg(F("BP")).toInt();
if (t <= 250) bootPreset = t;
gammaCorrectBri = request->hasArg(F("GB"));
gammaCorrectCol = request->hasArg(F("GC"));
gammaCorrectVal = request->arg(F("GV")).toFloat();
if (gammaCorrectVal < 0.1f || gammaCorrectVal > 3) {
gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false;
gammaCorrectCol = false;
}
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up tables
t = request->arg(F("TD")).toInt();
if (t >= 0) transitionDelayDefault = t;
t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH"));
nightlightTargetBri = request->arg(F("TB")).toInt();
t = request->arg(F("TL")).toInt();
if (t > 0) nightlightDelayMinsDefault = t;
nightlightDelayMins = nightlightDelayMinsDefault;
nightlightMode = request->arg(F("TW")).toInt();
t = request->arg(F("PB")).toInt();
if (t >= 0 && t < 4) paletteBlend = t;
t = request->arg(F("BF")).toInt();
if (t > 0) briMultiplier = t;
doInitBusses = busesChanged;
}
//UI
if (subPage == SUBPAGE_UI)
{
strlcpy(serverDescription, request->arg(F("DS")).c_str(), 33);
//syncToggleReceive = request->hasArg(F("ST"));
simplifiedUI = request->hasArg(F("SU"));
DEBUG_PRINTLN(F("Enumerating ledmaps"));
enumerateLedmaps();
DEBUG_PRINTLN(F("Loading custom palettes"));
loadCustomPalettes(); // (re)load all custom palettes
}
//SYNC
if (subPage == SUBPAGE_SYNC)
{
int t = request->arg(F("UP")).toInt();
if (t > 0) udpPort = t;
t = request->arg(F("U2")).toInt();
if (t > 0) udpPort2 = t;
#ifndef WLED_DISABLE_ESPNOW
useESPNowSync = request->hasArg(F("EN"));
#endif
syncGroups = request->arg(F("GS")).toInt();
receiveGroups = request->arg(F("GR")).toInt();
receiveNotificationBrightness = request->hasArg(F("RB"));
receiveNotificationColor = request->hasArg(F("RC"));
receiveNotificationEffects = request->hasArg(F("RX"));
receiveNotificationPalette = request->hasArg(F("RP"));
receiveSegmentOptions = request->hasArg(F("SO"));
receiveSegmentBounds = request->hasArg(F("SG"));
sendNotifications = request->hasArg(F("SS"));
notifyDirect = request->hasArg(F("SD"));
notifyButton = request->hasArg(F("SB"));
notifyAlexa = request->hasArg(F("SA"));
notifyHue = request->hasArg(F("SH"));
t = request->arg(F("UR")).toInt();
if ((t>=0) && (t<30)) udpNumRetries = t;
nodeListEnabled = request->hasArg(F("NL"));
if (!nodeListEnabled) Nodes.clear();
nodeBroadcastEnabled = request->hasArg(F("NB"));
receiveDirect = request->hasArg(F("RD")); // UDP realtime
useMainSegmentOnly = request->hasArg(F("MO"));
realtimeRespectLedMaps = request->hasArg(F("RLM"));
e131SkipOutOfSequence = request->hasArg(F("ES"));
e131Multicast = request->hasArg(F("EM"));
t = request->arg(F("EP")).toInt();
if (t > 0) e131Port = t;
t = request->arg(F("EU")).toInt();
if (t >= 0 && t <= 63999) e131Universe = t;
t = request->arg(F("DA")).toInt();
if (t >= 0 && t <= 510) DMXAddress = t;
t = request->arg(F("XX")).toInt();
if (t >= 0 && t <= 150) DMXSegmentSpacing = t;
t = request->arg(F("PY")).toInt();
if (t >= 0 && t <= 200) e131Priority = t;
t = request->arg(F("DM")).toInt();
if (t >= DMX_MODE_DISABLED && t <= DMX_MODE_PRESET) DMXMode = t;
t = request->arg(F("ET")).toInt();
if (t > 99 && t <= 65000) realtimeTimeoutMs = t;
arlsForceMaxBri = request->hasArg(F("FB"));
arlsDisableGammaCorrection = request->hasArg(F("RG"));
t = request->arg(F("WO")).toInt();
if (t >= -255 && t <= 255) arlsOffset = t;
#ifdef WLED_ENABLE_DMX_INPUT
dmxInputTransmitPin = request->arg(F("IDMT")).toInt();
dmxInputReceivePin = request->arg(F("IDMR")).toInt();
dmxInputEnablePin = request->arg(F("IDME")).toInt();
dmxInputPort = request->arg(F("IDMP")).toInt();
if(dmxInputPort <= 0 || dmxInputPort > 2) dmxInputPort = 2;
#endif
#ifndef WLED_DISABLE_ALEXA
alexaEnabled = request->hasArg(F("AL"));
strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33);
t = request->arg(F("AP")).toInt();
if (t >= 0 && t <= 9) alexaNumPresets = t;
#endif
#ifndef WLED_DISABLE_MQTT
mqttEnabled = request->hasArg(F("MQ"));
strlcpy(mqttServer, request->arg(F("MS")).c_str(), MQTT_MAX_SERVER_LEN+1);
t = request->arg(F("MQPORT")).toInt();
if (t > 0) mqttPort = t;
strlcpy(mqttUser, request->arg(F("MQUSER")).c_str(), 41);
if (!isAsterisksOnly(request->arg(F("MQPASS")).c_str(), 41)) strlcpy(mqttPass, request->arg(F("MQPASS")).c_str(), 65);
strlcpy(mqttClientID, request->arg(F("MQCID")).c_str(), 41);
strlcpy(mqttDeviceTopic, request->arg(F("MD")).c_str(), MQTT_MAX_TOPIC_LEN+1);
strlcpy(mqttGroupTopic, request->arg(F("MG")).c_str(), MQTT_MAX_TOPIC_LEN+1);
buttonPublishMqtt = request->hasArg(F("BM"));
retainMqttMsg = request->hasArg(F("RT"));
#endif
#ifndef WLED_DISABLE_HUESYNC
for (int i=0;i<4;i++){
String a = "H"+String(i);
hueIP[i] = request->arg(a).toInt();
}
t = request->arg(F("HL")).toInt();
if (t > 0) huePollLightId = t;
t = request->arg(F("HI")).toInt();
if (t > 50) huePollIntervalMs = t;
hueApplyOnOff = request->hasArg(F("HO"));
hueApplyBri = request->hasArg(F("HB"));
hueApplyColor = request->hasArg(F("HC"));
huePollingEnabled = request->hasArg(F("HP"));
hueStoreAllowed = true;
reconnectHue();
#endif
t = request->arg(F("BD")).toInt();
if (t >= 96 && t <= 15000) serialBaud = t;
updateBaudRate(serialBaud *100);
}
//TIME
if (subPage == SUBPAGE_TIME)
{
ntpEnabled = request->hasArg(F("NT"));
strlcpy(ntpServerName, request->arg(F("NS")).c_str(), 33);
useAMPM = !request->hasArg(F("CF"));
currentTimezone = request->arg(F("TZ")).toInt();
utcOffsetSecs = request->arg(F("UO")).toInt();
//start ntp if not already connected
if (ntpEnabled && WLED_CONNECTED && !ntpConnected) ntpConnected = ntpUdp.begin(ntpLocalPort);
ntpLastSyncTime = NTP_NEVER; // force new NTP query
longitude = request->arg(F("LN")).toFloat();
latitude = request->arg(F("LT")).toFloat();
// force a sunrise/sunset re-calculation
calculateSunriseAndSunset();
overlayCurrent = request->hasArg(F("OL")) ? 1 : 0;
overlayMin = request->arg(F("O1")).toInt();
overlayMax = request->arg(F("O2")).toInt();
analogClock12pixel = request->arg(F("OM")).toInt();
analogClock5MinuteMarks = request->hasArg(F("O5"));
analogClockSecondsTrail = request->hasArg(F("OS"));
analogClockSolidBlack = request->hasArg(F("OB"));
countdownMode = request->hasArg(F("CE"));
countdownYear = request->arg(F("CY")).toInt();
countdownMonth = request->arg(F("CI")).toInt();
countdownDay = request->arg(F("CD")).toInt();
countdownHour = request->arg(F("CH")).toInt();
countdownMin = request->arg(F("CM")).toInt();
countdownSec = request->arg(F("CS")).toInt();
setCountdown();
macroAlexaOn = request->arg(F("A0")).toInt();
macroAlexaOff = request->arg(F("A1")).toInt();
macroCountdown = request->arg(F("MC")).toInt();
macroNl = request->arg(F("MN")).toInt();
int ii = 0;
for (auto &button : buttons) {
char mp[4] = "MP"; mp[2] = (ii<10?'0':'A'-10)+ii; mp[3] = 0; // short
char ml[4] = "ML"; ml[2] = (ii<10?'0':'A'-10)+ii; ml[3] = 0; // long
char md[4] = "MD"; md[2] = (ii<10?'0':'A'-10)+ii; md[3] = 0; // double
//if (!request->hasArg(mp)) break;
button.macroButton = request->arg(mp).toInt(); // these will default to 0 if not present
button.macroLongPress = request->arg(ml).toInt();
button.macroDoublePress = request->arg(md).toInt();
ii++;
}
char k[3]; k[2] = 0;
for (int i = 0; i<10; i++) {
k[1] = i+48;//ascii 0,1,2,3,...
k[0] = 'H'; //timer hours
timerHours[i] = request->arg(k).toInt();
k[0] = 'N'; //minutes
timerMinutes[i] = request->arg(k).toInt();
k[0] = 'T'; //macros
timerMacro[i] = request->arg(k).toInt();
k[0] = 'W'; //weekdays
timerWeekday[i] = request->arg(k).toInt();
if (i<8) {
k[0] = 'M'; //start month
timerMonth[i] = request->arg(k).toInt() & 0x0F;
timerMonth[i] <<= 4;
k[0] = 'P'; //end month
timerMonth[i] += (request->arg(k).toInt() & 0x0F);
k[0] = 'D'; //start day
timerDay[i] = request->arg(k).toInt();
k[0] = 'E'; //end day
timerDayEnd[i] = request->arg(k).toInt();
}
}
}
//SECURITY
if (subPage == SUBPAGE_SEC)
{
if (request->hasArg(F("RS"))) //complete factory reset
{
WLED_FS.format();
serveMessage(request, 200, F("All Settings erased."), F("Connect to WLED-AP to setup again"),255);
doReboot = true; // may reboot immediately on dual-core system (race condition) which is desireable in this case
}
if (request->hasArg(F("PIN"))) {
const char *pin = request->arg(F("PIN")).c_str();
unsigned pinLen = strlen(pin);
if (pinLen == 4 || pinLen == 0) {
unsigned numZeros = 0;
for (unsigned i = 0; i < pinLen; i++) numZeros += (pin[i] == '0');
if (numZeros < pinLen || pinLen == 0) { // ignore 0000 input (placeholder)
strlcpy(settingsPIN, pin, 5);
}
settingsPIN[4] = 0;
}
}
bool pwdCorrect = !otaLock; //always allow access if ota not locked
if (request->hasArg(F("OP")))
{
if (otaLock && strcmp(otaPass,request->arg(F("OP")).c_str()) == 0)
{
// brute force protection: do not unlock even if correct if last save was less than 3 seconds ago
if (millis() - lastEditTime > PIN_RETRY_COOLDOWN) pwdCorrect = true;
}
if (!otaLock && request->arg(F("OP")).length() > 0)
{
strlcpy(otaPass,request->arg(F("OP")).c_str(), 33); // set new OTA password
}
}
if (pwdCorrect) //allow changes if correct pwd or no ota active
{
otaLock = request->hasArg(F("NO"));
wifiLock = request->hasArg(F("OW"));
#ifndef WLED_DISABLE_OTA
aOtaEnabled = request->hasArg(F("AO"));
#endif
otaSameSubnet = request->hasArg(F("SU"));
}
}
#ifdef WLED_ENABLE_DMX // include only if DMX is enabled
if (subPage == SUBPAGE_DMX)
{
int t = request->arg(F("PU")).toInt();
if (t >= 0 && t <= 63999) e131ProxyUniverse = t;
t = request->arg(F("CN")).toInt();
if (t>0 && t<16) {
DMXChannels = t;
}
t = request->arg(F("CS")).toInt();
if (t>0 && t<513) {
DMXStart = t;
}
t = request->arg(F("CG")).toInt();
if (t>0 && t<513) {
DMXGap = t;
}
t = request->arg(F("SL")).toInt();
if (t>=0 && t < MAX_LEDS) {
DMXStartLED = t;
}
for (int i=0; i<15; i++) {
String argname = "CH" + String((i+1));
t = request->arg(argname).toInt();
DMXFixtureMap[i] = t;
}
}
#endif
//USERMODS
if (subPage == SUBPAGE_UM)
{
if (!requestJSONBufferLock(JSON_LOCK_SETTINGS)) {
request->deferResponse();
return;
}
// global I2C & SPI pins
int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt();
int8_t hw_scl_pin = !request->arg(F("SCL")).length() ? -1 : (int)request->arg(F("SCL")).toInt();
if (i2c_sda != hw_sda_pin || i2c_scl != hw_scl_pin) {
// only if pins changed
uint8_t old_i2c[2] = { static_cast<uint8_t>(i2c_scl), static_cast<uint8_t>(i2c_sda) };
PinManager::deallocateMultiplePins(old_i2c, 2, PinOwner::HW_I2C); // just in case deallocation of old pins
PinManagerPinType i2c[2] = { { hw_sda_pin, true }, { hw_scl_pin, true } };
if (hw_sda_pin >= 0 && hw_scl_pin >= 0 && PinManager::allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) {
i2c_sda = hw_sda_pin;
i2c_scl = hw_scl_pin;
// no bus re-initialisation as usermods do not get any notification
//Wire.begin(i2c_sda, i2c_scl);
} else {
// there is no Wire.end()
DEBUG_PRINTLN(F("Could not allocate I2C pins."));
i2c_sda = -1;
i2c_scl = -1;
}
}
int8_t hw_mosi_pin = !request->arg(F("MOSI")).length() ? -1 : (int)request->arg(F("MOSI")).toInt();
int8_t hw_miso_pin = !request->arg(F("MISO")).length() ? -1 : (int)request->arg(F("MISO")).toInt();
int8_t hw_sclk_pin = !request->arg(F("SCLK")).length() ? -1 : (int)request->arg(F("SCLK")).toInt();
#ifdef ESP8266
// cannot change pins on ESP8266
if (hw_mosi_pin >= 0 && hw_mosi_pin != HW_PIN_DATASPI) hw_mosi_pin = HW_PIN_DATASPI;
if (hw_miso_pin >= 0 && hw_miso_pin != HW_PIN_MISOSPI) hw_mosi_pin = HW_PIN_MISOSPI;
if (hw_sclk_pin >= 0 && hw_sclk_pin != HW_PIN_CLOCKSPI) hw_sclk_pin = HW_PIN_CLOCKSPI;
#endif
if (spi_mosi != hw_mosi_pin || spi_miso != hw_miso_pin || spi_sclk != hw_sclk_pin) {
// only if pins changed
uint8_t old_spi[3] = { static_cast<uint8_t>(spi_mosi), static_cast<uint8_t>(spi_miso), static_cast<uint8_t>(spi_sclk) };
PinManager::deallocateMultiplePins(old_spi, 3, PinOwner::HW_SPI); // just in case deallocation of old pins
PinManagerPinType spi[3] = { { hw_mosi_pin, true }, { hw_miso_pin, true }, { hw_sclk_pin, true } };
if (hw_mosi_pin >= 0 && hw_sclk_pin >= 0 && PinManager::allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) {
spi_mosi = hw_mosi_pin;
spi_miso = hw_miso_pin;
spi_sclk = hw_sclk_pin;
// no bus re-initialisation as usermods do not get any notification
//SPI.end();
#ifdef ESP32
//SPI.begin(spi_sclk, spi_miso, spi_mosi);
#else
//SPI.begin();
#endif
} else {
//SPI.end();
DEBUG_PRINTLN(F("Could not allocate SPI pins."));
spi_mosi = -1;
spi_miso = -1;
spi_sclk = -1;
}
}
JsonObject um = pDoc->createNestedObject("um");
size_t args = request->args();
unsigned j=0;
for (size_t i=0; i<args; i++) {
String name = request->argName(i);
String value = request->arg(i);
// POST request parameters are combined as <usermodname>_<usermodparameter>
int umNameEnd = name.indexOf(":");
if (umNameEnd<1) continue; // parameter does not contain ":" or on 1st place -> wrong
JsonObject mod = um[name.substring(0,umNameEnd)]; // get a usermod JSON object
if (mod.isNull()) {
mod = um.createNestedObject(name.substring(0,umNameEnd)); // if it does not exist create it
}
DEBUG_PRINT(name.substring(0,umNameEnd));
DEBUG_PRINT(":");
name = name.substring(umNameEnd+1); // remove mod name from string
// if the resulting name still contains ":" this means nested object
JsonObject subObj;
int umSubObj = name.indexOf(":");
DEBUG_PRINTF_P(PSTR("(%d):"),umSubObj);
if (umSubObj>0) {
subObj = mod[name.substring(0,umSubObj)];
if (subObj.isNull())
subObj = mod.createNestedObject(name.substring(0,umSubObj));
name = name.substring(umSubObj+1); // remove nested object name from string
} else {
subObj = mod;
}
DEBUG_PRINT(name);
// check if parameters represent array
if (name.endsWith("[]")) {
name.replace("[]","");
value.replace(",","."); // just in case conversion
if (!subObj[name].is<JsonArray>()) {
JsonArray ar = subObj.createNestedArray(name);
if (value.indexOf(".") >= 0) ar.add(value.toFloat()); // we do have a float
else ar.add(value.toInt()); // we may have an int
j=0;
} else {
if (value.indexOf(".") >= 0) subObj[name].add(value.toFloat()); // we do have a float
else subObj[name].add(value.toInt()); // we may have an int
j++;
}
DEBUG_PRINTF_P(PSTR("[%d] = %s\n"), j, value.c_str());
} else {
// we are using a hidden field with the same name as our parameter (!before the actual parameter!)
// to describe the type of parameter (text,float,int), for boolean parameters the first field contains "off"
// so checkboxes have one or two fields (first is always "false", existence of second depends on checkmark and may be "true")
if (subObj[name].isNull()) {
// the first occurrence of the field describes the parameter type (used in next loop)
if (value == "false") subObj[name] = false; // checkboxes may have only one field
else subObj[name] = value;
} else {
String type = subObj[name].as<String>(); // get previously stored value as a type
if (subObj[name].is<bool>()) subObj[name] = true; // checkbox/boolean
else if (type == "number") {
value.replace(",","."); // just in case conversion
if (value.indexOf(".") >= 0) subObj[name] = value.toFloat(); // we do have a float
else subObj[name] = value.toInt(); // we may have an int
} else if (type == "int") subObj[name] = value.toInt();
else subObj[name] = value; // text fields
}
DEBUG_PRINTF_P(PSTR(" = %s\n"), value.c_str());
}
}
UsermodManager::readFromConfig(um); // force change of usermod parameters
DEBUG_PRINTLN(F("Done re-init UsermodManager::"));
releaseJSONBufferLock();
}
#ifndef WLED_DISABLE_2D
//2D panels
if (subPage == SUBPAGE_2D)
{
strip.isMatrix = request->arg(F("SOMP")).toInt();
strip.panel.clear();
if (strip.isMatrix) {
unsigned panels = constrain(request->arg(F("MPC")).toInt(), 1, WLED_MAX_PANELS);
strip.panel.reserve(panels); // pre-allocate memory
for (unsigned i=0; i<panels; i++) {
WS2812FX::Panel p;
char pO[8] = { '\0' };
snprintf_P(pO, 7, PSTR("P%d"), i); // WLED_MAX_PANELS is less than 100 so pO will always only be 4 characters or less
pO[7] = '\0';
unsigned l = strlen(pO);
// create P0B, P1B, ..., P63B, etc for other PxxX
pO[l] = 'B'; if (!request->hasArg(pO)) break;
pO[l] = 'B'; p.bottomStart = request->arg(pO).toInt();
pO[l] = 'R'; p.rightStart = request->arg(pO).toInt();
pO[l] = 'V'; p.vertical = request->arg(pO).toInt();
pO[l] = 'S'; p.serpentine = request->hasArg(pO);
pO[l] = 'X'; p.xOffset = request->arg(pO).toInt();
pO[l] = 'Y'; p.yOffset = request->arg(pO).toInt();
pO[l] = 'W'; p.width = request->arg(pO).toInt();
pO[l] = 'H'; p.height = request->arg(pO).toInt();
strip.panel.push_back(p);
}
}
strip.panel.shrink_to_fit(); // release unused memory
// we are changing matrix/ledmap geometry which *will* affect existing segments
// since we are not in loop() context we must make sure that effects are not running. credit @blazonchek for properly fixing #4911
strip.suspend();
strip.waitForIt();
strip.deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
strip.makeAutoSegments(true); // force re-creation of segments
strip.resume();
}
#endif
lastEditTime = millis();
// do not save if factory reset or LED settings (which are saved after LED re-init)
configNeedsWrite = subPage != SUBPAGE_LEDS && !(subPage == SUBPAGE_SEC && doReboot);
if (subPage == SUBPAGE_UM) doReboot = request->hasArg(F("RBT")); // prevent race condition on dual core system (set reboot here, after configNeedsWrite has been set)
#ifndef WLED_DISABLE_ALEXA
if (subPage == SUBPAGE_SYNC) alexaInit();
#endif
}
//HTTP API request parser
bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
{
if (!(req.indexOf("win") >= 0)) return false;
int pos = 0;
DEBUG_PRINTF_P(PSTR("API req: %s\n"), req.c_str());
//segment select (sets main segment)
pos = req.indexOf(F("SM="));
if (pos > 0 && !realtimeMode) {
strip.setMainSegmentId(getNumVal(req, pos));
}
byte selectedSeg = strip.getFirstSelectedSegId();
bool singleSegment = false;
pos = req.indexOf(F("SS="));
if (pos > 0) {
unsigned t = getNumVal(req, pos);
if (t < strip.getSegmentsNum()) {
selectedSeg = t;
singleSegment = true;
}
}
Segment& selseg = strip.getSegment(selectedSeg);
pos = req.indexOf(F("SV=")); //segment selected
if (pos > 0) {
unsigned t = getNumVal(req, pos);
if (t == 2) for (unsigned i = 0; i < strip.getSegmentsNum(); i++) strip.getSegment(i).selected = false; // unselect other segments
selseg.selected = t;
}
// temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg()
uint32_t col0 = selseg.colors[0];
uint32_t col1 = selseg.colors[1];
uint32_t col2 = selseg.colors[2];
byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)};
byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)};
byte effectIn = selseg.mode;
byte speedIn = selseg.speed;
byte intensityIn = selseg.intensity;
byte paletteIn = selseg.palette;
byte custom1In = selseg.custom1;
byte custom2In = selseg.custom2;
byte custom3In = selseg.custom3;
byte check1In = selseg.check1;
byte check2In = selseg.check2;
byte check3In = selseg.check3;
uint16_t startI = selseg.start;
uint16_t stopI = selseg.stop;
uint16_t startY = selseg.startY;
uint16_t stopY = selseg.stopY;
uint8_t grpI = selseg.grouping;
uint16_t spcI = selseg.spacing;
pos = req.indexOf(F("&S=")); //segment start
if (pos > 0) {
startI = std::abs(getNumVal(req, pos));
}
pos = req.indexOf(F("S2=")); //segment stop
if (pos > 0) {
stopI = std::abs(getNumVal(req, pos));
}
pos = req.indexOf(F("GP=")); //segment grouping
if (pos > 0) {
grpI = std::max(1,getNumVal(req, pos));
}
pos = req.indexOf(F("SP=")); //segment spacing
if (pos > 0) {
spcI = std::max(0,getNumVal(req, pos));
}
strip.suspend(); // must suspend strip operations before changing geometry
selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D);
strip.resume();
pos = req.indexOf(F("RV=")); //Segment reverse
if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0';
pos = req.indexOf(F("MI=")); //Segment mirror
if (pos > 0) selseg.mirror = req.charAt(pos+3) != '0';
pos = req.indexOf(F("SB=")); //Segment brightness/opacity
if (pos > 0) {
byte segbri = getNumVal(req, pos);
selseg.setOption(SEG_OPTION_ON, segbri); // use transition
if (segbri) {
selseg.setOpacity(segbri);
}
}
pos = req.indexOf(F("SW=")); //segment power
if (pos > 0) {
switch (getNumVal(req, pos)) {
case 0: selseg.setOption(SEG_OPTION_ON, false); break; // use transition
case 1: selseg.setOption(SEG_OPTION_ON, true); break; // use transition
default: selseg.setOption(SEG_OPTION_ON, !selseg.on); break; // use transition
}
}
pos = req.indexOf(F("PS=")); //saves current in preset
if (pos > 0) savePreset(getNumVal(req, pos));
pos = req.indexOf(F("P1=")); //sets first preset for cycle
if (pos > 0) presetCycMin = getNumVal(req, pos);
pos = req.indexOf(F("P2=")); //sets last preset for cycle
if (pos > 0) presetCycMax = getNumVal(req, pos);
//apply preset
if (updateVal(req.c_str(), "PL=", presetCycCurr, presetCycMin, presetCycMax)) {
applyPreset(presetCycCurr);
}
pos = req.indexOf(F("NP")); //advances to next preset in a playlist
if (pos > 0) doAdvancePlaylist = true;
//set brightness
updateVal(req.c_str(), "&A=", bri);
bool col0Changed = false, col1Changed = false, col2Changed = false;
//set colors
col0Changed |= updateVal(req.c_str(), "&R=", colIn[0]);
col0Changed |= updateVal(req.c_str(), "&G=", colIn[1]);
col0Changed |= updateVal(req.c_str(), "&B=", colIn[2]);
col0Changed |= updateVal(req.c_str(), "&W=", colIn[3]);
col1Changed |= updateVal(req.c_str(), "R2=", colInSec[0]);
col1Changed |= updateVal(req.c_str(), "G2=", colInSec[1]);
col1Changed |= updateVal(req.c_str(), "B2=", colInSec[2]);
col1Changed |= updateVal(req.c_str(), "W2=", colInSec[3]);
#ifdef WLED_ENABLE_LOXONE
//lox parser
pos = req.indexOf(F("LX=")); // Lox primary color
if (pos > 0) {
int lxValue = getNumVal(req, pos);
if (parseLx(lxValue, colIn)) {
bri = 255;
nightlightActive = false; //always disable nightlight when toggling
col0Changed = true;
}
}
pos = req.indexOf(F("LY=")); // Lox secondary color
if (pos > 0) {
int lxValue = getNumVal(req, pos);
if(parseLx(lxValue, colInSec)) {
bri = 255;
nightlightActive = false; //always disable nightlight when toggling
col1Changed = true;
}
}
#endif
//set hue
pos = req.indexOf(F("HU="));
if (pos > 0) {
uint16_t temphue = getNumVal(req, pos);
byte tempsat = 255;
pos = req.indexOf(F("SA="));
if (pos > 0) {
tempsat = getNumVal(req, pos);
}
byte sec = req.indexOf(F("H2"));
colorHStoRGB(temphue, tempsat, (sec>0) ? colInSec : colIn);
col0Changed |= (!sec); col1Changed |= sec;
}
//set white spectrum (kelvin)
pos = req.indexOf(F("&K="));
if (pos > 0) {
byte sec = req.indexOf(F("K2"));
colorKtoRGB(getNumVal(req, pos), (sec>0) ? colInSec : colIn);
col0Changed |= (!sec); col1Changed |= sec;
}
//set color from HEX or 32bit DEC
pos = req.indexOf(F("CL="));
if (pos > 0) {
colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str());
col0Changed = true;
}
pos = req.indexOf(F("C2="));
if (pos > 0) {
colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str());
col1Changed = true;
}
pos = req.indexOf(F("C3="));
if (pos > 0) {
byte tmpCol[4];
colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str());
col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]);
selseg.setColor(2, col2); // defined above (SS= or main)
col2Changed = true;
}
//set to random hue SR=0->1st SR=1->2nd
pos = req.indexOf(F("SR"));
if (pos > 0) {
byte sec = getNumVal(req, pos);
setRandomColor(sec? colInSec : colIn);
col0Changed |= (!sec); col1Changed |= sec;
}
// apply colors to selected segment, and all selected segments if applicable
if (col0Changed) {
col0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]);
selseg.setColor(0, col0);
}
if (col1Changed) {
col1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]);
selseg.setColor(1, col1);
}
//swap 2nd & 1st
pos = req.indexOf(F("SC"));
if (pos > 0) {
std::swap(col0,col1);
col0Changed = col1Changed = true;
}
bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false;
bool custom1Changed = false, custom2Changed = false, custom3Changed = false, check1Changed = false, check2Changed = false, check3Changed = false;
// set effect parameters
if (updateVal(req.c_str(), "FX=", effectIn, 0, strip.getModeCount()-1)) {
if (request != nullptr) unloadPlaylist(); // unload playlist if changing FX using web request
fxModeChanged = true;
}
speedChanged = updateVal(req.c_str(), "SX=", speedIn);
intensityChanged = updateVal(req.c_str(), "IX=", intensityIn);
paletteChanged = updateVal(req.c_str(), "FP=", paletteIn, 0, getPaletteCount()-1);
custom1Changed = updateVal(req.c_str(), "X1=", custom1In);
custom2Changed = updateVal(req.c_str(), "X2=", custom2In);
custom3Changed = updateVal(req.c_str(), "X3=", custom3In);
check1Changed = updateVal(req.c_str(), "M1=", check1In);
check2Changed = updateVal(req.c_str(), "M2=", check2In);
check3Changed = updateVal(req.c_str(), "M3=", check3In);
stateChanged |= (fxModeChanged || speedChanged || intensityChanged || paletteChanged || custom1Changed || custom2Changed || custom3Changed || check1Changed || check2Changed || check3Changed);
// apply to main and all selected segments to prevent #1618.
for (unsigned i = 0; i < strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (i != selectedSeg && (singleSegment || !seg.isActive() || !seg.isSelected())) continue; // skip non main segments if not applying to all
if (fxModeChanged) seg.setMode(effectIn, req.indexOf(F("FXD="))>0); // apply defaults if FXD= is specified
if (speedChanged) seg.speed = speedIn;
if (intensityChanged) seg.intensity = intensityIn;
if (paletteChanged) seg.setPalette(paletteIn);
if (col0Changed) seg.setColor(0, col0);
if (col1Changed) seg.setColor(1, col1);
if (col2Changed) seg.setColor(2, col2);
if (custom1Changed) seg.custom1 = custom1In;
if (custom2Changed) seg.custom2 = custom2In;
if (custom3Changed) seg.custom3 = custom3In;
if (check1Changed) seg.check1 = (bool)check1In;
if (check2Changed) seg.check2 = (bool)check2In;
if (check3Changed) seg.check3 = (bool)check3In;
}
//set advanced overlay
pos = req.indexOf(F("OL="));
if (pos > 0) {
overlayCurrent = getNumVal(req, pos);
}
//apply macro (deprecated, added for compatibility with pre-0.11 automations)
pos = req.indexOf(F("&M="));
if (pos > 0) {
applyPreset(getNumVal(req, pos) + 16);
}
//toggle send UDP direct notifications
pos = req.indexOf(F("SN="));
if (pos > 0) notifyDirect = (req.charAt(pos+3) != '0');
//toggle receive UDP direct notifications
pos = req.indexOf(F("RN="));
if (pos > 0) receiveGroups = (req.charAt(pos+3) != '0') ? receiveGroups | 1 : receiveGroups & 0xFE;
//receive live data via UDP/Hyperion
pos = req.indexOf(F("RD="));
if (pos > 0) receiveDirect = (req.charAt(pos+3) != '0');
//main toggle on/off (parse before nightlight, #1214)
pos = req.indexOf(F("&T="));
if (pos > 0) {
nightlightActive = false; //always disable nightlight when toggling
switch (getNumVal(req, pos))
{
case 0: if (bri != 0){briLast = bri; bri = 0;} break; //off, only if it was previously on
case 1: if (bri == 0) bri = briLast; break; //on, only if it was previously off
default: toggleOnOff(); //toggle
}
}
//toggle nightlight mode
bool aNlDef = false;
if (req.indexOf(F("&ND")) > 0) aNlDef = true;
pos = req.indexOf(F("NL="));
if (pos > 0)
{
if (req.charAt(pos+3) == '0')
{
nightlightActive = false;
} else {
nightlightActive = true;
if (!aNlDef) nightlightDelayMins = getNumVal(req, pos);
else nightlightDelayMins = nightlightDelayMinsDefault;
nightlightStartTime = millis();
}
} else if (aNlDef)
{
nightlightActive = true;
nightlightDelayMins = nightlightDelayMinsDefault;
nightlightStartTime = millis();
}
//set nightlight target brightness
pos = req.indexOf(F("NT="));
if (pos > 0) {
nightlightTargetBri = getNumVal(req, pos);
nightlightActiveOld = false; //re-init
}
//toggle nightlight fade
pos = req.indexOf(F("NF="));
if (pos > 0)
{
nightlightMode = getNumVal(req, pos);
nightlightActiveOld = false; //re-init
}
if (nightlightMode > NL_MODE_SUN) nightlightMode = NL_MODE_SUN;
pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(req, pos);
strip.setTransition(transitionDelay);
//set time (unix timestamp)
pos = req.indexOf(F("ST="));
if (pos > 0) {
setTimeFromAPI(getNumVal(req, pos));
}
//set countdown goal (unix timestamp)
pos = req.indexOf(F("CT="));
if (pos > 0) {
countdownTime = getNumVal(req, pos);
if (countdownTime - toki.second() > 0) countdownOverTriggered = false;
}
pos = req.indexOf(F("LO="));
if (pos > 0) {
realtimeOverride = getNumVal(req, pos);
if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS;
if (realtimeMode && useMainSegmentOnly) {
strip.getMainSegment().freeze = !realtimeOverride;
realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only
}
}
pos = req.indexOf(F("RB"));
if (pos > 0) doReboot = true;
// clock mode, 0: normal, 1: countdown
pos = req.indexOf(F("NM="));
if (pos > 0) countdownMode = (req.charAt(pos+3) != '0');
pos = req.indexOf(F("U0=")); //user var 0
if (pos > 0) {
userVar0 = getNumVal(req, pos);
}
pos = req.indexOf(F("U1=")); //user var 1
if (pos > 0) {
userVar1 = getNumVal(req, pos);
}
// you can add more if you need
// global colPri[], effectCurrent, ... are updated in stateChanged()
if (!apply) return true; // when called by JSON API, do not call colorUpdated() here
pos = req.indexOf(F("&NN")); //do not send UDP notifications this time
stateUpdated((pos > 0) ? CALL_MODE_NO_NOTIFY : CALL_MODE_DIRECT_CHANGE);
// internal call, does not send XML response
pos = req.indexOf(F("IN"));
if ((request != nullptr) && (pos < 1)) {
auto response = request->beginResponseStream("text/xml");
XML_response(*response);
request->send(response);
}
return true;
}