V2 of usermod configuration settings

- added parsing of POST fields to use proper type
- updated readFromConfig() to reflect parsing
- added a possibility to use nested object in UM settings
- internal changes and fixes
This commit is contained in:
Blaz Kristan 2021-06-27 12:15:35 +02:00
parent 81182bb125
commit f7ce83ea34
13 changed files with 204 additions and 291 deletions

View File

@ -426,6 +426,8 @@ class Animated_Staircase : public Usermod {
/* /*
* Reads the configuration to internal flash memory before setup() is called. * Reads the configuration to internal flash memory before setup() is called.
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject& root) { bool readFromConfig(JsonObject& root) {
bool oldUseUSSensorTop = useUSSensorTop; bool oldUseUSSensorTop = useUSSensorTop;
@ -435,49 +437,45 @@ class Animated_Staircase : public Usermod {
int8_t oldBottomAPin = bottomPIRorTriggerPin; int8_t oldBottomAPin = bottomPIRorTriggerPin;
int8_t oldBottomBPin = bottomEchoPin; int8_t oldBottomBPin = bottomEchoPin;
bool configComplete = true; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
JsonObject staircase = root[FPSTR(_name)]; DEBUG_PRINT(FPSTR(_name));
if (!staircase.isNull()) { DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
if (staircase[FPSTR(_enabled)].is<bool>()) { return false;
enabled = staircase[FPSTR(_enabled)].as<bool>();
} else {
String str = staircase[FPSTR(_enabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
segment_delay_ms = min(10000,max(10,staircase[FPSTR(_segmentDelay)].as<int>())); // max delay 10s
on_time_ms = min(900,max(10,staircase[FPSTR(_onTime)].as<int>())) * 1000; // min 10s, max 15min
if (staircase[FPSTR(_useTopUltrasoundSensor)].is<bool>()) {
useUSSensorTop = staircase[FPSTR(_useTopUltrasoundSensor)].as<bool>();
} else {
String str = staircase[FPSTR(_useTopUltrasoundSensor)]; // checkbox -> off or on
useUSSensorTop = (bool)(str!="off"); // off is guaranteed to be present
} }
topPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_topPIRorTrigger_pin)].as<int>())); enabled = top[FPSTR(_enabled)] | enabled;
topEchoPin = min(39,max(-1,staircase[FPSTR(_topEcho_pin)].as<int>()));
if (staircase[FPSTR(_useBottomUltrasoundSensor)].is<bool>()) { segment_delay_ms = top[FPSTR(_segmentDelay)] | segment_delay_ms;
useUSSensorBottom = staircase[FPSTR(_useBottomUltrasoundSensor)].as<bool>(); segment_delay_ms = (unsigned long) min((unsigned long)10000,max((unsigned long)10,(unsigned long)segment_delay_ms)); // max delay 10s
} else {
String str = staircase[FPSTR(_useBottomUltrasoundSensor)]; // checkbox -> off or on on_time_ms = top[FPSTR(_onTime)] | on_time_ms/1000;
useUSSensorBottom = (bool)(str!="off"); // off is guaranteed to be present on_time_ms = min(900,max(10,(int)on_time_ms)) * 1000; // min 10s, max 15min
}
bottomPIRorTriggerPin = min(39,max(-1,staircase[FPSTR(_bottomPIRorTrigger_pin)].as<int>())); useUSSensorTop = top[FPSTR(_useTopUltrasoundSensor)] | useUSSensorTop;
bottomEchoPin = min(39,max(-1,staircase[FPSTR(_bottomEcho_pin)].as<int>())); topPIRorTriggerPin = top[FPSTR(_topPIRorTrigger_pin)] | topPIRorTriggerPin;
topMaxDist = min(150,max(30,staircase[FPSTR(_topEchoCm)].as<int>())); // max distnace ~1.5m (a lag of 9ms may be expected) // topPIRorTriggerPin = min(33,max(-1,(int)topPIRorTriggerPin)); // bounds check
bottomMaxDist = min(150,max(30,staircase[FPSTR(_bottomEchoCm)].as<int>())); // max distance ~1.5m (a lag of 9ms may be expected) topEchoPin = top[FPSTR(_topEcho_pin)] | topEchoPin;
} else { // topEchoPin = min(39,max(-1,(int)topEchoPin)); // bounds check
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
configComplete = false; useUSSensorBottom = top[FPSTR(_useBottomUltrasoundSensor)] | useUSSensorBottom;
} bottomPIRorTriggerPin = top[FPSTR(_bottomPIRorTrigger_pin)] | bottomPIRorTriggerPin;
// bottomPIRorTriggerPin = min(33,max(-1,(int)bottomPIRorTriggerPin)); // bounds check
bottomEchoPin = top[FPSTR(_bottomEcho_pin)] | bottomEchoPin;
// bottomEchoPin = min(39,max(-1,(int)bottomEchoPin)); // bounds check
topMaxDist = top[FPSTR(_topEchoCm)] | topMaxDist;
topMaxDist = min(150,max(30,(int)topMaxDist)); // max distnace ~1.5m (a lag of 9ms may be expected)
bottomMaxDist = top[FPSTR(_bottomEchoCm)] | bottomMaxDist;
bottomMaxDist = min(150,max(30,(int)bottomMaxDist)); // max distance ~1.5m (a lag of 9ms may be expected)
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
DEBUG_PRINTLN(F("Staircase config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
// changing paramters from settings page // changing paramters from settings page
DEBUG_PRINTLN(F("Staircase config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
bool changed = false; bool changed = false;
if ((oldUseUSSensorTop != useUSSensorTop) || if ((oldUseUSSensorTop != useUSSensorTop) ||
(oldUseUSSensorBottom != useUSSensorBottom) || (oldUseUSSensorBottom != useUSSensorBottom) ||
@ -493,7 +491,8 @@ class Animated_Staircase : public Usermod {
} }
if (changed) setup(); if (changed) setup();
} }
return configComplete; // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
} }
/* /*

View File

@ -23,8 +23,12 @@
//class name. Use something descriptive and leave the ": public Usermod" part :) //class name. Use something descriptive and leave the ": public Usermod" part :)
class MyExampleUsermod : public Usermod { class MyExampleUsermod : public Usermod {
private: private:
// sample usermod default value for variable (you can also use constructor)
int userVar0 = 42;
//Private class members. You can declare variables and functions only accessible to your usermod here //Private class members. You can declare variables and functions only accessible to your usermod here
unsigned long lastTime = 0; unsigned long lastTime = 0;
public: public:
//Functions called by WLED //Functions called by WLED
@ -140,21 +144,14 @@ class MyExampleUsermod : public Usermod {
*/ */
bool readFromConfig(JsonObject& root) bool readFromConfig(JsonObject& root)
{ {
userVar0 = 42; //set your variables to their boot default value (this can also be done when declaring the variable) //set defaults for variables when declaring the variable (class definition or constructor)
JsonObject top = root["exampleUsermod"]; JsonObject top = root["exampleUsermod"];
if (!top.isNull()) { if (!top.isNull()) return false;
bool configComplete = true;
//check if value is there userVar0 = top["great"] | userVar0;
if (top.containsKey("great")) {
//convert value to the correct type
userVar0 = top["great"].as<int>();
} else configComplete = false;
if (configComplete) return true; // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
} return true;
return false;
} }

View File

@ -155,6 +155,7 @@ Delay <input type=\"number\" min=\"5\" max=\"300\" value=\"";
if (top.isNull()) return false; if (top.isNull()) return false;
m_pingDelayMs = top["PingDelayMs"] | m_pingDelayMs; m_pingDelayMs = top["PingDelayMs"] | m_pingDelayMs;
m_pingDelayMs = max(5000UL, min(18000000UL, m_pingDelayMs)); m_pingDelayMs = max(5000UL, min(18000000UL, m_pingDelayMs));
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }

