From f637a65abd0c477b94d0fff4b5c75d73e1871809 Mon Sep 17 00:00:00 2001 From: Me Here Date: Sat, 30 May 2026 07:28:12 -0500 Subject: [PATCH] Push reliability: 10s ACK timeout + per-chunk flush + periodic GC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Push to 0.0.14 stalls at ~150 chunks with the MIDI badge going gray (=device silent for >1s). It's an occasional slow flash flush on the device side — the file buffer fills, flush takes several seconds, the editor's 4s timeout cuts it off too early. - Editor: bump per-chunk ACK timeout 4s -> 10s; progress log every 25 chunks now with elapsed seconds so you can see it advancing through a slow chunk. - Device 0.0.14: flush per chunk (small, predictable per-chunk cost instead of infrequent multi-second bursts) + gc.collect() every 50 chunks to keep the heap fresh during a long push. Co-Authored-By: Claude Opus 4.7 (1M context) --- editor.html | 15 ++++++++++----- pico-cp/__pycache__/app.cpython-312.pyc | Bin 94235 -> 94521 bytes pico-cp/app.py | 10 +++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/editor.html b/editor.html index c59e79f..7117e86 100644 --- a/editor.html +++ b/editor.html @@ -1256,19 +1256,24 @@ function _b64(u8) { let s = ""; for (let i = 0; i < u8.length; i++) s += String. // A/B-installs + reboots. CH is small (and a multiple of 4) so a chunk fits the Pico's USB-MIDI RX buffer. async function _pushFirmware(b64) { _send([0xF0, 0x7D, 0x21, 0xF7]); - if (await _ack(3000) !== true) return "handshake"; + if (await _ack(5000) !== true) return "handshake"; const CH = 64, total = Math.ceil(b64.length / CH); let done = 0; + const t0 = Date.now(); for (let o = 0; o < b64.length; o += CH) { const part = b64.slice(o, o + CH); const msg = [0xF0, 0x7D, 0x22]; for (let i = 0; i < part.length; i++) msg.push(part.charCodeAt(i)); // base64 is ASCII msg.push(0xF7); _send(msg); - const a = await _ack(4000); + // Generous per-chunk timeout: an occasional flash flush on the device can take several seconds. + const a = await _ack(10000); if (a !== true) return "chunk " + (done + 1) + "/" + total + (a === false ? " rejected" : " no-ack"); - if (++done % 50 === 0) console.log("[fw] pushed", done, "/", total, "chunks"); + if (++done % 25 === 0) { + const el = ((Date.now() - t0) / 1000).toFixed(1); + console.log("[fw] pushed " + done + " / " + total + " chunks (" + el + " s)"); + } } - console.log("[fw] all", total, "chunks sent; committing..."); + console.log("[fw] all " + total + " chunks sent; committing..."); _send([0xF0, 0x7D, 0x23, 0xF7]); - return (await _ack(6000)) === true ? null : "verify"; + return (await _ack(10000)) === true ? null : "verify"; } function _pickBinary() { // offline fallback: choose an app.mpy -> Uint8Array (or null if cancelled) return new Promise(async (res) => { diff --git a/pico-cp/__pycache__/app.cpython-312.pyc b/pico-cp/__pycache__/app.cpython-312.pyc index c6a226a12e8c75e237e03b8a5da39a044be642ca..f1ddf6e582c39ab379d29ac21491adca90d25725 100644 GIT binary patch delta 7636 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 UP3vrJw)^ZqTq#kTm(Yp-2Px0fE&u=k diff --git a/pico-cp/app.py b/pico-cp/app.py index 2df5f11..8a923a2 100644 --- a/pico-cp/app.py +++ b/pico-cp/app.py @@ -407,7 +407,7 @@ class App: self.midi_in = usb_midi.ports[0] if (MIDI_ENABLED and usb_midi and len(usb_midi.ports) > 0) else None self._mbuf = bytearray(64); self.midi_host = False; self.last_midi_in = 0.0 self._sx = bytearray(); self._sxon = False # USB-MIDI SysEx assembler (clock + pushed programs) - self._fw = None # chunked firmware transfer: staging file handle + self._fw = None; self._fw_n = 0 # chunked firmware transfer: staging file handle + chunk counter self.led = RGB(P_RGB) self.spk = pwmio.PWMOut(P_SPK, frequency=1600, variable_frequency=True, duty_cycle=0) self.spk_off = 0 @@ -1088,13 +1088,17 @@ class App: try: try: self._fw.close() except Exception: pass - self._fw = open("/app.new", "wb"); self._ack(True) + self._fw = open("/app.new", "wb"); self._fw_n = 0; self._ack(True) except Exception: # read-only (editor mode) / no space self._fw = None; self._ack(False) elif cmd == 0x22: # DATA: a base64 chunk (multiple of 4) -> decode -> append try: if self._fw is None or a2b_base64 is None: raise OSError() - self._fw.write(a2b_base64(bytes(sx[2:]))); self._ack(True) + self._fw.write(a2b_base64(bytes(sx[2:]))) + self._fw.flush() # small, predictable per-chunk flush (no slow burst flushes later) + self._fw_n += 1 + if self._fw_n % 50 == 0: gc.collect() # keep the heap fresh during a long push + self._ack(True) except Exception: try: self._fw.close() except Exception: pass