From 4245a2ecc029f2908970ce3074113603acf1eae7 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 4 Sep 2026 19:52:05 -0700 Subject: 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 Claude-Session: https://claude.ai/code/session_014gVYGi29FjTeLdNTiutkZx --- src/routes/(app)/pos/+page.svelte | 71 +++++++++++++++++++++++++++++++++++++- static/firmware/boot_app0.bin | Bin 0 -> 8192 bytes static/firmware/bootloader.bin | Bin 0 -> 18688 bytes static/firmware/coinos-pos.bin | Bin 0 -> 1298928 bytes static/firmware/manifest.json | 12 +++++++ static/firmware/partitions.bin | Bin 0 -> 3072 bytes 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 static/firmware/boot_app0.bin create mode 100644 static/firmware/bootloader.bin create mode 100644 static/firmware/coinos-pos.bin create mode 100644 static/firmware/manifest.json create mode 100644 static/firmware/partitions.bin 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."; + } + };
@@ -224,7 +283,17 @@ {:else}
- + {#if release} +
+
Latest release: {release.version}
+
Built {release.built} ยท flashes bootloader, partitions and app; keeps your wifi/token config
+ +
+ {:else if releaseError} +
{releaseError}
+ {/if} + + diff --git a/static/firmware/boot_app0.bin b/static/firmware/boot_app0.bin new file mode 100644 index 00000000..13562cab Binary files /dev/null and b/static/firmware/boot_app0.bin differ diff --git a/static/firmware/bootloader.bin b/static/firmware/bootloader.bin new file mode 100644 index 00000000..8d855ec0 Binary files /dev/null and b/static/firmware/bootloader.bin differ diff --git a/static/firmware/coinos-pos.bin b/static/firmware/coinos-pos.bin new file mode 100644 index 00000000..de1ff071 Binary files /dev/null and b/static/firmware/coinos-pos.bin differ diff --git a/static/firmware/manifest.json b/static/firmware/manifest.json new file mode 100644 index 00000000..2ddcf9cc --- /dev/null +++ b/static/firmware/manifest.json @@ -0,0 +1,12 @@ +{ + "version": "2026.09.04-0ec9bae", + "commit": "0ec9bae", + "built": "2026-09-05T02:51:16Z", + "chip": "ESP32-C3", + "parts": [ + { "path": "/firmware/bootloader.bin", "address": 0, "size": 18688 }, + { "path": "/firmware/partitions.bin", "address": 32768, "size": 3072 }, + { "path": "/firmware/boot_app0.bin", "address": 57344, "size": 8192 }, + { "path": "/firmware/coinos-pos.bin", "address": 65536, "size": 1298928 } + ] +} diff --git a/static/firmware/partitions.bin b/static/firmware/partitions.bin new file mode 100644 index 00000000..21800fab Binary files /dev/null and b/static/firmware/partitions.bin differ -- cgit v1.2.3