Files
open-battery-information-web/js/obi.js
T
2026-08-19 21:35:27 +03:00

192 lines
5.8 KiB
JavaScript

/* Open Battery Information web app shell — wizard flow.
Guides the user through: 1. Connect 2. Read battery 3. Results. */
(function () {
"use strict";
const { debug, toast } = OBI.util;
const $ = (id) => document.getElementById(id);
const els = {
moduleSelect: $("module-select"),
interfaceSelect: $("interface-select"),
resetCheck: $("reset-check"),
connectBtn: $("connect-btn"),
ifaceState: $("iface-state"),
ifaceWidget: $("iface-widget"),
serialWarning: $("serial-warning"),
readBtn: $("read-btn"),
readHint: $("read-hint"),
resultsSummary: $("results-summary"),
readAgainBtn: $("read-again-btn"),
moduleContent: $("module-content"),
};
const stepItems = { 1: $("step-1"), 2: $("step-2"), 3: $("step-3") };
const panels = { 1: $("panel-connect"), 2: $("panel-read"), 3: $("panel-results") };
let iface = null; /* interface controller */
let moduleWidget = null; /* module controller */
let connected = false;
/* ---------- stepper ---------- */
function goToStep(n) {
for (const [k, el] of Object.entries(panels)) {
el.classList.toggle("active", Number(k) === n);
}
for (const [k, el] of Object.entries(stepItems)) {
el.classList.toggle("active", Number(k) === n);
el.classList.toggle("done", Number(k) < n);
}
}
function markStepDone(n) {
const el = stepItems[n];
if (el) {
el.classList.remove("active");
el.classList.add("done");
}
}
function resetSteps() {
for (const el of Object.values(stepItems)) {
el.classList.remove("active", "done");
}
for (const el of Object.values(panels)) {
el.classList.remove("active");
}
stepItems[1].classList.add("active");
panels[1].classList.add("active");
}
/* ---------- interface ---------- */
function handleConnectChange(v) {
connected = v;
updateConnectUi();
if (v) {
markStepDone(1);
goToStep(2);
} else {
resetSteps();
}
}
function updateConnectUi() {
els.connectBtn.textContent = connected ? "Disconnect" : "Connect";
els.connectBtn.classList.toggle("primary", !connected);
els.ifaceState.textContent = connected ? "Connected" : "Not connected";
els.ifaceState.className = "badge " + (connected ? "ok" : "");
els.readBtn.disabled = !connected;
}
function setupInterface() {
const key = els.interfaceSelect.value;
const ifaceDef = OBI.interfaces[key];
if (!ifaceDef) return;
if (iface) {
iface.disconnect && iface.disconnect();
iface = null;
}
els.ifaceWidget.innerHTML = "";
iface = ifaceDef.createWidget(els.ifaceWidget, OBI, {
onConnect: handleConnectChange,
});
connected = iface.isConnected();
updateConnectUi();
}
/* ---------- module ---------- */
function setupModule() {
const key = els.moduleSelect.value;
const moduleDef = OBI.modules[key];
if (!moduleDef) return;
if (moduleWidget) {
moduleWidget.destroy();
moduleWidget = null;
}
els.moduleContent.innerHTML = "";
moduleWidget = moduleDef.createWidget(els.moduleContent);
if (iface) moduleWidget.setInterface(iface);
}
/* ---------- reading ---------- */
async function doRead() {
if (!connected || !iface || !moduleWidget) return;
els.readBtn.disabled = true;
els.readBtn.textContent = "Reading…";
els.readHint.textContent = "Talking to the battery — this takes a few seconds…";
const ok = await moduleWidget.readBattery();
if (ok) {
toast("Battery read successfully");
els.readHint.textContent =
"Battery read successfully. Open the Results step or press Read again to refresh.";
markStepDone(2);
els.resultsSummary.textContent =
"Battery read — details below. Select rows and use Copy, or Read again to refresh.";
goToStep(3);
} else {
els.readHint.textContent =
"Could not read the battery. Check it is seated correctly and connected, then try again.";
}
els.readBtn.textContent = "Read battery";
els.readBtn.disabled = !connected;
}
/* ---------- wiring ---------- */
els.connectBtn.addEventListener("click", async () => {
if (!iface) return;
if (connected) {
await iface.disconnect();
} else {
await iface.connect(els.resetCheck.checked);
}
});
els.readBtn.addEventListener("click", doRead);
els.readAgainBtn.addEventListener("click", doRead);
els.moduleSelect.addEventListener("change", () => {
setupModule();
resetSteps();
});
els.interfaceSelect.addEventListener("change", () => {
setupInterface();
setupModule();
resetSteps();
});
/* ---------- init ---------- */
function init() {
if (!("serial" in navigator)) {
debug.error(
"Web Serial is not available in this browser. Use Chrome, Edge, Opera or Firefox 151+ on desktop over HTTPS (or localhost), or the desktop app."
);
if (els.serialWarning) els.serialWarning.hidden = false;
els.connectBtn.disabled = true;
}
const names = Object.keys(OBI.modules).sort();
for (const name of names) {
const opt = document.createElement("option");
opt.value = name;
opt.textContent = OBI.modules[name].getDisplayName();
els.moduleSelect.appendChild(opt);
}
if (names.length) els.moduleSelect.value = names[0];
const ifaceNames = Object.keys(OBI.interfaces).sort();
for (const name of ifaceNames) {
const opt = document.createElement("option");
opt.value = name;
opt.textContent = OBI.interfaces[name].getDisplayName();
els.interfaceSelect.appendChild(opt);
}
if (ifaceNames.length) els.interfaceSelect.value = ifaceNames[0];
setupInterface();
setupModule();
resetSteps();
}
document.addEventListener("DOMContentLoaded", init);
})();