Add configurable battery and USB display behavior

Persist separate display modes and timeouts in seconds for battery and USB power, expose four controls in WebConfig and CLI, and apply one shared policy across display-equipped roles.

Detect R8 external power from a USB host or calibrated battery voltage above 4.21 V, with cached sampling and hysteresis. Add policy, persistence, browser, and R8 power-detection regression coverage.
This commit is contained in:
mikecarper committed 2026-09-10 21:03:15 -07:00
1 parent d83153e935
commit a814a00ce7
39 files changed
+3187 -2488

No files matched your search

+34
View File
@@ -393,6 +393,18 @@ body.tab-cli{padding-bottom:0}
</div>
<div class="f"><label>TX power (dBm)</label><input type="number" data-k="tx" min="-9" max="30" style="max-width:120px"></div>
</div>
<div class="card hide" id="display-settings">
<h2>Display</h2>
<div class="row">
<div class="f"><label>On battery</label><select data-k="display.mode"><option value="off">Off</option><option value="on">On</option><option value="button">Button</option><option value="pairing">Pairing only</option><option value="button-pairing">Button + pairing</option><option value="automatic">Automatic</option></select></div>
<div class="f"><label>Timeout (seconds)</label><input type="number" data-k="display.timeout" min="1" max="3600" step="1"></div>
</div>
<div class="row">
<div class="f"><label>On USB power</label><select data-k="display.usb.mode"><option value="off">Off</option><option value="on">On</option><option value="button">Button</option><option value="pairing">Pairing only</option><option value="button-pairing">Button + pairing</option><option value="automatic">Automatic</option></select></div>
<div class="f"><label>Timeout (seconds)</label><input type="number" data-k="display.usb.timeout" min="1" max="3600" step="1"></div>
</div>
<div class="hint">Automatic shows startup and messages while the app is disconnected. Connecting blanks the display; a button or pairing request can wake it. USB power detection depends on the board.</div>
</div>
<div class="card">
<h2>Advanced</h2>
<div class="row">
@@ -800,6 +812,10 @@ function cfgVal(k){ // map a `set` key to its current value string, from st.cfg
case"radio.fem.rxgain":return r.fem_rxgain?"on":"off";
case"radio.rxps":return rxpsCfgValue(r);
case"powersaving":return r.powersaving?"on":"off";
case"display.mode":return (st.cfg.display||{}).mode||"button";
case"display.timeout":return String((st.cfg.display||{}).timeout||15);
case"display.usb.mode":return (st.cfg.display||{}).usb_mode||"button";
case"display.usb.timeout":return String((st.cfg.display||{}).usb_timeout||15);
case"repeat":return r.repeat?"on":"off";
case"flood.max":return String(r.flood_max);
case"flood.max.advert":return String(r.flood_max_advert);
@@ -945,6 +961,9 @@ function loadConfig(loadCapture){
});
$$("[data-cfm]").forEach(function(el){el.value=""});
$$("[data-rg]").forEach(function(el){el.value=String(c.radio[el.dataset.rg]);el.classList.remove("dirty")});
$("#display-settings").classList.toggle("hide",!c.display);
$$('#display-settings option[value="pairing"],#display-settings option[value="button-pairing"]').forEach(function(el){el.hidden=!(c.display&&c.display.pairing);el.disabled=el.hidden});
syncDisplayTimeouts();
loadRxps(c.radio);
st.orig.radio=radioCombo();afHint();
for(var i=1;i<=st.nslots;i++){refreshSlotFields($("#app-slots"),i);refreshSlotFields($("#wz-slots"),i)}
@@ -957,10 +976,17 @@ function loadConfig(loadCapture){
return true;
});
}
function syncDisplayTimeouts(){
["display","display.usb"].forEach(function(prefix){
var mode=$('[data-k="'+prefix+'.mode"]').value;
$('[data-k="'+prefix+'.timeout"]').disabled=(mode==="off"||mode==="on"||mode==="pairing");
});
}
function markDirty(k,v,el){
if(v===st.orig[k]){delete st.dirty[k]}else{st.dirty[k]=v}
if(el)el.classList.toggle("dirty",k in st.dirty);
if(k==="af")afHint();
if(k==="display.mode"||k==="display.usb.mode")syncDisplayTimeouts();
updateSaveBar();
}
// The CLI's `dutycycle` is not its own pref - it just writes airtime_factor as
@@ -1860,6 +1886,10 @@ var CLI_VERBS=[
mode: 0 = get and set, 1 = get only, 2 = set only
values: enum offered as value completions after `set <key> ` */
var CLI_KEYS=[
["display.mode","Display on battery",0,"off|on|button|pairing|button-pairing|automatic"],
["display.timeout","Battery display timeout in seconds (1-3600)",0],
["display.usb.mode","Display on USB power",0,"off|on|button|pairing|button-pairing|automatic"],
["display.usb.timeout","USB display timeout in seconds (1-3600)",0],
["name","Node name",0],
["lat","Advert latitude",0],
["lon","Advert longitude",0],
@@ -2000,6 +2030,10 @@ var CLI_COMPANION_VERBS=[
["reboot","Restart the node"]
];
var CLI_COMPANION_KEYS=[
["display.mode","Display on battery",0,"off|on|button|pairing|button-pairing|automatic"],
["display.timeout","Battery display timeout in seconds (1-3600)",0],
["display.usb.mode","Display on USB power",0,"off|on|button|pairing|button-pairing|automatic"],
["display.usb.timeout","USB display timeout in seconds (1-3600)",0],
["prv.key","Read this node's private identity key",1],
["name","Node name",0], ["role","Node role",1],
["lat","Advert latitude",0], ["lon","Advert longitude",0],