Added parsing usermod settings parameters.

Added nested object handling in usermod settings parameters.
This commit is contained in:
Blaz Kristan 2021-06-25 23:20:01 +02:00
parent 2424df0d18
commit 81182bb125
4 changed files with 54 additions and 14 deletions

View File

@ -29,7 +29,10 @@
if (numM > 0 || locip) ldS(); if (numM > 0 || locip) ldS();
else gId("um").innerHTML = "No Usermods installed."; else gId("um").innerHTML = "No Usermods installed.";
} }
function check(o,k) { // https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
function isF(n) { return n === +n && n !== (n|0); }
function isI(n) { return n === +n && n === (n|0); }
function check(o,k) { // input object, pin owner key
var n = o.name.replace("[]","").substr(-3); var n = o.name.replace("[]","").substr(-3);
if (o.type=="number" && n.substr(0,3)=="pin") { if (o.type=="number" && n.substr(0,3)=="pin") {
for (var i=0; i<pins.length; i++) { for (var i=0; i<pins.length; i++) {
@ -58,10 +61,12 @@
} }
} }
} }
function addField(k,f,o,a=false) { function addField(k,f,o,a=false) { //key, field, (sub)object, isArray
if (isO(o)) { if (isO(o)) {
for (const [s,v] of Object.entries(o)) { for (const [s,v] of Object.entries(o)) {
addField(k,s,v); // possibility to nest objects (only 1 level)
if (f!=='unknown' && !k.includes("_")) addField(k+"_"+f,s,v);
else addField(k,s,v);
} }
} else if (Array.isArray(o)) { } else if (Array.isArray(o)) {
for (var j=0; j<o.length; j++) { for (var j=0; j<o.length; j++) {
@ -73,15 +78,26 @@
case "boolean": case "boolean":
t = "checkbox"; c = o ? `checked value="on"` : ""; break; t = "checkbox"; c = o ? `checked value="on"` : ""; break;
case "number": case "number":
t = "number"; c = `value="${parseInt(o,10)}"`; break; if (isF(o)) {
c = `value="${parseFloat(o)}" step="0.01"`;
t = "float";
} else {
c = `value="${parseInt(o,10)}"`;
if (f==="pin") c += ' max="39" min="-1"';
t = "int";
}
break;
case "string": case "string":
t = "text"; c = `value="${o}"`; break; t = "text"; c = `value="${o}"`; break;
default: default:
t = "text"; c = `value="${o}"`; break; t = "text"; c = `value="${o}"`; break;
} }
if (k.includes("_")) urows += k.substr(k.indexOf("_")+1);
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="off">`;
urows += `${f}: <input type="${t}" name="${k}_${f}${a?"[]":""}" ${c} oninput="check(this,'${k}')"><br>`; 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>`;
} }
} }
function ldS() { function ldS() {

View File

@ -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 check(e,n){var o=e.name.replace("[]","").substr(-3);if("number"==e.type&&"pin"==o.substr(0,3))for(var i=0;i<pins.length;i++)if(n!=pinO[i]){if(e.value==pins[i]||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[o,i]of Object.entries(e))if(isO(i))owner=o,getPins(i);else if("pin"==o.replace("[]","").substr(-3))if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function addField(e,n,o,i=!1){if(isO(o))for(const[n,i]of Object.entries(o))addField(e,n,i);else if(Array.isArray(o))for(var t=0;t<o.length;t++)addField(e,n,o[t],!0);else{var r,s;switch(typeof o){case"boolean":r="checkbox",s=o?'checked value="on"':"";break;case"number":r="number",s=`value="${parseInt(o,10)}"`;break;case"string":default:r="text",s=`value="${o}"`}"checkbox"==r&&(urows+=`<input type="hidden" name="${e}_${n}${i?"[]":""}" value="off">`),urows+=`${n}: <input type="${r}" name="${e}_${n}${i?"[]":""}" ${s} oninput="check(this,'${e}')"><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 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;
%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

@ -431,7 +431,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// POST request parameters are combined as <usermodname>_<usermodparameter> // POST request parameters are combined as <usermodname>_<usermodparameter>
uint8_t umNameEnd = name.indexOf("_"); uint8_t umNameEnd = name.indexOf("_");
if (!umNameEnd) break; // parameter does not contain "_" -> 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,15 +441,27 @@ 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
JsonObject subObj;
uint8_t umSubObj = name.indexOf("_");
if (umSubObj>0) {
subObj = mod[name.substring(0,umSubObj-1)];
if (subObj.isNull())
subObj = mod.createNestedObject(name.substring(0,umSubObj-1));
name = name.substring(umSubObj+1); // remove nested object name from string
} else {
subObj = mod;
}
// check if parameters represent array // check if parameters represent array
if (name.endsWith("[]")) { if (name.endsWith("[]")) {
name.replace("[]",""); name.replace("[]","");
if (!mod[name].is<JsonArray>()) { if (!subObj[name].is<JsonArray>()) {
JsonArray ar = mod.createNestedArray(name); JsonArray ar = subObj.createNestedArray(name);
ar.add(value); ar.add(value.toInt());
j=0; j=0;
} else { } else {
mod[name].add(value); subObj[name].add(value.toInt());
j++; j++;
} }
DEBUG_PRINT(name); DEBUG_PRINT(name);
@ -458,8 +470,20 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
DEBUG_PRINT("] = "); DEBUG_PRINT("] = ");
DEBUG_PRINTLN(value); DEBUG_PRINTLN(value);
} else { } else {
mod.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on") // we are using a hidden field with the same name as our parameter (!before the actual parameter!)
mod[name] = value; // 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")
if (subObj[name].isNull()) {
// 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
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 == "float") subObj[name] = value.toDouble();
else if (type == "int") subObj[name] = value.toInt();
else subObj[name] = value; // text fields
}
DEBUG_PRINT(name); DEBUG_PRINT(name);
DEBUG_PRINT(" = "); DEBUG_PRINT(" = ");
DEBUG_PRINTLN(value); DEBUG_PRINTLN(value);

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // version code in format yymmddb (b = daily build)
#define VERSION 2106251 #define VERSION 2106260
//uncomment this if you have a "my_config.h" file you'd like to use //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG