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 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) (limited to 'src') 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} + + -- cgit v1.2.3