From d1088aefc2158f1b907e810f5e233c522dc745eb Mon Sep 17 00:00:00 2001 From: David Montero Date: Mon, 15 Jun 2026 18:55:13 +0200 Subject: [PATCH] fix(rp2040): Pico W always boots the W firmware (no `import network` crash) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two robustness fixes for the paid-WiFi open-core split: 1. A pi-pico-w board now boots the RPI_PICO_W firmware variant (which has the `network` module) based on its BOARD KIND, not on whether the WiFi peripheral happens to be attached. Previously the variant was `pioPeripheral ? 'pico-w' : 'pico'`, so any moment the peripheral was absent (see #2) booted the plain Pico firmware and a Pico W sketch crashed with "ImportError: no module named 'network'". Store boardKind in attachPioPeripheral and pick the variant from it. (OSS: 'pico-w' isn't registered, so firmwareConfig falls back to 'pico' — a self-hosted Pico W has no WiFi engine anyway.) 2. Re-attach the PIO peripheral in loadMicroPythonProgram before loading firmware. An example deep-link adds the board during render, which races the pro overlay's async mountPro that installs the CYW43 factory — so the board-add attach returned null and a PAID user's Pico W booted plain firmware too. attachPioPeripheral is idempotent; by run time the factory is installed, so a paid user gets the W peripheral and real WiFi. --- frontend/src/simulation/RP2040Simulator.ts | 22 ++++++++++++++++++---- frontend/src/store/useSimulatorStore.ts | 9 +++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/frontend/src/simulation/RP2040Simulator.ts b/frontend/src/simulation/RP2040Simulator.ts index 6104d8cf..8a666b84 100644 --- a/frontend/src/simulation/RP2040Simulator.ts +++ b/frontend/src/simulation/RP2040Simulator.ts @@ -165,6 +165,12 @@ export class RP2040Simulator { // in OSS (no factory installed); attached for boards a factory supports. private pioPeripheral: PioPeripheral | null = null; private pioHookedFifos: Array<{ restore: () => void }> = []; + // The board kind this simulator runs (set by attachPioPeripheral). A + // 'pi-pico-w' boots the RPI_PICO_W firmware (with the `network` module) + // regardless of whether a WiFi peripheral attached, so a Pico W sketch never + // crashes with "ImportError: no module named 'network'" — even if the pro + // factory hadn't installed yet when the board was added. + private boardKind = ''; /** Serial output callback — fires for each byte the Pico sends on UART0 (or USBCDC in MicroPython mode) */ public onSerialData: ((char: string) => void) | null = null; @@ -268,10 +274,14 @@ export class RP2040Simulator { files: Array<{ name: string; content: string }>, onProgress?: (loaded: number, total: number) => void, ): Promise { - // A WiFi/gSPI peripheral (the pro overlay's CYW43) is attached only for - // pi-pico-w boards; its presence selects the RPI_PICO_W firmware variant - // (network + driver + bigger LittleFS). OSS has no peripheral -> 'pico'. - const variant = this.pioPeripheral ? 'pico-w' : 'pico'; + // A pi-pico-w boots the RPI_PICO_W firmware variant (network + driver + + // bigger LittleFS) whether or not a WiFi peripheral attached — so a Pico W + // sketch never crashes on `import network`. The pro overlay registers that + // variant; in OSS it isn't registered and firmwareConfig() falls back to + // 'pico' (a self-hosted Pico W has no WiFi anyway). The pioPeripheral check + // stays as a belt-and-suspenders for any future factory-backed board. + const variant = + this.boardKind === 'pi-pico-w' || this.pioPeripheral ? 'pico-w' : 'pico'; console.log(`[RP2040] Loading MicroPython firmware (${variant})...`); // 1. Get MicroPython UF2 firmware (cached in IndexedDB) @@ -385,6 +395,10 @@ export class RP2040Simulator { * fragile FIFO plumbing + GPIO24 host-wake lifecycle stay here. */ attachPioPeripheral(boardKind: string, boardId: string): PioPeripheral | null { + // Record the kind even when no peripheral attaches (free user / OSS / + // factory-not-installed-yet) so loadMicroPython still picks the W firmware + // for a pi-pico-w board. + this.boardKind = boardKind; if (this.pioPeripheral) return this.pioPeripheral; const peripheral = createPioPeripheral(boardKind, boardId); if (!peripheral) return null; diff --git a/frontend/src/store/useSimulatorStore.ts b/frontend/src/store/useSimulatorStore.ts index a1911074..e7eb82fc 100644 --- a/frontend/src/store/useSimulatorStore.ts +++ b/frontend/src/store/useSimulatorStore.ts @@ -1557,6 +1557,15 @@ export const useSimulatorStore = create((set, get) => { // RP2040 path: load firmware + filesystem in browser const sim = getBoardSimulator(boardId); if (!(sim instanceof RP2040Simulator)) return; + // (Re)attach the PIO peripheral before loading firmware. An example + // deep-link adds the board during render, which can race the pro + // overlay's async mountPro that installs the CYW43 factory — so the + // board-add attach returned null and a paid user's Pico W would boot + // the plain firmware (no `network` -> ImportError). attachPioPeripheral + // is idempotent; by run time the factory is installed, so a paid user + // gets the W peripheral -> the RPI_PICO_W firmware variant. No-op in + // OSS (no factory) and for free users (factory returns null). + sim.attachPioPeripheral(board.boardKind, boardId); await sim.loadMicroPython(files); }