diff --git a/frontend/public/firmware/micropython-rp2040w.uf2 b/frontend/public/firmware/micropython-rp2040w.uf2 new file mode 100644 index 00000000..59cdc3ae Binary files /dev/null and b/frontend/public/firmware/micropython-rp2040w.uf2 differ diff --git a/frontend/src/data/examples-100-days.ts b/frontend/src/data/examples-100-days.ts index cd549b03..d65c8b0e 100644 --- a/frontend/src/data/examples-100-days.ts +++ b/frontend/src/data/examples-100-days.ts @@ -6153,10 +6153,10 @@ from machine import Pin from umqttsimple import MQTTClient # --- Configuration --- -WIFI_SSID = "kritish" # -WIFI_PASSWORD = "@pass" +WIFI_SSID = "Velxio-GUEST" # Velxio simulator's built-in WiFi network +WIFI_PASSWORD = "" # the simulator AP is open THINGSBOARD_HOST = "eu.thingsboard.cloud" -ACCESS_TOKEN = "tokenn" +ACCESS_TOKEN = "tokenn" # replace with YOUR ThingsBoard device token # --- Hardware Setup --- # Using "LED" for Pico W onboard LED, or Pin(15) for an external LED diff --git a/frontend/src/simulation/MicroPythonLoader.ts b/frontend/src/simulation/MicroPythonLoader.ts index d1e2a89a..36b36bc8 100644 --- a/frontend/src/simulation/MicroPythonLoader.ts +++ b/frontend/src/simulation/MicroPythonLoader.ts @@ -16,9 +16,7 @@ import littlefsWasmUrl from 'littlefs/dist/littlefs.wasm?url'; // Flash geometry (matches rp2040js and MicroPython defaults) const FLASH_START_ADDRESS = 0x10000000; -const MICROPYTHON_FS_FLASH_START = 0xa0000; const MICROPYTHON_FS_BLOCK_SIZE = 4096; -const MICROPYTHON_FS_BLOCK_COUNT = 352; // UF2 block constants const UF2_MAGIC_START0 = 0x0a324655; @@ -28,15 +26,48 @@ const UF2_PAYLOAD_SIZE = 256; const UF2_DATA_OFFSET = 32; const UF2_ADDR_OFFSET = 12; -// Firmware cache key for IndexedDB -const FIRMWARE_CACHE_KEY = 'micropython-rp2040-uf2-v1.20.0'; +/** + * MicroPython firmware variant. RP2040 boards split into the plain Raspberry + * Pi Pico and the Pico W. The W build is larger because it embeds the CYW43439 + * WiFi driver + blob and the `network`/`socket`/`ssl` modules the plain build + * omits, which also pushes its LittleFS filesystem to a higher flash offset. + * Loading the plain firmware onto a Pico W board is what produced + * "ImportError: no module named 'network'" on every WiFi/MQTT example. + */ +export type FirmwareVariant = 'pico' | 'pico-w'; -// Bundled fallback path (placed in public/firmware/) -const FIRMWARE_FALLBACK_PATH = '/firmware/micropython-rp2040.uf2'; +interface FirmwareConfig { + /** IndexedDB cache key — MUST differ per variant or the two builds collide. */ + cacheKey: string; + remoteUrl: string; + /** Bundled fallback served from public/firmware/. */ + fallbackPath: string; + /** Flash offset of the MicroPython LittleFS region (board-specific). */ + fsFlashStart: number; + /** Number of 4K blocks in the LittleFS region. */ + fsBlockCount: number; +} -// Remote firmware URL -const FIRMWARE_REMOTE_URL = - 'https://micropython.org/resources/firmware/RPI_PICO-20230426-v1.20.0.uf2'; +// Geometry mirrors MicroPython rp2 v1.20.0 board configs: +// PICO : MICROPY_HW_FLASH_STORAGE_BYTES = 1408K -> FS @ 0x200000-0x160000 = 0xa0000 (352 blocks) +// PICO_W: MICROPY_HW_FLASH_STORAGE_BYTES = 848K -> FS @ 0x200000-0x0d4000 = 0x12c000 (212 blocks) +// The W firmware spans flash up to ~0xab000, so its FS sits at 0x12c000 (no overlap). +const FIRMWARE_CONFIGS: Record = { + pico: { + cacheKey: 'micropython-rp2040-uf2-v1.20.0', + remoteUrl: 'https://micropython.org/resources/firmware/RPI_PICO-20230426-v1.20.0.uf2', + fallbackPath: '/firmware/micropython-rp2040.uf2', + fsFlashStart: 0xa0000, + fsBlockCount: 352, + }, + 'pico-w': { + cacheKey: 'micropython-rp2040w-uf2-v1.20.0', + remoteUrl: 'https://micropython.org/resources/firmware/RPI_PICO_W-20230426-v1.20.0.uf2', + fallbackPath: '/firmware/micropython-rp2040w.uf2', + fsFlashStart: 0x12c000, + fsBlockCount: 212, + }, +}; /** * Parse UF2 binary and write payload blocks into RP2040 flash. @@ -72,9 +103,11 @@ export function loadUF2(uf2Data: Uint8Array, flash: Uint8Array): void { export async function loadUserFiles( files: Array<{ name: string; content: string }>, flash: Uint8Array, + variant: FirmwareVariant = 'pico', ): Promise { + const { fsFlashStart, fsBlockCount } = FIRMWARE_CONFIGS[variant]; // Create a backing buffer for the LittleFS filesystem - const fsBuffer = new Uint8Array(MICROPYTHON_FS_BLOCK_COUNT * MICROPYTHON_FS_BLOCK_SIZE); + const fsBuffer = new Uint8Array(fsBlockCount * MICROPYTHON_FS_BLOCK_SIZE); // Initialize the littlefs WASM module. // locateFile redirects Emscripten's internal fetch to the Vite-resolved asset URL. @@ -109,7 +142,7 @@ export async function loadUserFiles( flashProg, flashErase, flashSync, - MICROPYTHON_FS_BLOCK_COUNT, + fsBlockCount, MICROPYTHON_FS_BLOCK_SIZE, ); const lfsInstance = lfs._new_lfs(); @@ -133,7 +166,7 @@ export async function loadUserFiles( lfs._free(config); // Copy the LittleFS image into RP2040 flash at the filesystem offset - flash.set(fsBuffer, MICROPYTHON_FS_FLASH_START); + flash.set(fsBuffer, fsFlashStart); } /** @@ -141,13 +174,16 @@ export async function loadUserFiles( * Checks IndexedDB cache first, then tries remote download, then bundled fallback. */ export async function getFirmware( + variant: FirmwareVariant = 'pico', onProgress?: (loaded: number, total: number) => void, ): Promise { + const { cacheKey, remoteUrl, fallbackPath } = FIRMWARE_CONFIGS[variant]; + // 1. Check IndexedDB cache try { - const cached = await idbGet(FIRMWARE_CACHE_KEY); + const cached = await idbGet(cacheKey); if (cached instanceof Uint8Array && cached.length > 0) { - console.log('[MicroPython] Firmware loaded from cache'); + console.log(`[MicroPython] Firmware (${variant}) loaded from cache`); return cached; } } catch { @@ -156,7 +192,7 @@ export async function getFirmware( // 2. Try remote download try { - const response = await fetch(FIRMWARE_REMOTE_URL); + const response = await fetch(remoteUrl); if (response.ok) { const total = Number(response.headers.get('content-length') || 0); const reader = response.body?.getReader(); @@ -182,12 +218,12 @@ export async function getFirmware( // Cache for next time try { - await idbSet(FIRMWARE_CACHE_KEY, firmware); + await idbSet(cacheKey, firmware); } catch { // Cache write failure is non-fatal } - console.log(`[MicroPython] Firmware downloaded (${firmware.length} bytes)`); + console.log(`[MicroPython] Firmware (${variant}) downloaded (${firmware.length} bytes)`); return firmware; } } @@ -196,20 +232,20 @@ export async function getFirmware( } // 3. Fallback to bundled firmware - const response = await fetch(FIRMWARE_FALLBACK_PATH); + const response = await fetch(fallbackPath); if (!response.ok) { - throw new Error('MicroPython firmware not available (remote and bundled both failed)'); + throw new Error(`MicroPython firmware (${variant}) not available (remote and bundled both failed)`); } const buffer = await response.arrayBuffer(); const firmware = new Uint8Array(buffer); // Cache for next time try { - await idbSet(FIRMWARE_CACHE_KEY, firmware); + await idbSet(cacheKey, firmware); } catch { // non-fatal } - console.log(`[MicroPython] Firmware loaded from bundled fallback (${firmware.length} bytes)`); + console.log(`[MicroPython] Firmware (${variant}) loaded from bundled fallback (${firmware.length} bytes)`); return firmware; } diff --git a/frontend/src/simulation/RP2040Simulator.ts b/frontend/src/simulation/RP2040Simulator.ts index 7dd8b113..d51f1047 100644 --- a/frontend/src/simulation/RP2040Simulator.ts +++ b/frontend/src/simulation/RP2040Simulator.ts @@ -280,10 +280,14 @@ export class RP2040Simulator { files: Array<{ name: string; content: string }>, onProgress?: (loaded: number, total: number) => void, ): Promise { - console.log('[RP2040] Loading MicroPython firmware...'); + // Pico W needs the RPI_PICO_W build (network + CYW43 driver + bigger, + // higher LittleFS). The cyw43 emulator is attached (via attachCyw43) only + // for pi-pico-w boards, so its presence selects the firmware variant. + const variant = this.cyw43 ? 'pico-w' : 'pico'; + console.log(`[RP2040] Loading MicroPython firmware (${variant})...`); // 1. Get MicroPython UF2 firmware (cached in IndexedDB) - const firmware = await getFirmware(onProgress); + const firmware = await getFirmware(variant, onProgress); // 2. Create fresh RP2040 instance this.rp2040 = new RP2040(); @@ -294,8 +298,9 @@ export class RP2040Simulator { loadUF2(firmware, this.rp2040.flash); console.log(`[RP2040] MicroPython UF2 loaded (${firmware.length} bytes)`); - // 4. Create LittleFS with user files and load into flash - await loadUserFiles(files, this.rp2040.flash); + // 4. Create LittleFS with user files and load into flash (variant-specific + // flash offset — the Pico W FS lives higher than the plain Pico's). + await loadUserFiles(files, this.rp2040.flash, variant); console.log(`[RP2040] LittleFS loaded with ${files.length} file(s)`); // Keep a flash copy for reset @@ -359,6 +364,16 @@ export class RP2040Simulator { } this.pioStepAccum = 0; + // Pico W: attachCyw43 installed the gSPI PIO-FIFO hooks on the RP2040 + // instance that existed at board-creation time. loadMicroPython just swapped + // in a fresh RP2040, so those hooks now point at the discarded instance. + // Re-install them on the new PIO FIFOs or the cyw43 driver's bit-banged + // traffic never reaches the emulator and WiFi never connects. + if (this.cyw43) { + this.cyw43HookedFifos = []; + this.installCyw43PioHooks(); + } + this.setupGpioListeners(); this.micropythonMode = true; console.log('[RP2040] MicroPython ready');