Firmware update: file-picker fallback so the OFFLINE on-device editor can update too
The editor that ships on the device opens via file://, whose null origin can't fetch() anything — so "Update firmware" died at the download-the-latest step (CORS-blocked) before the USB-MIDI push. Now _firmwareSource() tries the site (same-origin on the https editor; absolute URL when online) and, failing that, lets the user pick app.py — mirroring the programs.json download/drag fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
72bf3a2da2
commit
e24a39e4e8
1 changed files with 28 additions and 6 deletions
34
editor.html
34
editor.html
|
|
@ -1189,14 +1189,13 @@ async function updateFirmware() { // A/B firmware update over USB-MIDI, with a
|
|||
if (!(await _ensureMidi()) || !_midiOutputs().length)
|
||||
return alert("Connect the PM_K-1 (Chrome/Edge/Firefox), then try again.");
|
||||
const dev = await _queryDeviceVersion();
|
||||
let src;
|
||||
try { src = await (await fetch("/pico-cp-app.py", { cache: "no-store" })).text(); }
|
||||
catch (e) { return alert("Couldn't fetch the latest firmware from the site."); }
|
||||
const src = await _firmwareSource();
|
||||
if (src == null) return; // offline + user cancelled the picker
|
||||
const m = src.match(/APP_VERSION\s*=\s*["']([^"']+)["']/); const latest = m && m[1];
|
||||
if (!latest) return alert("Couldn't read the latest firmware version.");
|
||||
if (!latest) return alert("That file isn't PM_K-1 firmware (no APP_VERSION line).");
|
||||
const upToDate = dev && dev === latest;
|
||||
if (!confirm("Device firmware: " + (dev || "unknown") + "\nLatest: " + latest +
|
||||
(upToDate ? "\n\nYou're up to date. Re-install anyway?"
|
||||
if (!confirm("Device firmware: " + (dev || "unknown") + "\nNew build: " + latest +
|
||||
(upToDate ? "\n\nSame version. Re-install anyway?"
|
||||
: "\n\nUpdate now? The device reboots, runs the new build, and auto-rolls-back if it fails to start."))) return;
|
||||
const bytes = [0xF0, 0x7D, 0x20];
|
||||
for (let i = 0; i < src.length; i++) bytes.push(src.charCodeAt(i) & 0x7F); // app.py is ASCII
|
||||
|
|
@ -1208,6 +1207,29 @@ async function updateFirmware() { // A/B firmware update over USB-MIDI, with a
|
|||
: ok === false ? "The device is in editor mode (read-only to the updater). Reboot it normally (don't hold A) and try again."
|
||||
: "No acknowledgement from the device. Make sure it's connected and not in editor mode — or drag app.py onto the drive in editor mode.");
|
||||
}
|
||||
// Where the new app.py comes from: the site when online (the https editor, same-origin), else let the user
|
||||
// pick the file — so the OFFLINE editor that ships ON the device can update too (file:// pages can't fetch).
|
||||
async function _firmwareSource() {
|
||||
for (const url of ["/pico-cp-app.py", "https://metronome.varasys.io/pico-cp-app.py"]) {
|
||||
try { const r = await fetch(url, { cache: "no-store" }); if (r.ok) return await r.text(); } catch (_) {}
|
||||
}
|
||||
alert("Can't reach the site from this offline copy.\n\nPick the firmware file (app.py) to flash — download it from\n" +
|
||||
"metronome.varasys.io/pico-cp-app.py, or just open the online editor at\nmetronome.varasys.io/editor.html (it updates automatically).");
|
||||
return await _pickFile(".py,text/x-python,text/plain", "PM_K-1 firmware (app.py)", { "text/x-python": [".py"] });
|
||||
}
|
||||
function _pickFile(accept, desc, fsTypes) { // resolve to the chosen file's text, or null if cancelled
|
||||
return new Promise(async (res) => {
|
||||
if (window.showOpenFilePicker) {
|
||||
try { const [h] = await showOpenFilePicker({ types: [{ description: desc, accept: fsTypes }] });
|
||||
return res(await (await h.getFile()).text()); }
|
||||
catch (e) { if (e.name === "AbortError") return res(null); }
|
||||
}
|
||||
const inp = document.createElement("input"); inp.type = "file"; inp.accept = accept;
|
||||
inp.onchange = () => { inp.files[0] ? inp.files[0].text().then(res) : res(null); };
|
||||
inp.oncancel = () => res(null);
|
||||
inp.click();
|
||||
});
|
||||
}
|
||||
|
||||
// Apply a shared link on load. Returns true if it set the metronome state.
|
||||
function applyHashShare() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue