2021-09-23 19:44:24 +00:00
<!DOCTYPE html>
< html lang = "en" >
< head >
2023-01-16 21:12:02 +00:00
< meta charset = "utf-8" >
2023-10-04 19:37:10 +00:00
< meta content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name = "viewport" >
2021-09-23 19:44:24 +00:00
< title > LED Settings< / title >
2024-09-17 14:26:11 +00:00
< script src = "common.js" async type = "text/javascript" > < / script >
2021-09-23 19:44:24 +00:00
< script >
2024-09-17 14:26:11 +00:00
var laprev=55,maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxLbquot=0; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
2024-06-23 12:08:18 +00:00
var oMaxB=1;
2024-05-12 09:12:13 +00:00
var customStarts=false,startsDirty=[];
2024-09-17 14:26:11 +00:00
function off(n) { gN(n).value = -1;}
2024-01-16 20:02:08 +00:00
// these functions correspond to C macros found in const.h
2024-08-22 15:15:12 +00:00
function gT(t) { for (let type of d.ledTypes) if (t == type.i) return type; } // getType from available ledTypes
function isPWM(t) { return gT(t).t.charAt(0) === "A"; } // is PWM type
function isAna(t) { return gT(t).t === "" || isPWM(t); } // is analog type
function isDig(t) { return gT(t).t === "D" || isD2P(t); } // is digital type
function isD2P(t) { return gT(t).t === "2P"; } // is digital 2 pin type
2024-08-27 12:11:56 +00:00
function isNet(t) { return gT(t).t === "N"; } // is network type
function isVir(t) { return gT(t).t === "V" || isNet(t); } // is virtual type
2024-08-24 09:35:32 +00:00
function hasRGB(t) { return !!(gT(t).c & 0x01); } // has RGB
function hasW(t) { return !!(gT(t).c & 0x02); } // has white channel
function hasCCT(t) { return !!(gT(t).c & 0x04); } // is white CCT enabled
function is16b(t) { return !!(gT(t).c & 0x10); } // is digital 16 bit type
2024-09-22 11:56:14 +00:00
function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory
2024-09-08 09:07:53 +00:00
function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins
2024-09-17 14:26:11 +00:00
function S() {
getLoc();
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
d.ledTypes = [/*{i:22,c:1,t:"D",n:"WS2812"},{i:42,c:6,t:"AA",n:"PWM CCT"}*/]; // filled from GetV()
d.um_p = [];
d.rsvd = [];
d.ro_gpio = [];
d.max_gpio = 50;
}, ()=>{
2023-11-01 09:28:57 +00:00
checkSi();
2024-01-01 22:14:45 +00:00
setABL();
2023-06-12 17:21:14 +00:00
d.Sf.addEventListener("submit", trySubmit);
2023-01-16 21:12:02 +00:00
if (d.um_p[0]==-1) d.um_p.shift();
2023-06-10 18:43:27 +00:00
pinDropdowns();
2024-09-17 14:26:11 +00:00
}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/leds');
2023-01-16 21:12:02 +00:00
}
2024-06-29 18:22:47 +00:00
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
2024-06-23 12:08:18 +00:00
// maxB - max buses (can be changed if using ESP32 parallel I2S)
2024-06-29 18:22:47 +00:00
// maxD - max digital channels (can be changed if using ESP32 parallel I2S)
// maxA - max analog channels
2024-06-23 12:08:18 +00:00
// maxV - min virtual buses
// maxPB - max LEDs per bus
// maxM - max LED memory
2024-08-30 13:21:16 +00:00
// maxL - max LEDs (will serve to determine ESP >1664 == ESP32)
2024-06-23 12:08:18 +00:00
// maxCO - max Color Order mappings
2024-06-29 18:22:47 +00:00
oMaxB = maxB = b; maxD = d, maxA = a, maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o;
2023-01-16 21:12:02 +00:00
}
function pinsOK() {
2023-11-29 10:56:47 +00:00
var ok = true;
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
nList.forEach((LC,i)=>{
if (!ok) return; // prevent iteration after conflict
let nm = LC.name.substring(0,2);
let n = LC.name.substring(2);
let t = parseInt(d.Sf["LT"+n].value, 10); // LED type SELECT
2023-01-16 21:12:02 +00:00
// ignore IP address
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") {
2024-09-08 09:07:53 +00:00
if (isNet(t)) return;
2023-01-16 21:12:02 +00:00
}
//check for pin conflicts
2023-11-29 10:56:47 +00:00
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4")
if (LC.value!="" & & LC.value!="-1") {
let p = d.rsvd.concat(d.um_p); // used pin array
2023-06-12 17:21:14 +00:00
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
2023-11-29 10:56:47 +00:00
if (p.some((e)=>e==parseInt(LC.value))) {
2023-06-12 17:21:14 +00:00
alert(`Sorry, pins ${JSON.stringify(p)} can't be used.`);
2023-11-29 10:56:47 +00:00
LC.value="";
LC.focus();
ok = false;
return;
} else if (d.ro_gpio.some((e)=>e==parseInt(LC.value))) {
2023-06-12 17:21:14 +00:00
alert(`Sorry, pins ${JSON.stringify(d.ro_gpio)} are input only.`);
2023-11-29 10:56:47 +00:00
LC.value="";
LC.focus();
ok = false;
return;
2023-06-12 17:21:14 +00:00
}
2023-11-29 10:56:47 +00:00
for (j=i+1; j< nList.length ; j + + ) {
let n2 = nList[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4") {
2023-01-16 21:12:02 +00:00
if (n2.substring(0,1)==="L") {
2023-11-29 10:56:47 +00:00
var m = nList[j].name.substring(2);
var t2 = parseInt(d.Sf["LT"+m].value, 10);
2023-01-16 21:12:02 +00:00
if (t2>=80) continue;
}
2023-11-29 10:56:47 +00:00
if (nList[j].value!="" & & nList[i].value==nList[j].value) {
alert(`Pin conflict between ${LC.name}/${nList[j].name}!`);
nList[j].value="";
nList[j].focus();
ok = false;
return;
2023-06-12 17:21:14 +00:00
}
2023-01-16 21:12:02 +00:00
}
}
}
2023-11-29 10:56:47 +00:00
});
return ok;
2023-01-16 21:12:02 +00:00
}
function trySubmit(e) {
d.Sf.data.value = '';
e.preventDefault();
if (!pinsOK()) {e.stopPropagation();return false;} // Prevent form submission and contact with server
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000 ) msg + = " \ n \ rConsider using an ESP32 . " ; alert ( msg ) ; }
2024-01-01 22:14:45 +00:00
if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it)
2024-06-29 18:22:47 +00:00
if (d.Sf.checkValidity()) {
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case
d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
2023-01-16 21:12:02 +00:00
}
function enABL()
{
2023-12-29 22:07:29 +00:00
var en = d.Sf.ABL.checked;
2023-01-16 21:12:02 +00:00
gId('abl').style.display = (en) ? 'inline':'none';
gId('psu2').style.display = (en) ? 'inline':'none';
2023-12-29 22:07:29 +00:00
if (!en) d.Sf.PPL.checked = false;
2023-06-12 17:21:14 +00:00
UI();
2023-01-16 21:12:02 +00:00
}
2024-02-28 17:14:42 +00:00
// enable per port limiter and calculate current
function enPPL(sDI=0)
2023-01-16 21:12:02 +00:00
{
2023-12-29 22:07:29 +00:00
const abl = d.Sf.ABL.checked;
const ppl = d.Sf.PPL.checked;
let sumMA = 0;
d.Sf.MA.readonly = ppl;
d.Sf.MA.min = abl & & !ppl ? 250 : 0;
gId("psuMA").style.display = ppl ? 'none' : 'inline';
gId("ppldis").style.display = ppl ? 'inline' : 'none';
// set PPL minimum value and clear actual PPL limit if ABL disabled
2024-05-12 09:12:13 +00:00
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,x)=>{
var n = String.fromCharCode((x< 10 ? 48:55 ) + x ) ;
2023-12-29 22:07:29 +00:00
gId("PSU"+n).style.display = ppl ? "inline" : "none";
2023-11-15 18:37:07 +00:00
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
2024-02-28 17:14:42 +00:00
const c = parseInt(d.Sf["LC"+n].value); //get LED count
2024-01-09 20:54:21 +00:00
i.min = ppl & & !(isVir(t) || isAna(t)) ? 250 : 0;
2024-02-28 17:14:42 +00:00
if (!abl || isVir(t) || isAna(t)) i.value = 0;
2023-12-29 22:07:29 +00:00
else if (ppl) sumMA += parseInt(i.value,10);
2024-02-28 17:14:42 +00:00
else if (sDI) i.value = Math.round(parseInt(d.Sf.MA.value,10)*c/sDI);
2023-11-15 18:37:07 +00:00
});
2023-12-29 22:07:29 +00:00
if (ppl) d.Sf.MA.value = sumMA; // populate UI ABL value if PPL used
2023-11-15 18:37:07 +00:00
}
function enLA(s,n)
{
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
gId('LAdis'+n).style.display = s.selectedIndex==5 ? "inline" : "none";
2024-04-13 16:25:25 +00:00
if (s.value!=="0") d.Sf["LA"+n].value = s.value;
2024-01-09 20:54:21 +00:00
d.Sf["LA"+n].min = (isVir(t) || isAna(t)) ? 0 : 1;
2023-01-16 21:12:02 +00:00
}
function setABL()
{
2023-12-29 22:07:29 +00:00
d.Sf.ABL.checked = parseInt(d.Sf.MA.value) > 0;
2023-11-15 18:37:07 +00:00
// check if ABL is enabled (max mA entered per output)
d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,n)=>{
2023-12-29 22:07:29 +00:00
if (parseInt(i.value) > 0) d.Sf.ABL.checked = true;
2023-11-15 18:37:07 +00:00
});
// select appropriate LED current
2024-05-12 09:12:13 +00:00
d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{
2023-11-15 18:37:07 +00:00
sel.value = 0; // set custom
2024-05-12 09:12:13 +00:00
var n = String.fromCharCode((x< 10 ? 48:55 ) + x ) ;
2023-11-15 18:37:07 +00:00
switch (parseInt(d.Sf["LA"+n].value)) {
case 0: break; // disable ABL
case 15: sel.value = 15; break;
case 30: sel.value = 30; break;
case 35: sel.value = 35; break;
case 55: sel.value = 55; break;
case 255: sel.value = 255; break;
}
enLA(sel,n);
});
enABL();
2023-01-16 21:12:02 +00:00
gId('m1').innerHTML = maxM;
}
//returns mem usage
function getMem(t, n) {
2024-01-16 20:02:08 +00:00
if (isAna(t)) return 5; // analog
2023-01-16 21:12:02 +00:00
let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
2023-06-12 17:21:14 +00:00
let dbl = 0;
2024-08-27 12:11:56 +00:00
let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
2024-01-16 20:02:08 +00:00
let mul = 1;
if (isDig(t)) {
if (is16b(t)) len *= 2; // 16 bit LEDs
2023-01-16 21:12:02 +00:00
if (maxM < 10000 & & d . getElementsByName ( " L0 " + n ) [ 0 ] . value = = 3 ) { / / 8266 DMA uses 5x the mem
2024-01-16 20:02:08 +00:00
mul = 5;
2023-01-16 21:12:02 +00:00
}
2024-01-16 20:02:08 +00:00
if (maxM >= 10000) { //ESP32 RMT uses double buffer?
mul = 2;
}
if (d.Sf.LD.checked) dbl = len * ch; // double buffering
2023-01-16 21:12:02 +00:00
}
2024-01-16 20:02:08 +00:00
return len * ch * mul + dbl;
2023-01-16 21:12:02 +00:00
}
2023-02-01 14:57:58 +00:00
2021-09-23 19:44:24 +00:00
function UI(change=false)
{
2024-03-24 16:37:11 +00:00
let gRGBW = false, memu = 0;
2024-02-28 17:14:42 +00:00
let busMA = 0;
2023-11-15 18:37:07 +00:00
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
2023-12-29 22:07:29 +00:00
const ablEN = d.Sf.ABL.checked;
2024-06-23 12:08:18 +00:00
maxB = oMaxB; // TODO make sure we start with all possible buses
2024-08-22 15:15:12 +00:00
let setPinConfig = (n,t) => {
let p0d = "GPIO:";
let p1d = "";
2024-08-24 09:35:32 +00:00
let off = "Off Refresh";
2024-08-22 15:15:12 +00:00
switch (gT(t).t.charAt(0)) {
2024-08-27 12:11:56 +00:00
case '2': // 2 pin digital
2024-08-24 09:35:32 +00:00
p1d = "Clock "+p0d;
2024-08-27 12:11:56 +00:00
// fallthrough
case 'D': // digital
2024-08-22 15:15:12 +00:00
p0d = "Data "+p0d;
break;
2024-08-27 12:11:56 +00:00
case 'A': // PWM analog
2024-09-08 09:07:53 +00:00
if (numPins(t) > 1) p0d = "GPIOs:";
2024-08-30 13:21:16 +00:00
off = "Dithering";
2024-08-22 15:15:12 +00:00
break;
2024-08-27 12:11:56 +00:00
case 'N': // network
2024-08-22 15:15:12 +00:00
p0d = "IP address:";
break;
2024-08-27 12:11:56 +00:00
case 'V': // virtual/non-GPIO based
p0d = "Config:"
break;
2024-08-22 15:15:12 +00:00
}
2024-08-24 09:35:32 +00:00
gId("p0d"+n).innerText = p0d;
gId("p1d"+n).innerText = p1d;
gId("off"+n).innerText = off;
2024-08-27 12:11:56 +00:00
// secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off)
2024-08-30 13:21:16 +00:00
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t); // fixes network pins to 4
2024-08-22 15:15:12 +00:00
for (let p=1; p< 5 ; p + + ) {
var LK = d.Sf["L"+p+n];
if (!LK) continue;
LK.style.display = (p < pins ) ? " inline " : " none " ;
LK.required = (p < pins ) ;
if (p >= pins) LK.value="";
}
}
2021-09-23 19:44:24 +00:00
2023-01-16 21:12:02 +00:00
// enable/disable LED fields
2024-06-29 18:22:47 +00:00
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
LTs.forEach((s,i)=>{
if (i < LTs.length-1 ) s . disabled = true; / / prevent changing type ( as we can ' t update options )
2023-01-16 21:12:02 +00:00
// is the field a LED type?
2023-06-12 17:21:14 +00:00
var n = s.name.substring(2);
var t = parseInt(s.value);
memu += getMem(t, n); // calc memory
2024-08-22 15:15:12 +00:00
setPinConfig(n,t);
gId("abl"+n).style.display = (!ablEN || isVir(t) || isAna(t)) ? "none" : "inline";
2023-06-12 17:21:14 +00:00
if (change) {
2024-08-22 15:15:12 +00:00
gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state (mandatory for TM1814)
if (isAna(t)) d.Sf["LC"+n].value = 1; // for sanity change analog count just to 1 LED
2024-01-09 20:54:21 +00:00
d.Sf["LA"+n].min = (isVir(t) || isAna(t)) ? 0 : 1;
d.Sf["MA"+n].min = (isVir(t) || isAna(t)) ? 0 : 250;
2023-06-12 17:21:14 +00:00
}
2024-09-22 11:56:14 +00:00
gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory
2024-08-22 15:15:12 +00:00
gRGBW |= hasW(t); // RGBW checkbox
gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM
gId("dig"+n+"w").style.display = (isDig(t) & & hasW(t)) ? "inline":"none"; // show swap channels dropdown
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
if (!(isDig(t) & & hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
2024-01-09 20:54:21 +00:00
gId("dig"+n+"s").style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide skip 1st for virtual & analog
2024-08-30 13:21:16 +00:00
gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) & & maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32)
2024-08-22 15:15:12 +00:00
gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white
2024-01-09 20:54:21 +00:00
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
2024-08-22 15:15:12 +00:00
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
2023-06-12 17:21:14 +00:00
});
2023-03-19 10:23:59 +00:00
// display global white channel overrides
gId("wc").style.display = (gRGBW) ? 'inline':'none';
if (!gRGBW) {
d.Sf.AW.selectedIndex = 0;
d.Sf.CR.checked = false;
2021-09-23 19:44:24 +00:00
}
2023-11-29 10:56:47 +00:00
// update start indexes, max values, calculate current, etc
var nList = d.Sf.querySelectorAll("#mLC input[name^=L]");
nList.forEach((LC,i)=>{
let nm = LC.name.substring(0,2); // field name
let n = LC.name.substring(2); // bus number
let t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
2023-01-16 21:12:02 +00:00
// do we have a led count field
if (nm=="LC") {
2023-11-29 10:56:47 +00:00
let c = parseInt(LC.value,10); //get LED count
2024-07-05 19:22:05 +00:00
if (c > 300 & & i < 8 ) maxB = oMaxB - Math . max ( maxD-7 , 0 ) ; / / TODO: hard limit for buses when using ESP32 parallel I2S
2023-01-16 21:12:02 +00:00
if (!customStarts || !startsDirty[n]) gId("ls"+n).value=sLC; //update start value
gId("ls"+n).disabled = !customStarts; //enable/disable field editing
2023-11-29 10:56:47 +00:00
if (c) {
let s = parseInt(gId("ls"+n).value); //start value
2023-01-16 21:12:02 +00:00
if (s+c > sLC) sLC = s+c; //update total count
2023-11-29 10:56:47 +00:00
if (c > maxLC) maxLC = c; //max per output
2024-01-09 20:54:21 +00:00
if (!isVir(t)) sPC += c; //virtual out busses do not count towards physical LEDs
if (!(isVir(t) || isAna(t))) {
2023-12-29 22:07:29 +00:00
sDI += c; // summarize digital LED count
2023-11-15 18:37:07 +00:00
let maPL = parseInt(d.Sf["LA"+n].value);
2023-11-29 10:56:47 +00:00
if (maPL == 255) maPL = 12;
2023-12-29 22:07:29 +00:00
busMA += maPL*c; // summarize maximum bus current (calculated)
2023-11-15 18:37:07 +00:00
}
2023-01-16 21:12:02 +00:00
} // increase led count
2023-11-29 10:56:47 +00:00
return;
2023-01-16 21:12:02 +00:00
}
// do we have led pins for digital leds
if (nm=="L0" || nm=="L1") {
2023-11-29 10:56:47 +00:00
d.Sf["LC"+n].max = maxPB; // update max led count value
2023-01-16 21:12:02 +00:00
}
// ignore IP address (stored in pins for virtual busses)
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") {
2024-01-09 20:54:21 +00:00
if (isVir(t)) {
2023-11-29 10:56:47 +00:00
LC.max = 255;
LC.min = 0;
LC.style.color="#fff";
return; // do not check conflicts
2023-01-16 21:12:02 +00:00
} else {
2023-11-29 10:56:47 +00:00
LC.max = d.max_gpio;
LC.min = -1;
2023-01-16 21:12:02 +00:00
}
}
2023-11-29 10:56:47 +00:00
// check for pin conflicts & color fields
if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4")
if (LC.value!="" & & LC.value!="-1") {
let p = d.rsvd.concat(d.um_p); // used pin array
2023-06-12 17:21:14 +00:00
d.Sf.querySelectorAll("select.pin").forEach((e)=>{if(e.value>-1)p.push(parseInt(e.value));}) // buttons, IR & relay
2023-11-29 10:56:47 +00:00
for (j=0; j< nList.length ; j + + ) {
2023-01-16 21:12:02 +00:00
if (i==j) continue;
2023-11-29 10:56:47 +00:00
let n2 = nList[j].name.substring(0,2);
if (n2=="L0" || n2=="L1" || n2=="L2" || n2=="L3" || n2=="L4") {
2023-01-16 21:12:02 +00:00
if (n2.substring(0,1)==="L") {
2023-11-29 10:56:47 +00:00
let m = nList[j].name.substring(2);
let t2 = parseInt(d.Sf["LT"+m].value, 10);
2024-01-09 20:54:21 +00:00
if (isVir(t2)) continue;
2023-01-16 21:12:02 +00:00
}
2023-11-29 10:56:47 +00:00
if (nList[j].value!="" & & nList[j].value!="-1") p.push(parseInt(nList[j].value,10)); // add current pin
2023-01-16 21:12:02 +00:00
}
}
// now check for conflicts
2023-11-29 10:56:47 +00:00
if (p.some((e)=>e==parseInt(LC.value))) LC.style.color = "red";
else LC.style.color = d.ro_gpio.some((e)=>e==parseInt(LC.value)) ? "orange" : "#fff";
2023-01-16 21:12:02 +00:00
}
2023-11-29 10:56:47 +00:00
});
2024-02-28 17:14:42 +00:00
// distribute ABL current if not using PPL
enPPL(sDI);
2023-01-16 21:12:02 +00:00
// update total led count
gId("lc").textContent = sLC;
gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)";
2021-10-03 10:23:24 +00:00
2023-01-16 21:12:02 +00:00
// memory usage and warnings
gId('m0').innerHTML = memu;
bquot = memu / maxM * 100;
gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%, #444 ${bquot}% 100%)`;
gId('ledwarning').style.display = (maxLC > Math.min(maxPB,800) || bquot > 80) ? 'inline':'none';
gId('ledwarning').style.color = (maxLC > Math.max(maxPB,800) || bquot > 100) ? 'red':'orange';
gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (< b > ERROR: Using over ${maxM}B!< / b > )` : "") : "800 LEDs per output";
// calculate power
2023-11-15 18:37:07 +00:00
gId('ampwarning').style.display = (parseInt(d.Sf.MA.value,10) > 7200) ? 'inline':'none';
var val = Math.ceil((100 + busMA)/500)/2;
2023-01-16 21:12:02 +00:00
val = (val > 5) ? Math.ceil(val) : val;
2023-11-15 18:37:07 +00:00
var s = "A power supply with total of ";
s += val;
s += "A is required.";
var val2 = Math.ceil((100 + busMA)/1500)/2;
2023-01-16 21:12:02 +00:00
val2 = (val2 > 5) ? Math.ceil(val2) : val2;
var s2 = "(for most effects, ~";
s2 += val2;
s2 += "A is enough)< br > ";
gId('psu').innerHTML = s;
2023-11-15 18:37:07 +00:00
gId('psu2').innerHTML = s2;
2023-01-16 21:12:02 +00:00
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
}
function lastEnd(i) {
2024-05-12 14:34:07 +00:00
if (i-- < 1 ) return 0 ;
var s = String.fromCharCode((i< 10 ? 48:55 ) + i ) ;
v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value);
var t = parseInt(d.getElementsByName("LT"+s)[0].value);
2024-05-12 09:12:13 +00:00
if (isPWM(t)) v = 1; //PWM busses
2024-06-29 18:22:47 +00:00
return isNaN(v) ? 0 : v;
2023-01-16 21:12:02 +00:00
}
function addLEDs(n,init=true)
{
2024-09-17 14:26:11 +00:00
var o = gEBCN("iST");
2023-01-16 21:12:02 +00:00
var i = o.length;
2024-06-29 18:22:47 +00:00
let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); }
var f = gId("mLC");
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
f.querySelectorAll("select[name^=LT]").forEach((s)=>{
let t = s.value;
if (isDig(t) & & !isD2P(t)) digitalB++;
if (isD2P(t)) twopinB++;
2024-09-08 09:07:53 +00:00
if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel
2024-06-29 18:22:47 +00:00
if (isVir(t)) virtB++;
});
2021-09-23 19:44:24 +00:00
2023-01-16 21:12:02 +00:00
if ((n==1 & & i>=maxB+maxV) || (n==-1 & & i==0)) return;
2024-05-12 14:34:07 +00:00
var s = String.fromCharCode((i< 10 ? 48:55 ) + i ) ;
2021-09-23 19:44:24 +00:00
2023-01-16 21:12:02 +00:00
if (n==1) {
2021-09-23 19:44:24 +00:00
// npm run build has trouble minimizing spaces inside string
2023-01-16 21:12:02 +00:00
var cn = `< div class = "iST" >
2022-11-11 19:20:11 +00:00
< hr class = "sml" >
2021-09-23 19:44:24 +00:00
${i+1}:
2024-08-22 15:15:12 +00:00
< select name = "LT${s}" onchange = "UI(true)" > < / select > < br >
2024-05-12 09:12:13 +00:00
< div id = "abl${s}" >
2024-05-13 12:30:30 +00:00
mA/LED: < select name = "LAsel${s}" onchange = "enLA(this,'${s}');UI();" >
2023-11-15 18:37:07 +00:00
< option value = "55" selected > 55mA (typ. 5V WS281x)< / option >
< option value = "35" > 35mA (eco WS2812)< / option >
< option value = "30" > 30mA (typ. 12V)< / option >
< option value = "255" > 12mA (WS2815)< / option >
< option value = "15" > 15mA (seed/fairy pixels)< / option >
< option value = "0" > Custom< / option >
< / select > < br >
2024-05-12 09:12:13 +00:00
< div id = "LAdis${s}" style = "display: none;" > max. mA/LED: < input name = "LA${s}" type = "number" min = "1" max = "255" oninput = "UI()" > mA< br > < / div >
< div id = "PSU${s}" > PSU: < input name = "MA${s}" type = "number" class = "xl" min = "250" max = "65000" oninput = "UI()" value = "250" > mA< br > < / div >
2023-11-15 18:37:07 +00:00
< / div >
2024-05-12 09:12:13 +00:00
< div id = "co${s}" style = "display:inline" > Color Order:
< select name = "CO${s}" >
2021-09-23 19:44:24 +00:00
< option value = "0" > GRB< / option >
< option value = "1" > RGB< / option >
< option value = "2" > BRG< / option >
< option value = "3" > RBG< / option >
< option value = "4" > BGR< / option >
< option value = "5" > GBR< / option >
2022-04-30 10:45:38 +00:00
< / select > < / div >
2024-08-05 18:56:12 +00:00
< div id = "dig${s}w" style = "display:none" > Swap: < select name = "WO${s}" > < option value = "0" > None< / option > < option value = "1" > W & B< / option > < option value = "2" > W & G< / option > < option value = "3" > W & R< / option > < option data-opt = "CCT" value = "4" > WW & CW< / option > < / select > < / div >
2024-05-12 09:12:13 +00:00
< div id = "dig${s}l" style = "display:none" > Clock: < select name = "SP${s}" > < option value = "0" > Slowest< / option > < option value = "1" > Slow< / option > < option value = "2" > Normal< / option > < option value = "3" > Fast< / option > < option value = "4" > Fastest< / option > < / select > < / div >
2022-04-30 10:45:38 +00:00
< div >
2024-05-12 09:12:13 +00:00
< span id = "psd${s}" > Start:< / span > < input type = "number" name = "LS${s}" id = "ls${s}" class = "l starts" min = "0" max = "8191" value = "${lastEnd(i)}" oninput = "startsDirty[${i}]=true;UI();" required / >
< div id = "dig${s}c" style = "display:inline" > Length: < input type = "number" name = "LC${s}" class = "l" min = "1" max = "${maxPB}" value = "1" required oninput = "UI()" / > < / div > < br >
2022-04-30 10:45:38 +00:00
< / div >
2024-05-12 09:12:13 +00:00
< span id = "p0d${s}" > GPIO:< / span > < input type = "number" name = "L0${s}" required class = "s" onchange = "UI();pinUpd(this);" / >
< span id = "p1d${s}" > < / span > < input type = "number" name = "L1${s}" class = "s" onchange = "UI();pinUpd(this);" / >
< span id = "p2d${s}" > < / span > < input type = "number" name = "L2${s}" class = "s" onchange = "UI();pinUpd(this);" / >
< span id = "p3d${s}" > < / span > < input type = "number" name = "L3${s}" class = "s" onchange = "UI();pinUpd(this);" / >
< span id = "p4d${s}" > < / span > < input type = "number" name = "L4${s}" class = "s" onchange = "UI();pinUpd(this);" / >
< div id = "dig${s}r" style = "display:inline" > < br > < span id = "rev${s}" > Reversed< / span > : < input type = "checkbox" name = "CV${s}" > < / div >
< div id = "dig${s}s" style = "display:inline" > < br > Skip first LEDs: < input type = "number" name = "SL${s}" min = "0" max = "255" value = "0" oninput = "UI()" > < / div >
2024-08-24 09:35:32 +00:00
< div id = "dig${s}f" style = "display:inline" > < br > < span id = "off${s}" > Off Refresh< / span > : < input id = "rf${s}" type = "checkbox" name = "RF${s}" > < / div >
< div id = "dig${s}a" style = "display:inline" > < br > Auto-calculate W channel from RGB:< br > < select name = "AW${s}" > < option value = 0 > None< / option > < option value = 1 > Brighter< / option > < option value = 2 > Accurate< / option > < option value = 3 > Dual< / option > < option value = 4 > Max< / option > < / select > < / div >
2021-09-23 19:44:24 +00:00
< / div > `;
2023-01-16 21:12:02 +00:00
f.insertAdjacentHTML("beforeend", cn);
2024-08-22 15:15:12 +00:00
// fill led types (credit @netmindz)
2024-09-17 14:26:11 +00:00
f.querySelectorAll("select[name^=LT]").forEach((sel,n)=>{
2024-08-22 15:15:12 +00:00
if (sel.length == 0) { // ignore already updated
for (let type of d.ledTypes) {
2024-09-17 14:26:11 +00:00
let opt = cE("option");
2024-08-22 15:15:12 +00:00
opt.value = type.i;
opt.text = type.n;
if (type.t != undefined & & type.t != "") {
opt.setAttribute('data-type', type.t);
}
sel.appendChild(opt);
}
}
});
// disable inappropriate LED types
2024-06-29 18:22:47 +00:00
let sel = d.getElementsByName("LT"+s)[0]
2024-09-22 11:56:14 +00:00
if (i >= maxB || digitalB >= maxD) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
if (i >= maxB || twopinB >= 1) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P()
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
2024-06-29 18:22:47 +00:00
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
2023-01-16 21:12:02 +00:00
}
if (n==-1) {
o[--i].remove();--i;
2024-07-17 20:24:08 +00:00
o[i].querySelector("[name^=LT]").disabled = false;
2023-01-16 21:12:02 +00:00
}
2021-09-23 19:44:24 +00:00
2023-01-16 21:12:02 +00:00
gId("+").style.display = (i< maxB + maxV-1 ) ? " inline " : " none " ;
gId("-").style.display = (i>0) ? "inline":"none";
2021-09-23 19:44:24 +00:00
2023-12-29 22:07:29 +00:00
if (!init) {
UI();
}
2023-01-16 21:12:02 +00:00
}
2022-01-01 21:08:07 +00:00
2023-01-16 21:12:02 +00:00
function addCOM(start=0,len=1,co=0) {
2024-09-17 14:26:11 +00:00
var i = gEBCN("com_entry").length;
2024-05-12 09:12:13 +00:00
if (i >= maxCO) return;
var s = String.fromCharCode((i< 10 ? 48:55 ) + i ) ;
2023-01-16 21:12:02 +00:00
var b = `< div class = "com_entry" >
2022-11-11 19:20:11 +00:00
< hr class = "sml" >
2024-05-12 09:12:13 +00:00
${i+1}: Start: < input type = "number" name = "XS${s}" id = "xs${s}" class = "l starts" min = "0" max = "65535" value = "${start}" oninput = "UI();" required = "" >
Length: < input type = "number" name = "XC${s}" id = "xc${s}" class = "l" min = "1" max = "65535" value = "${len}" required = "" oninput = "UI()" >
2024-01-11 19:22:42 +00:00
< div > Color Order:
2024-05-12 09:12:13 +00:00
< select id = "xo${s}" name = "XO${s}" >
2022-01-01 21:08:07 +00:00
< option value = "0" > GRB< / option >
< option value = "1" > RGB< / option >
< option value = "2" > BRG< / option >
< option value = "3" > RBG< / option >
< option value = "4" > BGR< / option >
< option value = "5" > GBR< / option >
< / select >
2024-05-12 09:12:13 +00:00
Swap: < select id = "xw${s}" name = "XW${s}" >
2024-01-11 19:22:42 +00:00
< option value = "0" > Use global< / option >
< option value = "1" > W & B< / option >
< option value = "2" > W & G< / option >
< option value = "3" > W & R< / option >
< / select >
< / div > < / div > `;
2023-01-16 21:12:02 +00:00
gId("com_entries").insertAdjacentHTML("beforeend", b);
2024-05-12 09:12:13 +00:00
gId("xo"+s).value = co & 0x0F;
gId("xw"+s).value = co >> 4;
2023-01-16 21:12:02 +00:00
btnCOM(i+1);
UI();
}
2022-01-01 21:08:07 +00:00
2023-01-16 21:12:02 +00:00
function remCOM() {
2024-09-17 14:26:11 +00:00
var entries = gEBCN("com_entry");
2023-01-16 21:12:02 +00:00
var i = entries.length;
if (i === 0) return;
entries[i-1].remove();
btnCOM(i-1);
UI();
}
2022-01-01 21:08:07 +00:00
2023-01-16 21:12:02 +00:00
function resetCOM(_newMaxCOOverrides=undefined) {
if (_newMaxCOOverrides) {
2024-05-12 09:12:13 +00:00
maxCO = _newMaxCOOverrides;
2023-01-16 21:12:02 +00:00
}
2024-09-17 14:26:11 +00:00
for (let e of gEBCN("com_entry")) {
2023-01-16 21:12:02 +00:00
e.remove();
}
btnCOM(0);
}
2022-01-01 21:08:07 +00:00
2023-01-16 21:12:02 +00:00
function btnCOM(i) {
2024-05-12 09:12:13 +00:00
gId("com_add").style.display = (i< maxCO ) ? " inline " : " none " ;
2023-01-16 21:12:02 +00:00
gId("com_rem").style.display = (i>0) ? "inline":"none";
}
2022-01-01 21:08:07 +00:00
2023-01-16 21:12:02 +00:00
function addBtn(i,p,t) {
var c = gId("btns").innerHTML;
2024-05-12 09:12:13 +00:00
var s = String.fromCharCode((i< 10 ? 48:55 ) + i ) ;
c += `Button ${i} GPIO: < input type = "number" name = "BT${s}" onchange = "UI()" class = "xs" value = "${p}" > `;
c += ` < select name = "BE${s}" > `
2023-01-16 21:12:02 +00:00
c += `< option value = "0" $ { t = =0?"selected":""} > Disabled< / option > `;
c += `< option value = "2" $ { t = =2?"selected":""} > Pushbutton< / option > `;
c += `< option value = "3" $ { t = =3?"selected":""} > Push inverted< / option > `;
c += `< option value = "4" $ { t = =4?"selected":""} > Switch< / option > `;
c += `< option value = "5" $ { t = =5?"selected":""} > PIR sensor< / option > `;
c += `< option value = "6" $ { t = =6?"selected":""} > Touch< / option > `;
c += `< option value = "7" $ { t = =7?"selected":""} > Analog< / option > `;
c += `< option value = "8" $ { t = =8?"selected":""} > Analog inverted< / option > `;
2023-11-22 14:35:11 +00:00
c += `< option value = "9" $ { t = =9?"selected":""} > Touch (switch)< / option > `;
2023-01-16 21:12:02 +00:00
c += `< / select > `;
2024-05-12 09:12:13 +00:00
c += `< span style = "cursor: pointer;" onclick = "off('BT${s}')" > ✕ < / span > < br > `;
2023-01-16 21:12:02 +00:00
gId("btns").innerHTML = c;
}
function tglSi(cs) {
customStarts = cs;
if (!customStarts) startsDirty = []; //set all starts to clean
UI();
}
function checkSi() { //on load, checks whether there are custom start fields
var cs = false;
2024-09-17 14:26:11 +00:00
for (var i=1; i < gEBCN ( " iST " ) . length ; i + + ) {
var v = parseInt(gId("ls"+(i-1)).value) + parseInt(gN("LC"+(i-1)).value);
2023-01-16 21:12:02 +00:00
if (v != parseInt(gId("ls"+i).value)) {cs = true; startsDirty[i] = true;}
}
if (gId("ls0") & & parseInt(gId("ls0").value) != 0) {cs = true; startsDirty[0] = true;}
gId("si").checked = cs;
tglSi(cs);
}
// https://stackoverflow.com/questions/7346563/loading-local-json-file
function loadCfg(o) {
var f, fr;
2021-10-16 19:44:53 +00:00
2023-01-16 21:12:02 +00:00
if (typeof window.FileReader !== 'function') {
alert("The file API isn't supported on this browser yet.");
return;
}
2021-10-16 19:44:53 +00:00
2023-01-16 21:12:02 +00:00
if (!o.files) {
alert("This browser doesn't support the `files` property of file inputs.");
} else if (!o.files[0]) {
alert("Please select a JSON file first!");
} else {
f = o.files[0];
fr = new FileReader();
fr.onload = receivedText;
fr.readAsText(f);
}
o.value = '';
2021-10-16 19:44:53 +00:00
2023-01-16 21:12:02 +00:00
function receivedText(e) {
let lines = e.target.result;
var c = JSON.parse(lines);
if (c.hw) {
if (c.hw.led) {
for (var i=0; i< 10 ; i + + ) addLEDs ( -1 ) ;
var l = c.hw.led;
l.ins.forEach((v,i,a)=>{
addLEDs(1);
for (var j=0; j< v.pin.length ; j + + ) d . getElementsByName ( ` L $ { j } $ { i } ` ) [ 0 ] . value = v.pin[j];
d.getElementsByName("LT"+i)[0].value = v.type;
d.getElementsByName("LS"+i)[0].value = v.start;
d.getElementsByName("LC"+i)[0].value = v.len;
d.getElementsByName("CO"+i)[0].value = v.order;
d.getElementsByName("SL"+i)[0].value = v.skip;
d.getElementsByName("RF"+i)[0].checked = v.ref;
d.getElementsByName("CV"+i)[0].checked = v.rev;
});
}
if(c.hw.com) {
resetCOM();
c.hw.com.forEach(e => {
addCOM(e.start, e.len, e.order);
});
}
if (c.hw.btn) {
var b = c.hw.btn;
if (Array.isArray(b.ins)) gId("btns").innerHTML = "";
b.ins.forEach((v,i,a)=>{
addBtn(i,v.pin[0],v.type);
});
d.getElementsByName("TT")[0].value = b.tt;
}
if (c.hw.ir) {
d.getElementsByName("IR")[0].value = c.hw.ir.pin;
d.getElementsByName("IT")[0].value = c.hw.ir.type;
}
if (c.hw.relay) {
d.getElementsByName("RL")[0].value = c.hw.relay.pin;
2024-04-21 11:37:07 +00:00
d.getElementsByName("RM")[0].checked = c.hw.relay.rev;
d.getElementsByName("RO")[0].checked = c.hw.relay.odrain;
2023-01-16 21:12:02 +00:00
}
UI();
}
}
}
2023-06-10 18:43:27 +00:00
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);
2024-09-14 09:39:56 +00:00
for (var j = -1; j < d.max_gpio ; j + + ) {
2023-06-10 18:43:27 +00:00
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
}
}
}
2023-06-12 17:21:14 +00:00
// 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)";
});
}
2023-06-10 18:43:27 +00:00
}
// https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option
function addDropdown(field) {
2024-09-17 14:26:11 +00:00
let sel = cE('select');
2023-06-10 18:43:27 +00:00
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);
2023-06-12 17:21:14 +00:00
sel.setAttribute("onchange", "pinUpd(this)");
2023-06-10 18:43:27 +00:00
// 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
2024-09-17 14:26:11 +00:00
let opt = cE("option");
2023-06-10 18:43:27 +00:00
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;
}
2021-09-23 19:44:24 +00:00
< / script >
< style > @ import url ( "style.css" ) ; < / style >
< / head >
< body onload = "S()" >
< form id = "form_s" name = "Sf" method = "post" >
2023-01-16 21:12:02 +00:00
< div class = "toprow" >
2024-09-17 14:26:11 +00:00
< div class = "helpB" > < button type = "button" onclick = "H('features/settings/#led-settings')" > ?< / button > < / div >
2021-09-23 19:44:24 +00:00
< button type = "button" onclick = "B()" > Back< / button > < button type = "submit" > Save< / button > < hr >
2023-01-16 21:12:02 +00:00
< / div >
< h2 > LED & Hardware setup< / h2 >
Total LEDs: < span id = "lc" > ?< / span > < span id = "pc" > < / span > < br >
< i > Recommended power supply for brightest white:< / i > < br >
< b > < span id = "psu" > ?< / span > < / b > < br >
< span id = "psu2" > < br > < / span >
< br >
2023-11-15 18:37:07 +00:00
Enable automatic brightness limiter: < input type = "checkbox" name = "ABL" onchange = "enABL()" > < br >
2023-01-16 21:12:02 +00:00
< div id = "abl" >
2023-11-15 18:37:07 +00:00
< i > Automatically limits brightness to stay close to the limit.< br >
Keep at < 1A if poweing LEDs directly from the ESP 5V pin!< br >
2024-09-10 13:23:27 +00:00
If using multiple outputs it is recommended to use per-output limiter.< br >
2023-11-15 18:37:07 +00:00
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.< br > < / i >
2023-12-29 22:07:29 +00:00
< div id = "psuMA" > Maximum PSU Current: < input name = "MA" type = "number" class = "xl" min = "250" max = "65000" oninput = "UI()" required > mA< br > < / div >
2024-02-28 17:14:42 +00:00
Use per-output limiter: < input type = "checkbox" name = "PPL" onchange = "UI()" > < br >
2023-11-15 18:37:07 +00:00
< div id = "ppldis" style = "display:none;" >
2024-09-10 13:23:27 +00:00
< i > Make sure you enter correct value for each LED output.< br >
2024-08-22 15:15:12 +00:00
If using multiple outputs with only one PSU, distribute its power proportionally amongst outputs.< / i > < br >
2023-11-15 18:37:07 +00:00
< / div >
2023-05-30 17:36:14 +00:00
< div id = "ampwarning" class = "warn" style = "display: none;" >
2023-01-16 21:12:02 +00:00
⚠ Your power supply provides high current.< br >
To improve the safety of your setup,< br >
please use thick cables,< br >
multiple power injection points and a fuse!< br >
< / div >
< / div >
< h3 > Hardware setup< / h3 >
< div id = "mLC" > LED outputs:< / div >
< hr class = "sml" >
< button type = "button" id = "+" onclick = "addLEDs(1,false)" > +< / button >
< button type = "button" id = "-" onclick = "addLEDs(-1,false)" > -< / button > < br >
2024-09-10 13:23:27 +00:00
LED memory usage: < span id = "m0" > 0< / span > / < span id = "m1" > ?< / span > B< br >
2023-01-16 21:12:02 +00:00
< div id = "dbar" style = "display:inline-block; width: 100px; height: 10px; border-radius: 20px;" > < / div > < br >
2023-05-30 17:36:14 +00:00
< div id = "ledwarning" class = "warn" style = "display: none;" >
2023-01-16 21:12:02 +00:00
⚠ You might run into stability or lag issues.< br >
Use less than < span id = "wreason" > 800 LEDs per output< / span > for the best experience!< br >
< / div >
< hr class = "sml" >
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 >
2023-06-12 17:21:14 +00:00
Use global LED buffer: < input type = "checkbox" name = "LD" onchange = "UI()" > < br >
2023-01-16 21:12:02 +00:00
< hr class = "sml" >
< div id = "color_order_mapping" >
Color Order Override:
< div id = "com_entries" > < / div >
< hr class = "sml" >
< button type = "button" id = "com_add" onclick = "addCOM()" > +< / button >
< button type = "button" id = "com_rem" onclick = "remCOM()" > -< / button > < br >
< / div >
< hr class = "sml" >
< div id = "btns" > < / div >
Disable internal pull-up/down: < input type = "checkbox" name = "IP" > < br >
Touch threshold: < input type = "number" class = "s" min = "0" max = "100" name = "TT" required > < br >
2024-06-29 18:22:47 +00:00
< hr class = "sml" >
2023-01-16 21:12:02 +00:00
IR GPIO: < input type = "number" min = "-1" max = "48" name = "IR" onchange = "UI()" class = "xs" > < select name = "IT" onchange = "UI()" >
< option value = 0 > Remote disabled< / option >
< option value = 1 > 24-key RGB< / option >
< option value = 2 > 24-key with CT< / option >
< option value = 3 > 40-key blue< / option >
< option value = 4 > 44-key RGB< / option >
< option value = 5 > 21-key RGB< / option >
< option value = 6 > 6-key black< / option >
< option value = 7 > 9-key red< / option >
< option value = 8 > JSON remote< / option >
< / select > < span style = "cursor: pointer;" onclick = "off('IR')" > ✕ < / span > < br >
Apply IR change to main segment only: < input type = "checkbox" name = "MSO" > < br >
2024-09-17 14:26:11 +00:00
< div id = "json" style = "display:none;" > JSON file: < input type = "file" name = "data" accept = ".json" > < button type = "button" class = "sml" onclick = "uploadFile(d.Sf.data,'/ir.json')" > Upload< / button > < br > < / div >
2023-01-16 21:12:02 +00:00
< a href = "https://kno.wled.ge/interfaces/infrared/" target = "_blank" > IR info< / a > < br >
2024-06-29 18:22:47 +00:00
< hr class = "sml" >
2024-06-23 12:08:18 +00:00
Relay GPIO: < input type = "number" min = "-1" max = "48" name = "RL" onchange = "UI()" class = "xs" > < span style = "cursor: pointer;" onclick = "off('RL')" > ✕ < / span > < br >
Invert < input type = "checkbox" name = "RM" > Open drain < input type = "checkbox" name = "RO" > < br >
2023-01-16 21:12:02 +00:00
< hr class = "sml" >
2021-09-23 19:44:24 +00:00
< h3 > Defaults< / h3 >
Turn LEDs on after power up/reset: < input type = "checkbox" name = "BO" > < br >
2024-07-05 19:22:05 +00:00
Default brightness: < input name = "CA" type = "number" class = "m" min = "1" max = "255" required > (1-255)< br > < br >
2024-09-10 13:23:27 +00:00
Apply preset < input name = "BP" type = "number" class = "m" min = "0" max = "250" required > at boot (0 uses values from above)< br > < br >
2021-09-23 19:44:24 +00:00
Use Gamma correction for color: < input type = "checkbox" name = "GC" > (strongly recommended)< br >
2023-02-12 12:18:30 +00:00
Use Gamma correction for brightness: < input type = "checkbox" name = "GB" > (not recommended)< br >
Use Gamma value: < input name = "GV" type = "number" class = "m" placeholder = "2.8" min = "1" max = "3" step = "0.1" required > < br > < br >
2021-12-08 09:00:31 +00:00
Brightness factor: < input name = "BF" type = "number" class = "m" min = "1" max = "255" required > %
2021-09-23 19:44:24 +00:00
< h3 > Transitions< / h3 >
2023-11-27 12:39:52 +00:00
Enable transitions: < input type = "checkbox" name = "TF" onchange = "gId('tran').style.display=this.checked?'inline':'none';" > < br >
< span id = "tran" >
Effect blending: < input type = "checkbox" name = "EB" > < br >
2024-09-10 13:23:27 +00:00
Default transition time: < input name = "TD" type = "number" class = "xl" min = "0" max = "65500" > ms< br >
2023-11-27 12:39:52 +00:00
Palette transitions: < input type = "checkbox" name = "PF" > < br >
< / span >
2023-03-25 20:28:30 +00:00
< i > Random Cycle< / i > Palette Time: < input name = "TP" type = "number" class = "m" min = "1" max = "255" > s< br >
2024-02-06 10:06:23 +00:00
Use harmonic < i > Random Cycle< / i > Palette: < input type = "checkbox" name = "TH" > < br >
2021-09-23 19:44:24 +00:00
< h3 > Timed light< / h3 >
2024-09-10 13:23:27 +00:00
Default duration: < input name = "TL" type = "number" class = "m" min = "1" max = "255" required > min< br >
Default target brightness: < input name = "TB" type = "number" class = "m" min = "0" max = "255" required > < br >
2021-09-23 19:44:24 +00:00
Mode:
2023-01-16 21:12:02 +00:00
< select name = "TW" >
2021-09-23 19:44:24 +00:00
< option value = "0" > Wait and set< / option >
< option value = "1" > Fade< / option >
< option value = "2" > Fade Color< / option >
< option value = "3" > Sunrise< / option >
< / select >
2021-11-28 00:21:17 +00:00
< h3 > White management< / h3 >
2023-03-19 10:23:59 +00:00
White Balance correction: < input type = "checkbox" name = "CCT" > < br >
< div id = "wc" >
2023-01-16 21:12:02 +00:00
Global override for Auto-calculate white:< br >
< select name = "AW" >
< option value = 255 > Disabled< / option >
< option value = 0 > None< / option >
< option value = 1 > Brighter< / option >
< option value = 2 > Accurate< / option >
< option value = 3 > Dual< / option >
2023-02-14 00:33:06 +00:00
< option value = 4 > Max< / option >
2023-01-16 21:12:02 +00:00
< / select >
2023-03-19 10:23:59 +00:00
< br >
Calculate CCT from RGB: < input type = "checkbox" name = "CR" > < br >
2024-03-28 15:03:06 +00:00
CCT IC used (Athom 15W): < input type = "checkbox" name = "IC" > < br >
2024-08-30 13:21:16 +00:00
CCT additive blending: < input type = "number" class = "s" min = "0" max = "100" name = "CB" onchange = "UI()" required > %< br >
< i class = "warn" > WARNING: When using H-bridge for reverse polarity (2-wire) CCT LED strip< br > < b > make sure this value is 0< / b > .< br > (ESP32 variants only, ESP8266 does not support H-bridges)< / i >
2023-03-19 10:23:59 +00:00
< / div >
2023-01-16 21:12:02 +00:00
< h3 > Advanced< / h3 >
2024-09-10 13:23:27 +00:00
Palette wrapping:
2021-09-23 19:44:24 +00:00
< select name = "PB" >
< option value = "0" > Linear (wrap if moving)< / option >
< option value = "1" > Linear (always wrap)< / option >
< option value = "2" > Linear (never wrap)< / option >
< option value = "3" > None (not recommended)< / option >
< / select > < br >
2021-12-25 00:30:27 +00:00
Target refresh rate: < input type = "number" class = "s" min = "1" max = "120" name = "FR" required > FPS
2023-01-16 21:12:02 +00:00
< hr class = "sml" >
< div id = "cfg" > Config template: < input type = "file" name = "data2" accept = ".json" > < button type = "button" class = "sml" onclick = "loadCfg(d.Sf.data2)" > Apply< / button > < br > < / div >
< hr >
< button type = "button" onclick = "B()" > Back< / button > < button type = "submit" > Save< / button >
2021-09-23 19:44:24 +00:00
< / form >
2023-01-16 21:12:02 +00:00
< div id = "toast" > < / div >
2021-09-23 19:44:24 +00:00
< / body >
< / html >