192 lines
5.8 KiB
JavaScript
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);
|
||
|
|
})();
|