View File

@ -319,6 +319,8 @@ public:
/** /**
* restore the changeable values * restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject &root) bool readFromConfig(JsonObject &root)
{ {
@ -326,57 +328,32 @@ public:
int8_t oldPin = PIRsensorPin; int8_t oldPin = PIRsensorPin;
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) return false; if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
if (top["pin"] != nullptr) { DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
PIRsensorPin = min(39,max(-1,top["pin"].as<int>())); // check bounds return false;
} }
if (top[FPSTR(_enabled)] != nullptr) { PIRsensorPin = top["pin"] | PIRsensorPin;
if (top[FPSTR(_enabled)].is<bool>()) { // PIRsensorPin = min(39,max(-1,(int)PIRsensorPin)); // check bounds
enabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
}
if (top[FPSTR(_switchOffDelay)] != nullptr) { enabled = top[FPSTR(_enabled)] | enabled;
m_switchOffDelay = (top[FPSTR(_switchOffDelay)].as<int>() * 1000);
}
if (top[FPSTR(_onPreset)] != nullptr) { m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000;
m_onPreset = max(0,min(250,top[FPSTR(_onPreset)].as<int>()));
}
if (top[FPSTR(_offPreset)] != nullptr) { m_onPreset = top[FPSTR(_onPreset)] | m_onPreset;
m_offPreset = max(0,min(250,top[FPSTR(_offPreset)].as<int>())); m_onPreset = max(0,min(250,(int)m_onPreset));
}
if (top[FPSTR(_nightTime)] != nullptr) { m_offPreset = top[FPSTR(_offPreset)] | m_offPreset;
if (top[FPSTR(_nightTime)].is<bool>()) { m_offPreset = max(0,min(250,(int)m_offPreset));
m_nightTimeOnly = top[FPSTR(_nightTime)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_nightTime)]; // checkbox -> off or on
m_nightTimeOnly = (bool)(str!="off"); // off is guaranteed to be present
}
}
if (top[FPSTR(_mqttOnly)] != nullptr) { m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly;
if (top[FPSTR(_mqttOnly)].is<bool>()) { m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly;
m_mqttOnly = top[FPSTR(_mqttOnly)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_mqttOnly)]; // checkbox -> off or on
m_mqttOnly = (bool)(str!="off"); // off is guaranteed to be present
}
}
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// reading config prior to setup() // reading config prior to setup()
DEBUG_PRINTLN(F("PIR config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
if (oldPin != PIRsensorPin || oldEnabled != enabled) { if (oldPin != PIRsensorPin || oldEnabled != enabled) {
// check if pin is OK // check if pin is OK
@ -395,10 +372,10 @@ public:
if (enabled) { if (enabled) {
sensorPinState = digitalRead(PIRsensorPin); sensorPinState = digitalRead(PIRsensorPin);
} }
DEBUG_PRINTLN(F("PIR config (re)loaded."));
} }
DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }

View File

@ -173,31 +173,22 @@ public:
{ {
// we look for JSON object. // we look for JSON object.
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
if (!top.isNull()) DEBUG_PRINT(FPSTR(_name));
{ DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
if (top[FPSTR(_enabled)].is<bool>())
{
disabled = !top[FPSTR(_enabled)].as<bool>();
}
else
{
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
disabled = (bool)(str == "off"); // off is guaranteed to be present
};
readingInterval = min(120, max(10, top[FPSTR(_readInterval)].as<int>())) * 1000; // convert to ms
referenceVoltage = top[FPSTR(_referenceVoltage)].as<float>();
resistorValue = top[FPSTR(_resistorValue)].as<float>();
adcPrecision = top[FPSTR(_adcPrecision)].as<float>();
offset = top[FPSTR(_offset)].as<int>();
DEBUG_PRINTLN(F("Photoresistor config (re)loaded."));
}
else
{
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
return false; return false;
} }
disabled = !(top[FPSTR(_enabled)] | !disabled);
readingInterval = (top[FPSTR(_readInterval)] | readingInterval/1000) * 1000; // convert to ms
referenceVoltage = top[FPSTR(_referenceVoltage)] | referenceVoltage;
resistorValue = top[FPSTR(_resistorValue)] | resistorValue;
adcPrecision = top[FPSTR(_adcPrecision)] | adcPrecision;
offset = top[FPSTR(_offset)] | offset;
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(" config (re)loaded."));
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }
}; };

View File

@ -42,7 +42,7 @@ class UsermodTemperature : public Usermod {
bool waitingForConversion = false; bool waitingForConversion = false;
// flag set at startup if DS18B20 sensor not found, avoids trying to keep getting // flag set at startup if DS18B20 sensor not found, avoids trying to keep getting
// temperature if flashed to a board without a sensor attached // temperature if flashed to a board without a sensor attached
bool disabled = false; bool enabled = true;
// strings to reduce flash memory usage (used more than twice) // strings to reduce flash memory usage (used more than twice)
static const char _name[]; static const char _name[];
@ -110,23 +110,23 @@ class UsermodTemperature : public Usermod {
// pin retrieved from cfg.json (readFromConfig()) prior to running setup() // pin retrieved from cfg.json (readFromConfig()) prior to running setup()
if (!pinManager.allocatePin(temperaturePin,false)) { if (!pinManager.allocatePin(temperaturePin,false)) {
temperaturePin = -1; // allocation failed temperaturePin = -1; // allocation failed
disabled = true; enabled = false;
DEBUG_PRINTLN(F("Temperature pin allocation failed.")); DEBUG_PRINTLN(F("Temperature pin allocation failed."));
} else { } else {
if (!disabled) { if (enabled) {
// config says we are enabled // config says we are enabled
oneWire = new OneWire(temperaturePin); oneWire = new OneWire(temperaturePin);
if (!oneWire->reset()) if (!oneWire->reset())
disabled = true; // resetting 1-Wire bus yielded an error enabled = false; // resetting 1-Wire bus yielded an error
else else
while ((disabled=!findSensor()) && retries--) delay(25); // try to find sensor while ((enabled=findSensor()) && retries--) delay(25); // try to find sensor
} }
} }
initDone = true; initDone = true;
} }
void loop() { void loop() {
if (disabled || strip.isUpdating()) return; if (!enabled || strip.isUpdating()) return;
unsigned long now = millis(); unsigned long now = millis();
@ -181,7 +181,7 @@ class UsermodTemperature : public Usermod {
*/ */
void addToJsonInfo(JsonObject& root) { void addToJsonInfo(JsonObject& root) {
// dont add temperature to info if we are disabled // dont add temperature to info if we are disabled
if (disabled) return; if (!enabled) return;
JsonObject user = root["u"]; JsonObject user = root["u"];
if (user.isNull()) user = root.createNestedObject("u"); if (user.isNull()) user = root.createNestedObject("u");
@ -223,7 +223,7 @@ class UsermodTemperature : public Usermod {
void addToConfig(JsonObject &root) { void addToConfig(JsonObject &root) {
// we add JSON object: {"Temperature": {"pin": 0, "degC": true}} // we add JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = !disabled; top[FPSTR(_enabled)] = enabled;
top["pin"] = temperaturePin; // usermodparam top["pin"] = temperaturePin; // usermodparam
top["degC"] = degC; // usermodparam top["degC"] = degC; // usermodparam
top[FPSTR(_readInterval)] = readingInterval / 1000; top[FPSTR(_readInterval)] = readingInterval / 1000;
@ -233,57 +233,34 @@ class UsermodTemperature : public Usermod {
/** /**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject &root) { bool readFromConfig(JsonObject &root) {
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}} // we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root[FPSTR(_name)];
int8_t newTemperaturePin = temperaturePin; int8_t newTemperaturePin = temperaturePin;
if (top.isNull()) return true;
bool configComplete = true; JsonObject top = root[FPSTR(_name)];
if (top["pin"] != nullptr) { if (top.isNull()) {
if (top[FPSTR(_enabled)].is<bool>()) { DEBUG_PRINT(FPSTR(_name));
disabled = !top[FPSTR(_enabled)].as<bool>(); DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
} else { return false;
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
disabled = (bool)(str=="off"); // off is guaranteed to be present
}
newTemperaturePin = min(39,max(-1,top["pin"].as<int>()));
} }
if (top["degC"] != nullptr) { enabled = top[FPSTR(_enabled)] | enabled;
if (top["degC"].is<bool>()) { newTemperaturePin = top["pin"] | newTemperaturePin;
// reading from cfg.json // newTemperaturePin = min(33,max(-1,(int)newTemperaturePin)); // bounds check
degC = top["degC"].as<bool>(); degC = top["degC"] | degC;
} else { readingInterval = top[FPSTR(_readInterval)] | readingInterval/1000;
// new configuration from set.cpp readingInterval = min(120,max(10,(int)readingInterval)) * 1000; // convert to ms
String str = top["degC"]; // checkbox -> off or on parasite = top[FPSTR(_parasite)] | parasite;
degC = (bool)(str!="off"); // off is guaranteed to be present
}
}
if (top[FPSTR(_readInterval)] != nullptr) {
readingInterval = min(120,max(10,top[FPSTR(_readInterval)].as<int>())) * 1000; // convert to ms
}
if (top[FPSTR(_parasite)] != nullptr) {
if (top[FPSTR(_parasite)].is<bool>()) {
// reading from cfg.json
parasite = top[FPSTR(_parasite)].as<bool>();
} else {
// new configuration from set.cpp
String str = top[FPSTR(_parasite)]; // checkbox -> off or on
parasite = (bool)(str!="off"); // off is guaranteed to be present
}
}
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
temperaturePin = newTemperaturePin; temperaturePin = newTemperaturePin;
DEBUG_PRINTLN(F("Temperature config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
DEBUG_PRINTLN(F("Temperature config re-loaded."));
configComplete = false;
// changing paramters from settings page // changing paramters from settings page
if (newTemperaturePin != temperaturePin) { if (newTemperaturePin != temperaturePin) {
// deallocate pin and release memory // deallocate pin and release memory
@ -293,8 +270,10 @@ class UsermodTemperature : public Usermod {
// initialise // initialise
setup(); setup();
} }
DEBUG_PRINTLN(F(" config (re)loaded."));
} }
return configComplete; // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
} }
uint16_t getId() uint16_t getId()

View File

@ -347,55 +347,36 @@ class MultiRelay : public Usermod {
/** /**
* restore the changeable values * restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json * readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject &root) { bool readFromConfig(JsonObject &root) {
int8_t oldPin[MULTI_RELAY_MAX_RELAYS]; int8_t oldPin[MULTI_RELAY_MAX_RELAYS];
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) return false; if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
if (top[FPSTR(_enabled)] != nullptr) { enabled = top[FPSTR(_enabled)] | enabled;
if (top[FPSTR(_enabled)].is<bool>()) {
enabled = top[FPSTR(_enabled)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
}
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) { for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-"; String parName = FPSTR(_relay_str); parName += "-"; parName += i; parName += "-";
oldPin[i] = _relay[i].pin; oldPin[i] = _relay[i].pin;
if (top[parName+"pin"] != nullptr) _relay[i].pin = min(39,max(-1,top[parName+"pin"].as<int>())); _relay[i].pin = top[parName+"pin"] | _relay[i].pin;
// _relay[i].pin = min(39,max(-1,(int)_relay[i].pin)); // bounds checking
if (top[parName+FPSTR(_activeHigh)] != nullptr) { _relay[i].mode = top[parName+FPSTR(_activeHigh)] | _relay[i].mode;
if (top[parName+FPSTR(_activeHigh)].is<bool>()) { _relay[i].external = top[parName+FPSTR(_external)] | _relay[i].external;
_relay[i].mode = top[parName+FPSTR(_activeHigh)].as<bool>(); // reading from cfg.json _relay[i].delay = top[parName+FPSTR(_delay_str)] | _relay[i].delay;
} else { _relay[i].delay = min(600,max(0,abs((int)_relay[i].delay))); // bounds checking max 10min
// change from settings page
String str = top[parName+FPSTR(_activeHigh)]; // checkbox -> off or on
_relay[i].mode = (bool)(str!="off"); // off is guaranteed to be present
}
}
if (top[parName+FPSTR(_external)] != nullptr) {
if (top[parName+FPSTR(_external)].is<bool>()) {
_relay[i].external = top[parName+FPSTR(_external)].as<bool>(); // reading from cfg.json
} else {
// change from settings page
String str = top[parName+FPSTR(_external)]; // checkbox -> off or on
_relay[i].external = (bool)(str!="off"); // off is guaranteed to be present
}
}
_relay[i].delay = min(600,max(0,abs(top[parName+FPSTR(_delay_str)].as<int>())));
} }
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// reading config prior to setup() // reading config prior to setup()
DEBUG_PRINTLN(F("MultiRelay config loaded.")); DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
// deallocate all pins 1st // deallocate all pins 1st
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
@ -411,8 +392,9 @@ class MultiRelay : public Usermod {
} }
_relay[i].active = false; _relay[i].active = false;
} }
DEBUG_PRINTLN(F("MultiRelay config (re)loaded.")); DEBUG_PRINTLN(F(" config (re)loaded."));
} }
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true; return true;
} }

View File

@ -33,12 +33,12 @@ class AutoSaveUsermod : public Usermod {
bool enabled = true; bool enabled = true;
// configurable parameters // configurable parameters
unsigned long autoSaveAfterSec = 15; // 15s by default uint16_t autoSaveAfterSec = 15; // 15s by default
uint8_t autoSavePreset = 250; // last possible preset uint8_t autoSavePreset = 250; // last possible preset
bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot?
// If we've detected the need to auto save, this will be non zero. // If we've detected the need to auto save, this will be non zero.
unsigned long autoSaveAfter = 0; uint16_t autoSaveAfter = 0;
uint8_t knownBrightness = 0; uint8_t knownBrightness = 0;
uint8_t knownEffectSpeed = 0; uint8_t knownEffectSpeed = 0;
@ -197,36 +197,28 @@ class AutoSaveUsermod : public Usermod {
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
* but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup.
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :) * If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/ */
bool readFromConfig(JsonObject& root) { bool readFromConfig(JsonObject& root) {
// we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} // we look for JSON object: {"Autosave": {"enabled": true, "autoSaveAfterSec": 10, "autoSavePreset": 250, ...}}
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (top.isNull()) { if (top.isNull()) {
DEBUG_PRINTLN(F("No config found. (Using defaults.)")); DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false; return false;
} }
bool configComplete = true; enabled = top[FPSTR(_autoSaveEnabled)] | enabled;
autoSaveAfterSec = top[FPSTR(_autoSaveAfterSec)] | autoSaveAfterSec;
autoSaveAfterSec = (uint16_t) min(3600,max(10,(int)autoSaveAfterSec)); // bounds checking
autoSavePreset = top[FPSTR(_autoSavePreset)] | autoSavePreset;
autoSavePreset = (uint8_t) min(250,max(100,(int)autoSavePreset)); // bounds checking
applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)] | applyAutoSaveOnBoot;
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(" config (re)loaded."));
if (top[FPSTR(_autoSaveEnabled)].is<bool>()) { // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
// reading from cfg.json
enabled = top[FPSTR(_autoSaveEnabled)].as<bool>();
} else {
// reading from POST message
String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on
enabled = (bool)(str!="off"); // off is guaranteed to be present
}
autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as<int>()));
autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as<int>()));
if (top[FPSTR(_autoSaveApplyOnBoot)].is<bool>()) {
// reading from cfg.json
applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as<bool>();
} else {
// reading from POST message
String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on
applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present
}
DEBUG_PRINTLN(F("Autosave config (re)loaded."));
return true; return true;
} }

