aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-04 19:52:05 -0700
committerAdam Soltys <[email protected]>2026-09-04 20:12:59 -0700
commit4245a2ecc029f2908970ce3074113603acf1eae7 (patch)
treebd13a6b29a81b652ca98ae26a3fe41001f9dd334 /src
parentada2d0dbb5442fc6828d4521dd2b32ad955c7cb3 (diff)
downloadcoinos-ui-4245a2ecc029f2908970ce3074113603acf1eae7.tar.gz
coinos-ui-4245a2ecc029f2908970ce3074113603acf1eae7.zip
pos: offer "Flash latest release" from a published firmware manifest
Host the ESP32-C3 firmware under static/firmware (bootloader, partition table, boot_app0, app) with a manifest.json written by coinos-pos's publish.sh. The flasher fetches the manifest, downloads every part, verifies sizes, flashes them at their offsets and resets the device. The config partition is not in the manifest so credentials survive. Manual .bin upload stays available underneath. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_014gVYGi29FjTeLdNTiutkZx
Diffstat (limited to 'src')
-rw-r--r--src/routes/(app)/pos/+page.svelte71
1 files changed, 70 insertions, 1 deletions
diff --git a/src/routes/(app)/pos/+page.svelte b/src/routes/(app)/pos/+page.svelte
index 5ad1c99e..eb074b1a 100644
--- a/src/routes/(app)/pos/+page.svelte
+++ b/src/routes/(app)/pos/+page.svelte
@@ -48,6 +48,17 @@
let fwDone = $state(false);
let fwError = $state("");
+ // --- Latest published release (see coinos-pos/publish.sh) ---
+ /** @type {any} */
+ let release = $state(null); // manifest.json contents
+ let releaseError = $state("");
+ $effect(() => {
+ fetch("/firmware/manifest.json", { cache: "no-store" })
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
+ .then((m) => (release = m))
+ .catch((e) => (releaseError = `No published release: ${e.message}`));
+ });
+
// Simple tab toggle
let tab = $state("config"); // "config" | "firmware"
@@ -170,6 +181,54 @@
fwError = e?.message || "Flash failed.";
}
};
+
+ // Flash every part of the published release at its offset (bootloader,
+ // partition table, OTA data, app). The LittleFS config partition is not
+ // part of the manifest, so device credentials survive.
+ let flashRelease = async () => {
+ if (!esploader) {
+ fwError = "Not connected.";
+ return;
+ }
+ if (!release?.parts?.length) {
+ fwError = "No release available.";
+ return;
+ }
+ fwError = "";
+ fwDone = false;
+ fwProgress = 0;
+
+ try {
+ const fileArray = [];
+ for (const part of release.parts) {
+ const r = await fetch(`${part.path}?v=${encodeURIComponent(release.version)}`, { cache: "no-store" });
+ if (!r.ok) throw new Error(`Download failed: ${part.path} (HTTP ${r.status})`);
+ const buf = new Uint8Array(await r.arrayBuffer());
+ if (part.size && buf.length !== part.size)
+ throw new Error(`Size mismatch for ${part.path}: got ${buf.length}, expected ${part.size}`);
+ fileArray.push({ data: bs(buf), address: Number(part.address) });
+ }
+
+ const totals = fileArray.map((f) => f.data.length);
+ const grand = totals.reduce((a, b) => a + b, 0);
+ await esploader.writeFlash({
+ fileArray,
+ flashSize: "keep",
+ eraseAll: false,
+ compress: true,
+ reportProgress: (fileIndex, written, total) => {
+ const before = totals.slice(0, fileIndex).reduce((a, b) => a + b, 0);
+ fwProgress = Math.floor(((before + (written / total) * totals[fileIndex]) / grand) * 100);
+ },
+ });
+
+ fwDone = true;
+ await resetDevice();
+ } catch (e) {
+ console.error(e);
+ fwError = e?.message || "Flash failed.";
+ }
+ };
</script>
<div class="container px-4 max-w-lg mx-auto space-y-5 mt-20">
@@ -224,7 +283,17 @@
{:else}
<!-- NEW FIRMWARE FLOW -->
<div class="space-y-3">
- <label class="label">Select .ino.bin (firmware)</label>
+ {#if release}
+ <div class="p-3 rounded-lg border border-current/20 space-y-2">
+ <div class="font-semibold">Latest release: {release.version}</div>
+ <div class="text-sm opacity-70">Built {release.built} · flashes bootloader, partitions and app; keeps your wifi/token config</div>
+ <button class="btn" onclick={flashRelease}>Flash latest release</button>
+ </div>
+ {:else if releaseError}
+ <div class="text-sm opacity-70">{releaseError}</div>
+ {/if}
+
+ <label class="label">Or select a .ino.bin (firmware)</label>
<input type="file" accept=".bin,application/octet-stream" class="input" onchange={onPickFw} />
<label class="label">Firmware address (hex, default 0x10000)</label>