Skip to content
135 changes: 135 additions & 0 deletions wled00/data/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,138 @@ function sendDDP(ws, start, len, colors) {
}
return true;
}

// Pin utilities
function getOwnerName(o,t,n) {
// Use firmware-provided name if available
if(n) return n;
if(!o) return "System"; // no owner provided
if(o===0x85){ return getBtnTypeName(t); } // button pin
return "UM #"+o;
}
function getBtnTypeName(t) {
var n=["None","Reserved","Push","Push Inv","Switch","PIR","Touch","Analog","Analog Inv","Touch Switch"];
var label = n[t] || "?";
return 'Button <span style="font-size:10px;color:#888">'+label+'</span>';
}
function getCaps(p,c) {
var r=[];
// Use touch info from settings endpoint
if(d.touch && d.touch.includes(p)) r.push("Touch");
if(d.ro_gpio && d.ro_gpio.includes(p)) r.push("Input Only");
// Use other caps from JSON (Analog, Boot, Input Only)
if(c&0x02) r.push("Analog");
if(c&0x08) r.push("Flash Boot");
if(c&0x10) r.push("Bootstrap");
return r.length?r.join(", "):"-";
}

// Fetch GPIO caps (/settings/s.js?p=11) then pin occupancy (/json/pins) with retry.
// Caches result in d.pinsData. Calls cb() when ready (or on failure).
// If page already loaded its own s.js (d.max_gpio set), skips caps load and goes straight to pins fetch.
function fetchPinInfo(cb, retries=5) {
if (d.pinsData) { cb&&cb(); return; }
var done=false, fr=retries;
function doFetch() {
fetch(getURL('/json/pins'))
.then(r=>r.json())
.then(j=>{ if(!done){done=true; d.pinsData=j.pins||[]; cb&&cb();} })
.catch(()=>{ fr-->0 ? setTimeout(doFetch,100) : (!done&&(done=true,d.pinsData=[],cb&&cb())); });
}
if (d.max_gpio) { doFetch(); return; }
// Load GPIO caps from s.js?p=11 first (sets d.rsvd/ro_gpio/max_gpio/touch/adc/um_p)
d.max_gpio=50; d.rsvd=[]; d.ro_gpio=[]; d.touch=[]; d.adc=[]; d.um_p=[];
var cr=retries;
function tryCaps() {
var s=cE("script"); s.src=getURL('/settings/s.js?p=11');
d.body.appendChild(s);
s.onload=function(){ GetV(); doFetch(); };
s.onerror=function(){ cr-->0 ? setTimeout(tryCaps,100) : doFetch(); };
}
tryCaps();
}

// Pin dropdown utilities
// Create or rebuild a pin <select> from an <input> or existing <select>
// name: form field name, requirement flags bitmask: 1=output, 2=touch, 4=ADC
function makePinSelect(name, flags) {
let el = gN(name);
if (!el) return null;
let v = parseInt(el.value);
if (isNaN(v)) v = -1;

let sel;
if (el.tagName === "SELECT") {
sel = el;
while (sel.lastChild) sel.lastChild.remove();
} else {
sel = cE('select');
sel.classList.add("pin");
sel.name = el.name;
if (el.required) sel.required = true;
let oc = el.getAttribute("onchange");
if (oc) sel.setAttribute("onchange", oc);
el.parentElement.replaceChild(sel, el);
}

let hasV = false;
for (let j = -1; j < (d.max_gpio||0); j++) {
if (j > -1 && d.rsvd && d.rsvd.includes(j)) continue;
if (j > -1 && (flags & 1) && d.ro_gpio && d.ro_gpio.includes(j)) continue;
if (j > -1 && (flags & 2) && (!d.touch || !d.touch.includes(j))) continue;
if (j > -1 && (flags & 4) && (!d.adc || !d.adc.includes(j))) continue;

let pInfo = d.pinsData && d.pinsData.find(p => p.p === j);
let used = j > -1 && pInfo && pInfo.a && j !== v;
let txt = j === -1 ? "unused" : `${j}`;
if (used) txt += ` (${getOwnerName(pInfo.o, pInfo.t, pInfo.n)})`;
// if (j > -1 && d.ro_gpio && d.ro_gpio.includes(j)) txt += " (R/O)"; // read only pins note: removed as pin is not shown for outputs

let opt = cE("option");
opt.value = j;
opt.text = txt;
sel.appendChild(opt);

if (j === v) { opt.selected = true; hasV = true; }
else if (used) opt.disabled = true;
}

// Safety for invalid pins currently saved
if (!hasV && v >= 0) {
let opt = cE("option");
opt.value = v; opt.text = v + " ⚠"; opt.selected = true;
sel.insertBefore(opt, sel.options[1]);
}
sel.dataset.val = v;
return sel;
}

// Convert pin <select> back to <input type="number">
function unmakePinSelect(name) {
let sel = gN(name);
if (!sel || sel.tagName !== "SELECT") return null;
let inp = cE('input');
inp.type = "number";
inp.name = sel.name;
inp.value = sel.value;
inp.className = "s";
if (sel.required) inp.required = true;
let oc = sel.getAttribute("onchange");
if (oc) inp.setAttribute("onchange", oc);
sel.parentElement.replaceChild(inp, sel);
return inp;
}
// Add option to select, auto-select matching data-val
function addOption(sel, txt, val) {
if (!sel) return null;
let opt = cE("option");
opt.value = val;
opt.text = txt;
sel.appendChild(opt);
if (sel.dataset.val !== undefined) {
for (let i = 0; i < sel.options.length; i++) {
if (sel.options[i].value == sel.dataset.val) { sel.selectedIndex = i; break; }
}
}
return opt;
}
Loading
Loading