View File

@ -623,48 +623,30 @@ class FourLineDisplayUsermod : public Usermod {
int8_t newScl = sclPin; int8_t newScl = sclPin;
int8_t newSda = sdaPin; int8_t newSda = sdaPin;
bool configComplete = true;
JsonObject top = root[FPSTR(_name)]; JsonObject top = root[FPSTR(_name)];
if (!top.isNull() && top["pin"] != nullptr) { if (top.isNull()) {
newScl = top["pin"][0]; DEBUG_PRINT(FPSTR(_name));
newSda = top["pin"][1]; DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
newType = top["type"]; return false;
if (top[FPSTR(_flip)].is<bool>()) {
flip = top[FPSTR(_flip)].as<bool>();
} else {
String str = top[FPSTR(_flip)]; // checkbox -> off or on
flip = (bool)(str!="off"); // off is guaranteed to be present
needsRedraw |= true;
}
contrast = top[FPSTR(_contrast)].as<int>();
refreshRate = top[FPSTR(_refreshRate)].as<int>() * 1000;
screenTimeout = top[FPSTR(_screenTimeOut)].as<int>() * 1000;
if (top[FPSTR(_sleepMode)].is<bool>()) {
sleepMode = top[FPSTR(_sleepMode)].as<bool>();
} else {
String str = top[FPSTR(_sleepMode)]; // checkbox -> off or on
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
needsRedraw |= true;
}
if (top[FPSTR(_clockMode)].is<bool>()) {
clockMode = top[FPSTR(_clockMode)].as<bool>();
} else {
String str = top[FPSTR(_clockMode)]; // checkbox -> off or on
clockMode = (bool)(str!="off"); // off is guaranteed to be present
needsRedraw |= true;
}
DEBUG_PRINTLN(F("4 Line Display config (re)loaded."));
} else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
configComplete = false;
} }
newScl = top["pin"][0] | newScl;
newSda = top["pin"][1] | newSda;
newType = top["type"] | newType;
flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast;
refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/1000) * 1000;
screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000;
sleepMode = top[FPSTR(_sleepMode)] | sleepMode;
clockMode = top[FPSTR(_clockMode)] | clockMode;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) { if (!initDone) {
// first run: reading from cfg.json // first run: reading from cfg.json
sclPin = newScl; sclPin = newScl;
sdaPin = newSda; sdaPin = newSda;
type = newType; type = newType;
DEBUG_PRINTLN(F(" config loaded."));
} else { } else {
// changing paramters from settings page // changing paramters from settings page
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) { if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
@ -675,17 +657,18 @@ class FourLineDisplayUsermod : public Usermod {
sdaPin = newSda; sdaPin = newSda;
if (newScl<0 || newSda<0) { if (newScl<0 || newSda<0) {
type = NONE; type = NONE;
return configComplete; return true;
} else } else type = newType;
type = newType;
setup(); setup();
needsRedraw |= true; needsRedraw |= true;
} }
setContrast(contrast); setContrast(contrast);
setFlipMode(flip); setFlipMode(flip);
if (needsRedraw && !wakeDisplay()) redraw(true); if (needsRedraw && !wakeDisplay()) redraw(true);
DEBUG_PRINTLN(F(" config (re)loaded."));
} }
return configComplete; // use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return true;
} }
/* /*

View File

@ -65,7 +65,7 @@
if (isO(o)) { if (isO(o)) {
for (const [s,v] of Object.entries(o)) { for (const [s,v] of Object.entries(o)) {
// possibility to nest objects (only 1 level) // possibility to nest objects (only 1 level)
if (f!=='unknown' && !k.includes("_")) addField(k+"_"+f,s,v); if (f!=='unknown' && !k.includes(":")) addField(k+":"+f,s,v);
else addField(k,s,v); else addField(k,s,v);
} }
} else if (Array.isArray(o)) { } else if (Array.isArray(o)) {
@ -76,28 +76,31 @@
var t,c; var t,c;
switch (typeof o) { switch (typeof o) {
case "boolean": case "boolean":
t = "checkbox"; c = o ? `checked value="on"` : ""; break; t = "checkbox"; c = o ? `checked value="true"` : "";
break;
case "number": case "number":
c = `value="${o}"`;
if (isF(o)) { if (isF(o)) {
c = `value="${parseFloat(o)}" step="0.01"`; c += ` step="0.01" class="xxl"`;
t = "float"; t = "float";
} else { } else {
c = `value="${parseInt(o,10)}"`; if (f.substr(-3)==="pin") c += ' max="39" min="-1" class="small"';
if (f==="pin") c += ' max="39" min="-1"'; else c += ` class="big"`;
t = "int"; t = "int";
} }
break; break;
case "string": // case "string":
t = "text"; c = `value="${o}"`; break; // t = "text"; c = `value="${o}" style="width:150px;"`; break;
default: default:
t = "text"; c = `value="${o}"`; break; t = "text"; c = `value="${o}" style="width:150px;"`;
break;
} }
if (k.includes("_")) urows += k.substr(k.indexOf("_")+1); if (k.includes(":")) urows += k.substr(k.indexOf(":")+1);
urows += ` ${f}: `; urows += ` ${f}: `;
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes // https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${f}${a?"[]":""}" value="off">`; if (t=="checkbox") urows += `<input type="hidden" name="${k}:${f}${a?"[]":""}" value="false">`;
else if (!a) urows += `<input type="hidden" name="${k}_${f}${a?"[]":""}" value="${t}">`; else if (!a) urows += `<input type="hidden" name="${k}:${f}${a?"[]":""}" value="${t}">`;
urows += `<input type="${t==="float"||t==="int"?"number":t}" name="${k}_${f}${a?"[]":""}" ${c} oninput="check(this,'${k.substr(k.indexOf("_")+1)}')"><br>`; urows += `<input type="${t==="float"||t==="int"?"number":t}" name="${k}:${f}${a?"[]":""}" ${c} oninput="check(this,'${k.substr(k.indexOf(":")+1)}')"><br>`;
} }
} }
function ldS() { function ldS() {

View File

@ -39,13 +39,19 @@ input {
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
border: 0.5ch solid #333; border: 0.5ch solid #333;
} }
input[type="text"] {
font-size: medium;
}
input[type="number"] { input[type="number"] {
width: 4em; width: 4em;
font-size: medium; font-size: medium;
margin: 2px; margin: 2px;
} }
input[type="number"].xxl {
width: 100px;
}
input[type="number"].big { input[type="number"].big {
width: 70px; width: 85px;
} }
input[type="number"].med { input[type="number"].med {
width: 55px; width: 55px;

View File

@ -6,7 +6,7 @@
*/ */
// Autogenerated from wled00/data/style.css, do not edit!! // Autogenerated from wled00/data/style.css, do not edit!!
const char PAGE_settingsCss[] PROGMEM = R"=====(<style>body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%%;margin:0}hr{border-color:#666}button{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;border-radius:24px;display:inline-block;font-size:20px;margin:12px 8px 8px;padding:8px 12px;min-width:48px;cursor:pointer}.toprow{top:0;position:sticky;background-color:#222;z-index:1}.helpB{text-align:left;position:absolute;width:60px}input{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.5ch solid #333}input[type=number]{width:4em;font-size:medium;margin:2px}input[type=number].big{width:70px}input[type=number].med{width:55px}input[type=number].small{width:40px}select{margin:2px;font-size:medium}input[type=checkbox]{-ms-transform:scale(2);-moz-transform:scale(2);-webkit-transform:scale(2);-o-transform:scale(2);transform:scale(2);margin-right:10px}select{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.5ch solid #333}td{padding:2px}.d5{width:4.5em!important}#toast{opacity:0;background-color:#444;border-radius:5px;bottom:64px;color:#fff;font-size:17px;padding:16px;pointer-events:none;position:fixed;text-align:center;z-index:5;transform:translateX(-50%%);max-width:90%%;left:50%%}#toast.show{opacity:1;background-color:#264;animation:fadein .5s,fadein .5s 2.5s reverse}#toast.error{opacity:1;background-color:#b21;animation:fadein .5s}</style>)====="; const char PAGE_settingsCss[] PROGMEM = R"=====(<style>body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%%;margin:0}hr{border-color:#666}button{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;border-radius:24px;display:inline-block;font-size:20px;margin:12px 8px 8px;padding:8px 12px;min-width:48px;cursor:pointer}.toprow{top:0;position:sticky;background-color:#222;z-index:1}.helpB{text-align:left;position:absolute;width:60px}input{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.5ch solid #333}input[type=text]{font-size:medium}input[type=number]{width:4em;font-size:medium;margin:2px}input[type=number].xxl{width:100px}input[type=number].big{width:85px}input[type=number].med{width:55px}input[type=number].small{width:40px}select{margin:2px;font-size:medium}input[type=checkbox]{-ms-transform:scale(2);-moz-transform:scale(2);-webkit-transform:scale(2);-o-transform:scale(2);transform:scale(2);margin-right:10px}select{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.5ch solid #333}td{padding:2px}.d5{width:4.5em!important}#toast{opacity:0;background-color:#444;border-radius:5px;bottom:64px;color:#fff;font-size:17px;padding:16px;pointer-events:none;position:fixed;text-align:center;z-index:5;transform:translateX(-50%%);max-width:90%%;left:50%%}#toast.show{opacity:1;background-color:#264;animation:fadein .5s,fadein .5s 2.5s reverse}#toast.error{opacity:1;background-color:#b21;animation:fadein .5s}</style>)=====";
// Autogenerated from wled00/data/settings.htm, do not edit!! // Autogenerated from wled00/data/settings.htm, do not edit!!
@ -408,7 +408,7 @@ type="submit">Save & Reboot</button></form></body></html>)=====";
// Autogenerated from wled00/data/settings_um.htm, do not edit!! // Autogenerated from wled00/data/settings_um.htm, do not edit!!
const char PAGE_settings_um[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta const char PAGE_settings_um[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta
name="viewport" content="width=500"><title>Usermod Settings</title><script> name="viewport" content="width=500"><title>Usermod Settings</title><script>
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["rsvd","rsvd","rsvd","rsvd","rsvd","rsvd"],loc=!1,numM=0;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),GetV(),numM>0||locip?ldS():gId("um").innerHTML="No Usermods installed."}function isF(e){return e===+e&&e!==(0|e)}function isI(e){return e===+e&&e===(0|e)}function check(e,n){var i=e.name.replace("[]","").substr(-3);if("number"==e.type&&"pin"==i.substr(0,3))for(var o=0;o<pins.length;o++)if(n!=pinO[o]){if(e.value==pins[o]||e.value<-1||e.value>39){e.style.color="red";break}e.style.color=e.value>33?"orange":"#fff"}}function getPins(e){if(isO(e))for(const[i,o]of Object.entries(e))if(isO(o))owner=i,getPins(o);else if("pin"==i.replace("[]","").substr(-3))if(Array.isArray(o))for(var n=0;n<o.length;n++)o[n]>=0&&(pins.push(o[n]),pinO.push(owner));else o>=0&&(pins.push(o),pinO.push(owner));else if(Array.isArray(o))for(n=0;n<o.length;n++)getPins(o[n])}function addField(e,n,i,o=!1){if(isO(i))for(const[o,t]of Object.entries(i))"unknown"===n||e.includes("_")?addField(e,o,t):addField(e+"_"+n,o,t);else if(Array.isArray(i))for(var t=0;t<i.length;t++)addField(e,n,i[t],!0);else{var s,r;switch(typeof i){case"boolean":s="checkbox",r=i?'checked value="on"':"";break;case"number":isF(i)?(r=`value="${parseFloat(i)}" step="0.01"`,s="float"):(r=`value="${parseInt(i,10)}"`,"pin"===n&&(r+=' max="39" min="-1"'),s="int");break;case"string":default:s="text",r=`value="${i}"`}e.includes("_")&&(urows+=e.substr(e.indexOf("_")+1)),urows+=` ${n}: `,"checkbox"==s?urows+=`<input type="hidden" name="${e}_${n}${o?"[]":""}" value="off">`:o||(urows+=`<input type="hidden" name="${e}_${n}${o?"[]":""}" value="${s}">`),urows+=`<input type="${"float"===s||"int"===s?"number":s}" name="${e}_${n}${o?"[]":""}" ${r} oninput="check(this,'${e.substr(e.indexOf("_")+1)}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg))for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);""===urows&&(urows="Usermods configuration not found.<br>Press <i>Save</i> to initialize defaults."),gId("um").innerHTML=urows}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV() {var d=document; var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["rsvd","rsvd","rsvd","rsvd","rsvd","rsvd"],loc=!1,numM=0;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),GetV(),numM>0||locip?ldS():gId("um").innerHTML="No Usermods installed."}function isF(e){return e===+e&&e!==(0|e)}function isI(e){return e===+e&&e===(0|e)}function check(e,n){var i=e.name.replace("[]","").substr(-3);if("number"==e.type&&"pin"==i.substr(0,3))for(var s=0;s<pins.length;s++)if(n!=pinO[s]){if(e.value==pins[s]||e.value<-1||e.value>39){e.style.color="red";break}e.style.color=e.value>33?"orange":"#fff"}}function getPins(e){if(isO(e))for(const[i,s]of Object.entries(e))if(isO(s))owner=i,getPins(s);else if("pin"==i.replace("[]","").substr(-3))if(Array.isArray(s))for(var n=0;n<s.length;n++)s[n]>=0&&(pins.push(s[n]),pinO.push(owner));else s>=0&&(pins.push(s),pinO.push(owner));else if(Array.isArray(s))for(n=0;n<s.length;n++)getPins(s[n])}function addField(e,n,i,s=!1){if(isO(i))for(const[s,t]of Object.entries(i))"unknown"===n||e.includes(":")?addField(e,s,t):addField(e+":"+n,s,t);else if(Array.isArray(i))for(var t=0;t<i.length;t++)addField(e,n,i[t],!0);else{var o,r;switch(typeof i){case"boolean":o="checkbox",r=i?'checked value="true"':"";break;case"number":r=`value="${i}"`,isF(i)?(r+=' step="0.01" class="xxl"',o="float"):("pin"===n.substr(-3)?r+=' max="39" min="-1" class="small"':r+=' class="big"',o="int");break;default:o="text",r=`value="${i}" style="width:150px;"`}e.includes(":")&&(urows+=e.substr(e.indexOf(":")+1)),urows+=` ${n}: `,"checkbox"==o?urows+=`<input type="hidden" name="${e}:${n}${s?"[]":""}" value="false">`:s||(urows+=`<input type="hidden" name="${e}:${n}${s?"[]":""}" value="${o}">`),urows+=`<input type="${"float"===o||"int"===o?"number":o}" name="${e}:${n}${s?"[]":""}" ${r} oninput="check(this,'${e.substr(e.indexOf(":")+1)}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg))for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);""===urows&&(urows="Usermods configuration not found.<br>Press <i>Save</i> to initialize defaults."),gId("um").innerHTML=urows}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV() {var d=document;
%CSS%%SCSS%<link href="/skin.css" %CSS%%SCSS%<link href="/skin.css"
rel="stylesheet"></head><body onload="S()"><form id="form_s" name="Sf" rel="stylesheet"></head><body onload="S()"><form id="form_s" name="Sf"
method="post" onsubmit="svS(event)"><div class="toprow"><div class="helpB"> method="post" onsubmit="svS(event)"><div class="toprow"><div class="helpB">

View File

@ -430,8 +430,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
String value = request->arg(i); String value = request->arg(i);
// POST request parameters are combined as <usermodname>_<usermodparameter> // POST request parameters are combined as <usermodname>_<usermodparameter>
uint8_t umNameEnd = name.indexOf("_"); int umNameEnd = name.indexOf(":");
if (umNameEnd<1) break; // parameter does not contain "_" or on 1st place -> wrong if (umNameEnd<1) break; // parameter does not contain ":" or on 1st place -> wrong
JsonObject mod = um[name.substring(0,umNameEnd)]; // get a usermod JSON object JsonObject mod = um[name.substring(0,umNameEnd)]; // get a usermod JSON object
if (mod.isNull()) { if (mod.isNull()) {
@ -441,17 +441,19 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINT(":"); DEBUG_PRINT(":");
name = name.substring(umNameEnd+1); // remove mod name from string name = name.substring(umNameEnd+1); // remove mod name from string
// if the resulting name still contains "_" this means nested object // if the resulting name still contains ":" this means nested object
JsonObject subObj; JsonObject subObj;
uint8_t umSubObj = name.indexOf("_"); int umSubObj = name.indexOf(":");
DEBUG_PRINTF("(%d):",umSubObj);
if (umSubObj>0) { if (umSubObj>0) {
subObj = mod[name.substring(0,umSubObj-1)]; subObj = mod[name.substring(0,umSubObj)];
if (subObj.isNull()) if (subObj.isNull())
subObj = mod.createNestedObject(name.substring(0,umSubObj-1)); subObj = mod.createNestedObject(name.substring(0,umSubObj));
name = name.substring(umSubObj+1); // remove nested object name from string name = name.substring(umSubObj+1); // remove nested object name from string
} else { } else {
subObj = mod; subObj = mod;
} }
DEBUG_PRINT(name);
// check if parameters represent array // check if parameters represent array
if (name.endsWith("[]")) { if (name.endsWith("[]")) {
@ -464,7 +466,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
subObj[name].add(value.toInt()); subObj[name].add(value.toInt());
j++; j++;
} }
DEBUG_PRINT(name);
DEBUG_PRINT("["); DEBUG_PRINT("[");
DEBUG_PRINT(j); DEBUG_PRINT(j);
DEBUG_PRINT("] = "); DEBUG_PRINT("] = ");
@ -472,10 +473,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
} else { } else {
// we are using a hidden field with the same name as our parameter (!before the actual parameter!) // 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 patameters the first field contains "off" // to describe the type of parameter (text,float,int), for boolean patameters the first field contains "off"
// so checkboxes have one or two fields (first is always "off", existence of second depends on checkmark and may be "on") // 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()) { if (subObj[name].isNull()) {
// the first occurence of the field describes the parameter type (used in next loop) // the first occurence of the field describes the parameter type (used in next loop)
if (value == "off") subObj[name] = false; // checkboxes may have only one field if (value == "false") subObj[name] = false; // checkboxes may have only one field
else subObj[name] = value; else subObj[name] = value;
} else { } else {
String type = subObj[name].as<String>(); // get previously stored value as a type String type = subObj[name].as<String>(); // get previously stored value as a type
@ -484,11 +485,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
else if (type == "int") subObj[name] = value.toInt(); else if (type == "int") subObj[name] = value.toInt();
else subObj[name] = value; // text fields else subObj[name] = value; // text fields
} }
DEBUG_PRINT(name);
DEBUG_PRINT(" = "); DEBUG_PRINT(" = ");
DEBUG_PRINTLN(value); DEBUG_PRINTLN(value);
} }
} }
#ifdef WLED_DEBUG
serializeJson(um,Serial);
#endif
usermods.readFromConfig(um); // force change of usermod parameters usermods.readFromConfig(um); // force change of usermod parameters
} }