Files
espresso_frame/server/app/static/device_status_bar.js
T
tfaour 95d69a5512
Build and push server image / build-and-push (push) Successful in 42s
Rewrite battery-remaining estimate around per-wake drop rate
The old estimate used a single linear percent/second rate from the
current discharge cycle's battery_history, which resets to empty on
every recharge -- so "not enough data yet" kept showing up despite the
frame having plenty of history overall, and the rate it did compute was
tied to whatever refresh interval produced it (changing the interval
didn't move the estimate until enough new history accumulated under
the new setting).

Now pulls the last 100 rows from the permanent battery_log table
instead, and averages the *per-wake* percent drop (not per-second) --
recharge jumps are skipped rather than counted as negative drain,
flat/zero-drop wakes still count so the rate isn't overstated, and
more recent steps are weighted more heavily. The per-wake rate then
converts to wall-clock time using the frame's current
refresh_interval_s and quiet-hours settings, so halving the refresh
interval roughly halves the estimate immediately, and quiet hours
correctly stretches it out (fewer wakes/day at the same per-wake cost).
2026-07-22 21:21:17 -04:00

81 lines
2.7 KiB
JavaScript

// Device status bar: always-visible strip (below the page title, above
// the tabs -- see _device_status_bar.html) showing last-seen/firmware/
// battery, so it's not tucked away on just the Stats tab. Shared by
// every frame page; each sets window.FRAME_API before this loads.
let lastDeviceStatus = null;
function renderDeviceStatusBar(device) {
const el = document.getElementById('device-status');
if (!el) return;
el.innerHTML = '';
if (!device || !device.last_seen) {
el.innerHTML = '<p class="sub">The frame hasn\'t checked in yet.</p>';
return;
}
const now = Date.now() / 1000;
const rows = [];
const ago = formatDuration(Math.max(0, now - device.last_seen));
rows.push(['Last seen', `${ago} ago`, device.overdue]);
if (device.firmware_version) {
let fw = `v${device.firmware_version}`;
if (device.firmware_available && device.firmware_available !== device.firmware_version) {
fw += ` (v${device.firmware_available} waiting)`;
}
rows.push(['Firmware', fw, false]);
}
if (device.battery) {
rows.push(['Battery', `${device.battery.percent}%`, false]);
// Shown as soon as there's any battery reading at all, even before
// battery_estimate_s has enough discharge samples in battery_log to
// average (see common.py) -- so it's clear the number is coming, not
// that the feature is broken.
const hasEstimate = device.battery_estimate_s !== null && device.battery_estimate_s !== undefined;
rows.push([
'Est. battery life left',
hasEstimate ? `~${formatDuration(device.battery_estimate_s)}` : 'Not enough data yet',
false,
]);
}
for (const [label, value, alert] of rows) {
const stat = document.createElement('span');
stat.className = 'device-stat' + (alert ? ' alert' : '');
const labelPart = document.createTextNode(label + ': ');
const valuePart = document.createElement('strong');
valuePart.textContent = value;
stat.appendChild(labelPart);
stat.appendChild(valuePart);
el.appendChild(stat);
}
}
async function loadDeviceStatusBar() {
try {
const resp = await fetch(`${window.FRAME_API}/queue`);
if (!resp.ok) {
return;
}
const data = await resp.json();
lastDeviceStatus = data.device;
renderDeviceStatusBar(data.device);
} catch (e) { /* retried on the next poll */ }
}
loadDeviceStatusBar();
document.addEventListener('themechange', () => {
if (lastDeviceStatus) {
renderDeviceStatusBar(lastDeviceStatus);
}
});
// Fast tick: re-renders "Last seen" from already-fetched data every
// second so it counts up smoothly without hitting the server that often.
setInterval(() => {
if (lastDeviceStatus) {
renderDeviceStatusBar(lastDeviceStatus);
}
}, 1000);
setInterval(loadDeviceStatusBar, 10000);