1st working usermod settings (Temperature).

Added color on pin conflicts in LEDs setting page.
This commit is contained in:
Blaz Kristan 2021-04-10 00:17:15 +02:00
parent 9a6d709082
commit 13b3b2fd23
9 changed files with 231 additions and 39 deletions

View File

@ -333,6 +333,18 @@ const char PAGE_settings_dmx[] PROGMEM = R"=====()=====";
"function GetV() {var d=document;\n"
),
},
{
file: "settings_um.htm",
name: "PAGE_settings_um",
prepend: "=====(",
append: ")=====",
method: "plaintext",
filter: "html-minify",
mangle: (str) =>
str
.replace(/\<link rel="stylesheet".*\>/gms, "")
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
}
],
"wled00/html_settings.h"
);

View File

@ -173,45 +173,48 @@ class UsermodTemperature : public Usermod {
/**
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
* Add "pin_Temperature" to json state. This can be used to check which GPIO pin usermod uses.
* Add "<usermodename>_<usermodparam>" to json state. This can be used to check which GPIO pin usermod uses.
*/
void addToJsonState(JsonObject &root)
{
root[F("pin_Temperature")] = temperaturePin;
root[F("mode_Temperature")] = degC ? ("C") : ("F");
//root[F("Temperature_pin")] = temperaturePin;
//root[F("Temperature_degC")] = degC ? ("C") : ("F");
}
/**
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
* Read "pin_Temperature" from json state and and change GPIO pin used.
* Read "<usermodname>_<usermodparam>" from json state and and change settings (i.e. GPIO pin) used.
*/
void readFromJsonState(JsonObject &root) {
if (root[F("pin_Temperature")] != nullptr) {
int8_t pin = (int)root[F("pin_Temperature")];
// deallocate pin and release memory
pinManager.deallocatePin(temperaturePin);
delete sensor;
delete oneWire;
// disable usermod
temperaturePin = -1;
disabled = true;
// check if pin is OK
if (pin>=0 && pinManager.allocatePin(pin,false)) {
// allocat memory
oneWire = new OneWire(pin);
sensor = new DallasTemperature(oneWire);
if (sensor) {
temperaturePin = pin;
sensor->begin();
disabled = !sensor->getAddress(sensorDeviceAddress, 0);
} else {
pinManager.deallocatePin(pin);
if (root[F("Temperature_pin")] != nullptr) {
int8_t pin = (int)root[F("Temperature_pin")];
if (pin != temperaturePin) {
// deallocate pin and release memory
delete sensor;
delete oneWire;
pinManager.deallocatePin(temperaturePin);
// disable usermod
temperaturePin = -1;
disabled = true;
// check if pin is OK
if (pin>=0 && pinManager.allocatePin(pin,false)) {
// allocat memory
oneWire = new OneWire(pin);
sensor = new DallasTemperature(oneWire);
if (sensor) {
temperaturePin = pin;
sensor->begin();
disabled = !sensor->getAddress(sensorDeviceAddress, 0);
} else {
pinManager.deallocatePin(pin);
}
}
}
}
if (root[F("mode_Temperature")] != nullptr) {
degC = (root[F("mode_Temperature")]==String(PSTR("C")));
if (root[F("Temperature_degC")] != nullptr) {
String strDegC = root[F("Temperature_degC")]; // checkbox -> off or on
degC = (bool) (strDegC!="off"); // off is guaranteed to be present
}
}
@ -220,9 +223,9 @@ class UsermodTemperature : public Usermod {
*/
void addToConfig(JsonObject &root) {
// we add JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root.createNestedObject(F("Temperature"));
top[F("pin")] = temperaturePin;
top[F("degC")] = degC;
JsonObject top = root.createNestedObject(F("Temperature")); // usermodname
top["pin"] = temperaturePin; // usermodparam
top[F("degC")] = degC; // usermodparam
}
/**
@ -231,8 +234,8 @@ class UsermodTemperature : public Usermod {
void readFromConfig(JsonObject &root) {
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
JsonObject top = root[F("Temperature")];
if (!top.isNull() && top[F("pin")] != nullptr) {
temperaturePin = (int)top[F("pin")];
if (!top.isNull() && top["pin"] != nullptr) {
temperaturePin = (int)top["pin"];
degC = top[F("degC")] != nullptr ? top[F("degC")] : true;
} else {
DEBUG_PRINTLN(F("No Temperature sensor config found. (Using defaults.)"));

View File

@ -10,7 +10,7 @@
margin: 0;
}
html {
--h: 11.55vh;
--h: 10.55vh;
}
button {
background: #333;
@ -18,7 +18,7 @@
font-family: Verdana, Helvetica, sans-serif;
border: 0.3ch solid #333;
display: inline-block;
font-size: 8vmin;
font-size: 6vmin;
height: var(--h);
width: 95%;
margin-top: 2.4vh;
@ -41,6 +41,7 @@
<form action="/settings/ui"><button type="submit">User Interface</button></form>
<form action="/settings/sync"><button type="submit">Sync Interfaces</button></form>
<form action="/settings/time"><button type="submit">Time & Macros</button></form>
<form action="/settings/um"><button type="submit">Usermods</button></form>
<form action="/settings/sec"><button type="submit">Security & Updates</button></form>
</body>
</html>

View File

@ -163,6 +163,18 @@
var lc=d.getElementsByName("LC"+LCs[i].name.substring(2))[0];
lc.max=maxPB;
}
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR" || nm=="AX")
if (LCs[i].value!="" && LCs[i].value!="-1") {
var p = [];
if (d.um_p && Array.isArray(d.um_p)) for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]);
for (j=0; j<LCs.length; j++) {
if (i==j) continue;
var n2 = LCs[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4" || n2=="RL" || n2=="BT" || n2=="IR" || n2=="AX")
if (LCs[j].value!="" && LCs[j].value!="-1") p.push(parseInt(LCs[j].value,10));
}
if (p.some((e)=>e==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color="#fff";
}
}
if (d.getElementById("LC").readOnly) d.getElementsByName("LC")[0].value = sLC;

125
wled00/data/settings_um.htm Normal file
View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=500">
<title>UI Settings</title>
<script>
var d = document;
var umCfg = {};
var pins = [6,7,8,9,10,11];
var pinO = ["reserved","reserved","reserved","reserved","reserved","reserved"], owner;
var loc = false, locip;
function gId(s) { return d.getElementById(s); }
function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); }
function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); }
function B() { window.open("/settings","_self"); }
function S() {
if (window.location.protocol == "file:") {
loc = true;
locip = localStorage.getItem('locIp');
if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip);
}
}
ldS();
}
function check(o,k) {
if (o.type=="number" && o.name.substr(-4)=="_pin") {
for (var i=0; i<pins.length; i++) {
if (k==pinO[i]) continue;
if (o.value==pins[i]) { o.style.color="red"; break; } else o.style.color="#fff";
}
}
}
function getPins(o) {
if (isO(o)) {
for (const [k,v] of Object.entries(o)) {
if (isO(v)) {
owner = k;
getPins(v);
continue;
}
if (k=="pin") {
if (Array.isArray(v)) {
for (var i=0; i<v.length; i++) if (v[i]>=0) { pins.push(v[i]); pinO.push(owner); }
} else {
if (v>=0) { pins.push(v); pinO.push(owner); }
}
} else if (Array.isArray(v)) {
for (var i=0; i<v.length; i++) getPins(v[i]);
}
}
}
}
function ldS() {
var url = (loc?`http://${locip}`:'') + '/cfg.json';
fetch(url, {
method: 'get'
})
.then(res => {
if (!res.ok) gId('lserr').style.display = "inline";
return res.json();
})
.then(json => {
umCfg = json.um;
getPins(json);
var urows="";
if (isO(umCfg)) {
for (const [k,o] of Object.entries(umCfg)) {
urows += `<hr><h3>${k}</h3>`;
if (isO(o)) {
for (const [s,v] of Object.entries(o)) {
var t,c;
switch (typeof v) {
case "boolean":
t = "checkbox"; c = v ? `checked value="on"` : ""; break;
case "number":
t = "number"; c = `value="${parseInt(v,10)}"`; break;
case "string":
t = "text"; c = `value="${v}"`; break;
default:
t = "text"; c = `value="${v}"`; break;
}
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${s}" value="off">`;
urows += `${s}: <input type="${t}" name="${k}_${s}" ${c} oninput="check(this,'${k}')"><br>`;
}
}
}
gId("um").innerHTML = urows;
}
})
.catch(function (error) {
gId('lserr').style.display = "inline"
console.log(error);
});
}
function svS(e) {
e.preventDefault();
console.log(d.Sf);
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
function GetV() {}
</script>
<style>
@import url("style.css");
</style>
</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post" onsubmit="svS(event)">
<div class="toprow">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><br>
<span id="lssuc" style="color:green; display:none">&#10004; Configuration saved!</span>
<span id="lserr" style="color:red; display:none">&#9888; Could not load configuration.</span><hr>
</div>
<h2>Usermod Setup</h2>
<div id="um">Loading settings...</div>
<hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -32,7 +32,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
{
//0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec 7: DMX
if (subPage <1 || subPage >7) return;
if (subPage <1 || subPage >8) return;
//WIFI SETTINGS
if (subPage == 1)
@ -408,6 +408,26 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
#endif
//USERMODS
if (subPage == 8)
{
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
JsonObject um = doc.createNestedObject(F("um"));
size_t args = request->args();
for (size_t i=0; i<args; i++) {
String name = request->argName(i);
String value = request->arg(i);
um.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on")
um[name] = value;
DEBUG_PRINT(name);
DEBUG_PRINT(" = ");
DEBUG_PRINTLN(value);
}
usermods.readFromJsonState(um);
}
if (subPage != 2 && (subPage != 6 || !doReboot)) serializeConfig(); //do not save if factory reset or LED settings (which are saved after LED re-init)
if (subPage == 4) alexaInit();
}

View File

@ -373,6 +373,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
#ifdef WLED_ENABLE_DMX // include only if DMX is enabled
else if (url.indexOf("dmx") > 0) subPage = 7;
#endif
else if (url.indexOf("um") > 0) subPage = 8;
} else subPage = 255; //welcome page
if (subPage == 1 && wifiLock && otaLock)
@ -394,6 +395,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
case 5: strcpy_P(s, PSTR("Time")); break;
case 6: strcpy_P(s, PSTR("Security")); strcpy_P(s2, PSTR("Rebooting, please wait ~10 seconds...")); break;
case 7: strcpy_P(s, PSTR("DMX")); break;
case 8: strcpy_P(s, PSTR("Usermods")); break;
}
strcat_P(s, PSTR(" settings saved."));
@ -420,6 +422,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
case 5: request->send_P(200, "text/html", PAGE_settings_time, settingsProcessor); break;
case 6: request->send_P(200, "text/html", PAGE_settings_sec , settingsProcessor); break;
case 7: request->send_P(200, "text/html", PAGE_settings_dmx , settingsProcessor); break;
case 8: request->send_P(200, "text/html", PAGE_settings_um , settingsProcessor); break;
case 255: request->send_P(200, "text/html", PAGE_welcome); break;
default: request->send_P(200, "text/html", PAGE_settings , settingsProcessor);
}

View File

@ -274,9 +274,9 @@ void getSettingsJS(byte subPage, char* dest)
} else if (!kv.value().isNull()) {
// element is an JsonObject
JsonObject obj = kv.value();
if (obj[F("pin")] != nullptr) {
if (obj["pin"] != nullptr) {
if (i++) oappend(SET_F(","));
oappendi((int)obj[F("pin")]);
oappendi((int)obj["pin"]);
}
}
}