Merge branch 'pin-dropdown' into beta-3

This commit is contained in:
Blaz Kristan 2023-06-12 22:22:47 +02:00
commit 5ca8f4a3aa
11 changed files with 1099 additions and 832 deletions

View File

@ -122,6 +122,9 @@
#define I_SS_LPO_3 48 #define I_SS_LPO_3 48
// In the following NeoGammaNullMethod can be replaced with NeoGammaWLEDMethod to perform Gamma correction implicitly
// unfortunately that may apply Gamma correction to pre-calculated palettes which is undesired
/*** ESP8266 Neopixel methods ***/ /*** ESP8266 Neopixel methods ***/
#ifdef ESP8266 #ifdef ESP8266
//RGB //RGB

View File

@ -338,7 +338,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
if (light_gc_col > 1.0f) gammaCorrectCol = true; if (light_gc_col > 1.0f) gammaCorrectCol = true;
else gammaCorrectCol = false; else gammaCorrectCol = false;
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) {
if (gammaCorrectVal != 2.8f) calcGammaTable(gammaCorrectVal); if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
} else { } else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;

View File

@ -302,7 +302,7 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) {
} }
//gamma 2.8 lookup table used for color correction //gamma 2.8 lookup table used for color correction
static byte gammaT[] = { uint8_t NeoGammaWLEDMethod::gammaT[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
@ -320,27 +320,22 @@ static byte gammaT[] = {
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
uint8_t gamma8_cal(uint8_t b, float gamma)
{
return (int)(powf((float)b / 255.0f, gamma) * 255.0f + 0.5f);
}
// re-calculates & fills gamma table // re-calculates & fills gamma table
void calcGammaTable(float gamma) void NeoGammaWLEDMethod::calcGammaTable(float gamma)
{ {
for (uint16_t i = 0; i < 256; i++) { for (size_t i = 0; i < 256; i++) {
gammaT[i] = gamma8_cal(i, gamma); gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f);
} }
} }
// used for individual channel or brightness gamma correction uint8_t NeoGammaWLEDMethod::Correct(uint8_t value)
uint8_t gamma8(uint8_t b)
{ {
return gammaT[b]; if (!gammaCorrectCol) return value;
return gammaT[value];
} }
// used for color gamma correction // used for color gamma correction
uint32_t gamma32(uint32_t color) uint32_t NeoGammaWLEDMethod::Correct32(uint32_t color)
{ {
if (!gammaCorrectCol) return color; if (!gammaCorrectCol) return color;
uint8_t w = W(color); uint8_t w = W(color);

View File

@ -10,7 +10,7 @@
d.um_p = []; d.um_p = [];
d.rsvd = []; d.rsvd = [];
d.ro_gpio = []; d.ro_gpio = [];
d.max_gpio = 39; d.max_gpio = 50;
var customStarts=false,startsDirty=[],maxCOOverrides=5; var customStarts=false,startsDirty=[],maxCOOverrides=5;
var loc = false, locip, locproto = "http:"; var loc = false, locip, locproto = "http:";
function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");} function H(){window.open("https://kno.wled.ge/features/settings/#led-settings");}
@ -26,8 +26,12 @@
d.body.appendChild(scE); d.body.appendChild(scE);
// success event // success event
scE.addEventListener("load", () => { scE.addEventListener("load", () => {
GetV();checkSi();setABL(); GetV();
checkSi();
setABL();
d.Sf.addEventListener("submit", trySubmit);
if (d.um_p[0]==-1) d.um_p.shift(); if (d.um_p[0]==-1) d.um_p.shift();
pinDropdowns();
}); });
// error event // error event
scE.addEventListener("error", (ev) => { scE.addEventListener("error", (ev) => {
@ -49,7 +53,7 @@
maxB = b; maxV = v; maxM = m; maxPB = p; maxL = l; maxB = b; maxV = v; maxM = m; maxPB = p; maxL = l;
} }
function pinsOK() { function pinsOK() {
var LCs = d.getElementsByTagName("input"); var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields
for (i=0; i<LCs.length; i++) { for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2); var nm = LCs[i].name.substring(0,2);
// ignore IP address // ignore IP address
@ -59,23 +63,36 @@
if (t>=80) continue; if (t>=80) continue;
} }
//check for pin conflicts //check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR") if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4"/* || nm=="RL" || nm=="BT" || nm=="IR"*/)
if (LCs[i].value!="" && LCs[i].value!="-1") { if (LCs[i].value!="" && LCs[i].value!="-1") {
var p = []; // used pin array var p = d.rsvd.concat(d.um_p); // used pin array
for (k=0;k<d.rsvd.length;k++) p.push(d.rsvd[k]); // fill with reservations d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]); // fill with usermod pins if (p.some((e)=>e==parseInt(LCs[i].value))) {
if (p.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);LCs[i].value="";LCs[i].focus();return false;} alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);
else if (!(nm == "IR" || nm=="BT") && d.ro_gpio.some((e)=>e==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);LCs[i].value="";LCs[i].focus();return false;} LCs[i].value="";
for (j=i+1; j<LCs.length; j++) LCs[i].focus();
{ return false;
}
else if (/*!(nm == "IR" || nm=="BT") &&*/ d.ro_gpio.some((e)=>e==parseInt(LCs[i].value))) {
alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);
LCs[i].value="";
LCs[i].focus();
return false;
}
for (j=i+1; j<LCs.length; j++) {
var n2 = LCs[j].name.substring(0,2); 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") { if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4"/* || n2=="RL" || n2=="BT" || n2=="IR"*/) {
if (n2.substring(0,1)==="L") { if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2); var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10); var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10);
if (t2>=80) continue; if (t2>=80) continue;
} }
if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`);LCs[j].value="";LCs[j].focus();return false;} if (LCs[j].value!="" && LCs[i].value==LCs[j].value) {
alert(`Pin conflict between ${LCs[i].name}/${LCs[j].name}!`);
LCs[j].value="";
LCs[j].focus();
return false;
}
} }
} }
} }
@ -96,6 +113,7 @@
gId('abl').style.display = (en) ? 'inline':'none'; gId('abl').style.display = (en) ? 'inline':'none';
gId('psu2').style.display = (en) ? 'inline':'none'; gId('psu2').style.display = (en) ? 'inline':'none';
if (d.Sf.LA.value > 0) setABL(); if (d.Sf.LA.value > 0) setABL();
UI();
} }
function enLA() function enLA()
{ {
@ -117,28 +135,28 @@
default: gId('LAdis').style.display = 'inline'; default: gId('LAdis').style.display = 'inline';
} }
gId('m1').innerHTML = maxM; gId('m1').innerHTML = maxM;
d.getElementsByName("Sf")[0].addEventListener("submit", trySubmit);
UI();
} }
//returns mem usage //returns mem usage
function getMem(t, n) { function getMem(t, n) {
let len = parseInt(d.getElementsByName("LC"+n)[0].value); let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
let dbl = 0;
if (d.Sf.LD.checked) dbl = len * 3; // double buffering
if (t < 32) { if (t < 32) {
if (t==26 || t==29) len *= 2; // 16 bit LEDs if (t==26 || t==29) len *= 2; // 16 bit LEDs
if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem
if (t > 28) return len*20; //RGBW if (t > 28) return len*20 + dbl; //RGBW
return len*15; return len*15 + dbl;
} else if (maxM >= 10000) //ESP32 RMT uses double buffer? } else if (maxM >= 10000) //ESP32 RMT uses double buffer?
{ {
if (t > 28) return len*8; //RGBW if (t > 28) return len*8 + dbl; //RGBW
return len*6; return len*6 + dbl;
} }
if (t > 28) return len*4; //RGBW if (t > 28) return len*4 + dbl; //RGBW
return len*3; return len*3 + dbl;
} }
if (t > 31 && t < 48) return 5; if (t > 31 && t < 48) return 5; // analog
return len*3; return len*3 + dbl;
} }
function UI(change=false) function UI(change=false)
@ -151,15 +169,13 @@
else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value; else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value;
// enable/disable LED fields // enable/disable LED fields
var s = d.getElementsByTagName("select"); d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{
for (i=0; i<s.length; i++) {
// is the field a LED type? // is the field a LED type?
if (s[i].name.substring(0,2)=="LT") { var n = s.name.substring(2);
var n = s[i].name.substring(2); var t = parseInt(s.value);
var t = parseInt(s[i].value,10);
gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:"; gId("p0d"+n).innerHTML = (t>=80 && t<96) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t > 41) ? "GPIOs:" : "GPIO:";
gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : ""; gId("p1d"+n).innerHTML = (t> 49 && t<64) ? "Clk GPIO:" : "";
var LK = d.getElementsByName("L1"+n)[0]; // clock pin //var LK = d.getElementsByName("L1"+n)[0]; // clock pin
memu += getMem(t, n); // calc memory memu += getMem(t, n); // calc memory
@ -196,8 +212,7 @@
gId("dig"+n+"l").style.display = (t > 48 && t < 64) ? "inline":"none"; // bus clock speed gId("dig"+n+"l").style.display = (t > 48 && t < 64) ? "inline":"none"; // bus clock speed
gId("rev"+n).innerHTML = (t >= 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog gId("rev"+n).innerHTML = (t >= 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog
gId("psd"+n).innerHTML = (t >= 40 && t < 48) ? "Index:":"Start:"; // change analog start description gId("psd"+n).innerHTML = (t >= 40 && t < 48) ? "Index:":"Start:"; // change analog start description
} });
}
// display global white channel overrides // display global white channel overrides
gId("wc").style.display = (gRGBW) ? 'inline':'none'; gId("wc").style.display = (gRGBW) ? 'inline':'none';
if (!gRGBW) { if (!gRGBW) {
@ -205,7 +220,7 @@
d.Sf.CR.checked = false; d.Sf.CR.checked = false;
} }
// check for pin conflicts // check for pin conflicts
var LCs = d.getElementsByTagName("input"); var LCs = d.Sf.querySelectorAll("#mLC input[name^=L]"); // input fields
var sLC = 0, sPC = 0, maxLC = 0; var sLC = 0, sPC = 0, maxLC = 0;
for (i=0; i<LCs.length; i++) { for (i=0; i<LCs.length; i++) {
var nm = LCs[i].name.substring(0,2); // field name var nm = LCs[i].name.substring(0,2); // field name
@ -243,15 +258,14 @@
} }
} }
// check for pin conflicts // check for pin conflicts
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR") if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4"/* || nm=="RL" || nm=="BT" || nm=="IR"*/)
if (LCs[i].value!="" && LCs[i].value!="-1") { if (LCs[i].value!="" && LCs[i].value!="-1") {
var p = []; // used pin array var p = d.rsvd.concat(d.um_p); // used pin array
for (k=0;k<d.rsvd.length;k++) p.push(d.rsvd[k]); // fill with reservations d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
for (k=0;k<d.um_p.length;k++) p.push(d.um_p[k]); // fill with usermod pins
for (j=0; j<LCs.length; j++) { for (j=0; j<LCs.length; j++) {
if (i==j) continue; if (i==j) continue;
var n2 = LCs[j].name.substring(0,2); 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") { if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4"/* || n2=="RL" || n2=="BT" || n2=="IR"*/) {
if (n2.substring(0,1)==="L") { if (n2.substring(0,1)==="L") {
var m = LCs[j].name.substring(2); var m = LCs[j].name.substring(2);
var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10); var t2 = parseInt(d.getElementsByName("LT"+m)[0].value, 10);
@ -261,13 +275,13 @@
} }
} }
// now check for conflicts // now check for conflicts
if (p.some((e)=>e==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color=d.ro_gpio.some((e)=>e==parseInt(LCs[i].value,10))?"orange":"#fff"; if (p.some((e)=>e==parseInt(LCs[i].value))) LCs[i].style.color="red"; else LCs[i].style.color=d.ro_gpio.some((e)=>e==parseInt(LCs[i].value))?"orange":"#fff";
} }
// check buttons, IR & relay // check buttons, IR & relay
if (nm=="IR" || nm=="BT" || nm=="RL") { //if (nm=="IR" || nm=="BT" || nm=="RL") {
LCs[i].max = d.max_gpio; // LCs[i].max = d.max_gpio;
LCs[i].min = -1; // LCs[i].min = -1;
} //}
} }
// update total led count // update total led count
gId("lc").textContent = sLC; gId("lc").textContent = sLC;
@ -366,11 +380,11 @@ ${i+1}:
<span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp; <span id="psd${i}">Start:</span> <input type="number" name="LS${i}" id="ls${i}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />&nbsp;
<div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br> <div id="dig${i}c" style="display:inline">Length: <input type="number" name="LC${i}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br>
</div> </div>
<span id="p0d${i}">GPIO:</span> <input type="number" name="L0${i}" required class="s" onchange="UI()"/> <span id="p0d${i}">GPIO:</span><input type="number" name="L0${i}" required class="s" onchange="UI();pinUpd(this);"/>
<span id="p1d${i}"></span><input type="number" name="L1${i}" class="s" onchange="UI()"/> <span id="p1d${i}"></span><input type="number" name="L1${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p2d${i}"></span><input type="number" name="L2${i}" class="s" onchange="UI()"/> <span id="p2d${i}"></span><input type="number" name="L2${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p3d${i}"></span><input type="number" name="L3${i}" class="s" onchange="UI()"/> <span id="p3d${i}"></span><input type="number" name="L3${i}" class="s" onchange="UI();pinUpd(this);"/>
<span id="p4d${i}"></span><input type="number" name="L4${i}" class="s" onchange="UI()"/> <span id="p4d${i}"></span><input type="number" name="L4${i}" class="s" onchange="UI();pinUpd(this);"/>
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div> <div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
<div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div> <div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div>
<div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div> <div id="dig${i}f" style="display:inline"><br>Off Refresh: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
@ -546,7 +560,103 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
} }
} }
} }
function S(){ function pinDropdowns() {
let fields = ["IR","RL"]; // IR & relay
gId("btns").querySelectorAll('input[type="number"]').forEach((e)=>{fields.push(e.name);}) // buttons
for (let i of d.Sf.elements) {
if (i.type === "number" && fields.includes(i.name)) { //select all pin select elements
let v = parseInt(i.value);
let sel = addDropdown(i.name,0);
for (var j = -1; j <= d.max_gpio; j++) {
if (d.rsvd.includes(j)) continue;
let foundPin = d.um_p.indexOf(j);
let txt = (j === -1) ? "unused" : `${j}`;
if (foundPin >= 0 && j !== v) txt += ` used`; // already reserved pin
if (d.ro_gpio.includes(j)) txt += " (R/O)";
let opt = addOption(sel, txt, j);
if (j === v) opt.selected = true; // this is "our" pin
else if (d.um_p.includes(j)) opt.disabled = true; // someone else's pin
}
}
}
// update select options
d.Sf.querySelectorAll("select.pin").forEach((e)=>{pinUpd(e);});
// add dataset values for LED GPIO pins
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{
if (i.value!=="" && i.value>=0)
i.dataset.val = i.value;
});
}
function pinUpd(e) {
// update changed select options across all usermods
let oldV = parseInt(e.dataset.val);
e.dataset.val = e.value;
let txt = e.name;
let pins = [];
d.Sf.querySelectorAll(".iST input.s[name^=L]").forEach((i)=>{
if (i.value!=="" && i.value>=0 && i.max<255)
pins.push(i.value);
});
let selects = d.Sf.querySelectorAll("select.pin");
for (let sel of selects) {
if (sel == e) continue
Array.from(sel.options).forEach((i)=>{
let led = pins.includes(i.value);
if (!(i.value==oldV || i.value==e.value || led)) return;
if (i.value == -1) {
i.text = "unused";
return
}
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value || led) {
i.disabled = true;
i.text += ` ${led?'LED':txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
});
}
}
// https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option
function addDropdown(field) {
let sel = d.createElement('select');
sel.classList.add("pin");
let inp = d.getElementsByName(field)[0];
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName
let v = inp.value;
let n = inp.name;
// copy the existing input element's attributes to the new select element
for (var i = 0; i < inp.attributes.length; ++ i) {
var att = inp.attributes[i];
// type and value don't apply, so skip them
// ** you might also want to skip style, or others -- modify as needed **
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') {
sel.setAttribute(att.name, att.value);
}
}
sel.setAttribute("data-val", v);
sel.setAttribute("onchange", "pinUpd(this)");
// finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp);
return sel;
}
return null;
}
function addOption(sel,txt,val) {
if (sel===null) return; // select object missing
let opt = d.createElement("option");
opt.value = val;
opt.text = txt;
sel.appendChild(opt);
for (let i=0; i<sel.childNodes.length; i++) {
let c = sel.childNodes[i];
if (c.value == sel.dataset.val) sel.selectedIndex = i;
}
return opt;
}
function S() {
let l = window.location; let l = window.location;
if (l.protocol == "file:") { if (l.protocol == "file:") {
loc = true; loc = true;
@ -624,7 +734,7 @@ Length: <input type="number" name="XC${i}" id="xc${i}" class="l" min="1" max="65
<hr class="sml"> <hr class="sml">
Make a segment for each output: <input type="checkbox" name="MS"><br> Make a segment for each output: <input type="checkbox" name="MS"><br>
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br> Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br>
Use global LED buffer: <input type="checkbox" name="LD"><br> Use global LED buffer: <input type="checkbox" name="LD" onchange="UI()"><br>
<hr class="sml"> <hr class="sml">
<div id="color_order_mapping"> <div id="color_order_mapping">
Color Order Override: Color Order Override:

View File

@ -7,7 +7,7 @@
<title>Usermod Settings</title> <title>Usermod Settings</title>
<script> <script>
var d = document; var d = document;
d.max_gpio = 39; d.max_gpio = 50;
d.um_p = []; d.um_p = [];
d.rsvd = []; d.rsvd = [];
d.ro_gpio = []; d.ro_gpio = [];
@ -39,6 +39,7 @@
d.Sf.MISO.max = d.max_gpio; d.Sf.MISO.max = d.max_gpio;
let inp = d.getElementsByTagName("input"); let inp = d.getElementsByTagName("input");
for (let i of inp) if (i.type === "number" && i.name.replace("[]","").substr(-3) === "pin") i.max = d.max_gpio; for (let i of inp) if (i.type === "number" && i.name.replace("[]","").substr(-3) === "pin") i.max = d.max_gpio;
pinDropdowns();
}); });
// error event // error event
scE.addEventListener("error", (ev) => { scE.addEventListener("error", (ev) => {
@ -103,8 +104,10 @@
if (isO(o)) { if (isO(o)) {
for (const [k,v] of Object.entries(o)) { for (const [k,v] of Object.entries(o)) {
if (isO(v)) { if (isO(v)) {
let oldO = owner; // keep parent name
owner = k; owner = k;
getPins(v); getPins(v);
owner = oldO;
continue; continue;
} }
if (k.replace("[]","").substr(-3)=="pin") { if (k.replace("[]","").substr(-3)=="pin") {
@ -163,11 +166,67 @@
urows += `<input type="${t==="int"?"number":t}" name="${k}:${f}${a?"[]":""}" ${c} oninput="check(this,'${k.substr(k.indexOf(":")+1)}')"><br>`; urows += `<input type="${t==="int"?"number":t}" name="${k}:${f}${a?"[]":""}" ${c} oninput="check(this,'${k.substr(k.indexOf(":")+1)}')"><br>`;
} }
} }
function pinDropdowns() {
for (let i of d.Sf.elements) {
if (i.type === "number" && (i.name.includes("pin") || ["SDA","SCL","MOSI","MISO","SCLK"].includes(i.name))) { //select all pin select elements
let v = parseInt(i.value);
let sel = addDropdown(i.name,0);
for (var j = -1; j <= d.max_gpio; j++) {
if (d.rsvd.includes(j)) continue;
let foundPin = pins.indexOf(j);
let txt = (j === -1) ? "unused" : `${j}`;
if (foundPin >= 0 && j !== v) txt += ` ${pinO[foundPin]=="if"?"global":pinO[foundPin]}`; // already reserved pin
if (d.ro_gpio.includes(j)) txt += " (R/O)";
let opt = addOption(sel, txt, j);
if (j === v) opt.selected = true; // this is "our" pin
else if (pins.includes(j)) opt.disabled = true; // someone else's pin
}
}
}
}
function UI(e) {
// update changed select options across all usermods
let oldV = parseInt(e.dataset.val);
e.dataset.val = e.value;
let txt = e.name.split(":")[e.name.split(":").length-2];
let selects = d.Sf.querySelectorAll("select[class='pin']");
for (let sel of selects) {
if (sel == e) continue
Array.from(sel.options).forEach((i)=>{
if (!(i.value==oldV || i.value==e.value)) return;
if (i.value == -1) {
i.text = "unused";
return
}
i.text = i.value;
if (i.value==oldV) {
i.disabled = false;
}
if (i.value==e.value) {
i.disabled = true;
i.text += ` ${txt}`;
}
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
});
}
}
// https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option // https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option
function addDropdown(um,fld) { function addDropdown(um,fld) {
let sel = d.createElement('select'); let sel = d.createElement('select');
let arr = d.getElementsByName(um+":"+fld); if (typeof(fld) === "string") { // parameter from usermod (field name)
let inp = arr[1]; // assume 1st field to be hidden (type) if (fld.includes("pin")) sel.classList.add("pin");
um += ":"+fld;
} else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class
let arr = d.getElementsByName(um);
let idx = arr[0].type==="hidden"?1:0; // ignore hidden field
if (arr.length > 2) {
// we have array of values (usually pins)
for (let i of arr) {
if (i.type === "number") break;
idx++;
}
}
let inp = arr[idx];
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName
let v = inp.value; let v = inp.value;
let n = inp.name; let n = inp.name;
@ -181,8 +240,10 @@
} }
} }
sel.setAttribute("data-val", v); sel.setAttribute("data-val", v);
sel.setAttribute("onchange", "UI(this)");
// finally, replace the old input element with the new select element // finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp); inp.parentElement.replaceChild(sel, inp);
if (arr[0].type==="hidden") arr[0].parentElement.removeChild(arr[0]); // remove hidden element from DOM
return sel; return sel;
} }
return null; return null;
@ -197,6 +258,7 @@
let c = sel.childNodes[i]; let c = sel.childNodes[i];
if (c.value == sel.dataset.val) sel.selectedIndex = i; if (c.value == sel.dataset.val) sel.selectedIndex = i;
} }
return opt;
} }
// https://stackoverflow.com/questions/26440494/insert-text-after-this-input-element-with-javascript // https://stackoverflow.com/questions/26440494/insert-text-after-this-input-element-with-javascript
function addInfo(name,el,txt, txt2="") { function addInfo(name,el,txt, txt2="") {
@ -208,6 +270,10 @@
if (txt2!="") obj[el].insertAdjacentHTML('beforebegin', txt2 + '&nbsp;'); //add pre texts if (txt2!="") obj[el].insertAdjacentHTML('beforebegin', txt2 + '&nbsp;'); //add pre texts
} }
} }
// add Help Button
function addHB(um) {
addInfo(um + ':help',0,`<button onclick="location.href='https://kno.wled.ge/usermods/${um}'" type="button">?</button>`);
}
// load settings and insert values into DOM // load settings and insert values into DOM
function ldS() { function ldS() {
fetch(getURL('/cfg.json'), { fetch(getURL('/cfg.json'), {

View File

@ -61,6 +61,9 @@ button.sml {
.hide { .hide {
display: none; display: none;
} }
.err {
color: #f00;
}
.warn { .warn {
color: #fa0; color: #fa0;
} }
@ -117,6 +120,10 @@ select {
font-family: Verdana, sans-serif; font-family: Verdana, sans-serif;
border: 0.5ch solid #333; border: 0.5ch solid #333;
} }
select.pin {
max-width: 120px;
text-overflow: ellipsis;
}
tr { tr {
line-height: 100%; line-height: 100%;
} }

View File

@ -50,6 +50,18 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau
//colors.cpp //colors.cpp
// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod)
class NeoGammaWLEDMethod {
public:
static uint8_t Correct(uint8_t value); // apply Gamma to single channel
static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB)
static void calcGammaTable(float gamma); // re-calculates & fills gamma table
static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB)
private:
static uint8_t gammaT[];
};
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false);
uint32_t color_add(uint32_t,uint32_t); uint32_t color_add(uint32_t,uint32_t);
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
@ -63,10 +75,6 @@ bool colorFromHexString(byte* rgb, const char* in);
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb); void setRandomColor(byte* rgb);
uint8_t gamma8_cal(uint8_t b, float gamma);
void calcGammaTable(float gamma);
uint8_t gamma8(uint8_t b);
uint32_t gamma32(uint32_t);
//dmx.cpp //dmx.cpp
void initDMX(); void initDMX();

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,8 @@ void applyValuesToSelectedSegs()
if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;} if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;}
if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;} if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;}
if (effectPalette != selsegPrev.palette) {seg.palette = effectPalette; stateChanged = true;} if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette); stateChanged = true;}
if (effectCurrent != selsegPrev.mode) {strip.setMode(i, effectCurrent); stateChanged = true;} if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent); stateChanged = true;}
uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]); uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]);
uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]); uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]);
if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0); stateChanged = true;} if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0); stateChanged = true;}

View File

@ -233,7 +233,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
gammaCorrectCol = request->hasArg(F("GC")); gammaCorrectCol = request->hasArg(F("GC"));
gammaCorrectVal = request->arg(F("GV")).toFloat(); gammaCorrectVal = request->arg(F("GV")).toFloat();
if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3)
calcGammaTable(gammaCorrectVal); NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal);
else { else {
gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectVal = 1.0f; // no gamma correction
gammaCorrectBri = false; gammaCorrectBri = false;

View File

@ -197,7 +197,7 @@ void appendGPIOinfo() {
#elif defined(CONFIG_IDF_TARGET_ESP32C3) #elif defined(CONFIG_IDF_TARGET_ESP32C3)
oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17")); oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17"));
#elif defined(ESP32) #elif defined(ESP32)
oappend(SET_F("d.rsvd=[6,7,8,9,10,11,24,28,29,30,31")); oappend(SET_F("d.rsvd=[6,7,8,9,10,11,24,28,29,30,31,37,38"));
#else #else
oappend(SET_F("d.rsvd=[6,7,8,9,10,11")); oappend(SET_F("d.rsvd=[6,7,8,9,10,11"));
#endif #endif