diff options
| author | Adam Soltys <[email protected]> | 2026-09-04 19:52:05 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-04 20:12:59 -0700 |
| commit | 4245a2ecc029f2908970ce3074113603acf1eae7 (patch) | |
| tree | bd13a6b29a81b652ca98ae26a3fe41001f9dd334 /src | |
| parent | ada2d0dbb5442fc6828d4521dd2b32ad955c7cb3 (diff) | |
| download | coinos-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.svelte | 71 |
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> |
