fix(sim): Pico W MicroPython loads the RPI_PICO_W firmware (network + CYW43)

The RP2040 MicroPython loader always fetched the plain RPI_PICO build, which
ships no `network` module and no CYW43 WiFi driver. Every Pico W WiFi/MQTT
example therefore failed at `import network` ("no module named 'network'"),
which surfaced as a compile/run error in the editor.

- getFirmware()/loadUserFiles() are now variant-aware. pi-pico-w boards load
  RPI_PICO_W-20230426-v1.20.0 (network/socket/ssl + the CYW43439 driver) and
  write the LittleFS at the W board's flash offset (0x12c000, 212 blocks)
  instead of the plain Pico's 0xa0000/352. The W firmware spans flash to
  ~0xab000 and would otherwise be clobbered by the filesystem. Each variant
  gets its own IndexedDB cache key.
- The variant is selected by the presence of the already-wired CYW43 emulator
  (attachCyw43 runs for pi-pico-w boards only).
- loadMicroPython swaps in a fresh RP2040 each run, so the CYW43 PIO-FIFO hooks
  are re-installed on the new instance; otherwise the driver's gSPI traffic
  never reaches the emulator and WiFi never comes up.
- Bundle micropython-rp2040w.uf2 as the offline fallback.
- Point the ThingsBoard example at the simulator's Velxio-GUEST network.
This commit is contained in:
David Montero 2026-06-12 16:46:41 +02:00
parent a19f5940ee
commit 4d80a9d1c3
4 changed files with 79 additions and 28 deletions

Binary file not shown.

View File

@ -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

View File

@ -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<FirmwareVariant, FirmwareConfig> = {
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<void> {
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<Uint8Array> {
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;
}

View File

@ -280,10 +280,14 @@ export class RP2040Simulator {
files: Array<{ name: string; content: string }>,
onProgress?: (loaded: number, total: number) => void,
): Promise<void> {
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');