# Pico W Wi-Fi Emulation (CYW43439) This document describes how Velxio emulates the Raspberry Pi Pico W's on-board Wi-Fi chip (Infineon CYW43439). The integration mirrors the existing ESP32 WiFi pipeline so the same UI, settings, and mental model carries over. ## TL;DR | Question | Answer | |---|---| | Does it work today? | Yes. `network.WLAN(STA_IF)`, `wlan.connect()`, `wlan.scan()`, `socket.send/recv`, `urequests`, `umqtt`, raw WebSocket — all functional. | | Does the on-board LED on Pico W work? | Yes. The LED is wired through the CYW43 chip on real hardware — Velxio observes the `gpioout` IOCTL and toggles the canvas LED. | | What SSID does it advertise? | `Velxio-GUEST` (BSSID `02:42:DA:42:00:01`, channel 6). Same naming convention as the ESP32 path. | | Is the closed firmware blob shipped? | **No.** Velxio absorbs the 224 KB firmware-stream writes and synthesises the post-load chip state. See `docs/wiki/picow-cyw43-emulation.md`. | | Where do TCP/UDP packets go? | Backend WebSocket bridge, same pattern as ESP32. | | What about Bluetooth? | Out of scope (matches Wokwi). | | What about ESP-NOW / 802.11 raw frames? | Out of scope — slirp only routes TCP/UDP. | ## Architecture ``` ┌────────────────────────────────────────────────────────────────┐ │ Frontend (browser) │ │ │ │ RP2040Simulator (rp2040js) │ │ │ │ │ │ PIO TX FIFO push hooks │ │ ▼ │ │ Cyw43Emulator ── full chip-side state machine │ │ ├── PioBusSniffer → decode 32-bit gSPI command words │ │ ├── F0/F1 register state │ │ ├── SDPCM/CDC framing on F2 │ │ ├── IOCTL handler (UP/DOWN/SCAN/SET_SSID/SET_VAR/...) │ │ ├── Async event injection (LINK / SET_SSID / ESCAN_RESULT) │ │ ├── Velxio-GUEST virtual AP │ │ └── on-board LED IOCTL → canvas │ │ │ Ethernet frames (chip ↔ host) │ │ ▼ │ │ Cyw43Bridge (WebSocket) │ │ │ │ └────────│───────────────────────────────────────────────────────┘ │ ws://…/api/simulation/ws/ │ { type: 'start_picow' | 'stop_picow' | 'picow_packet_out' } │ { type: 'wifi_status' | 'picow_packet_in' } ▼ ┌────────────────────────────────────────────────────────────────┐ │ Backend (FastAPI) │ │ │ │ picow_net_bridge.PicowNetManager │ │ ├── Per-instance state (sta_ip = 10.13.37.42) │ │ ├── Layer-2 → Layer-3 demux (IPv4 only at first) │ │ └── TCP/UDP fan-out to host network (slirp-style) │ └────────────────────────────────────────────────────────────────┘ ``` ## Comparison with the ESP32 path | Feature | ESP32 (existing) | Pico W (new) | |---|---|---| | Frontend bridge class | `Esp32Bridge` | `Cyw43Bridge` | | WS message type prefix | `start_esp32`, `esp32_*` | `start_picow`, `picow_*` | | Backend service | `esp_qemu_manager.py` (QEMU) | `picow_net_bridge.py` (no QEMU — chip is JS) | | Where the CPU runs | Espressif QEMU (out-of-process) | rp2040js (in-page, no QEMU) | | Where the WiFi chip runs | QEMU `esp32_wifi` NIC model | `Cyw43Emulator` (frontend) | | Firmware blob shipped? | No (Espressif QEMU has it built-in) | **No** (synthesised post-load state) | | Sample SSID | `Velxio-GUEST` | `Velxio-GUEST` | | Auto-detect from sketch | Yes — `#include ` etc. | Yes — `import network`, `network.WLAN`, `WiFi.begin(...)` | ## Files added/changed ``` frontend/src/simulation/cyw43/ ├── index.ts barrel export ├── constants.ts F0/F1/WLC/WLC_E/SdpcmChannel ├── sdpcm.ts SDPCM + CDC + event-frame codec ├── virtual-ap.ts Velxio-GUEST single-source-of-truth ├── PioBusSniffer.ts 32-bit gSPI command decoder ├── Cyw43Emulator.ts chip-side state machine └── Cyw43Bridge.ts WS link to backend (ESP32-bridge twin) frontend/src/simulation/RP2040Simulator.ts + attachCyw43() / detachCyw43() / installCyw43PioHooks() + PIO TX FIFO interception, RX FIFO repacking + onPicoWLed / onPicoWWifiUp listener slots frontend/src/store/useSimulatorStore.ts + cyw43BridgeMap, getCyw43Bridge() + per-board attach when boardKind === 'pi-pico-w' + WiFi auto-detect from main.py / sketch.ino content frontend/src/data/examples-picow-wifi.ts + 4 curated examples drawn from github.com/KritishMohapatra/100_Days_100_IoT_Projects frontend/src/data/examples.ts + 'pi-pico-w' added to the boardType union + picowWifiExamples concatenated into exampleProjects frontend/src/components/examples/ExamplesGallery.tsx + Pico W (Wi-Fi) board tab frontend/src/__tests__/picow-cyw43-integration.test.ts + 6 frontend integration tests (barrel exports, handshake, IOCTL, bridge protocol shape) backend/app/services/picow_net_bridge.py + PicowNetManager + start/stop/deliver_packet_out + IPv4 demux stub (TCP SYN logging at first iteration) backend/app/api/routes/simulation.py + start_picow / stop_picow / picow_packet_out message types ``` ## Settings + UX ### Auto-detect Enabling Wi-Fi on a Pico W board is **automatic**. When the user clicks Run, `useSimulatorStore.startBoard()` scans the active file group and flips `cyw43Bridge.wifiEnabled` to true if any of the following appear: ```regex import\s+network\b # MicroPython network module network\.WLAN # explicit WLAN constructor #include\s* # arduino-pico Wi-Fi WiFi\.begin\( # explicit AP join from C++ ``` Same heuristic as the ESP32 path — see `useSimulatorStore.ts:921` for the parallel ESP32 implementation. ### Manual override For projects that need WiFi but don't match the heuristic (rare), power users can `import { getCyw43Bridge } from '../store/useSimulatorStore'` and set `wifiEnabled = true` before calling `connect()`. A future PR should expose this via the same right-panel toggle the ESP32 has. ### Visual feedback The board's `wifiStatus` in the simulator store is updated by the backend's `wifi_status` events. UI components that already render the ESP32 status banner pick up Pico W status without code changes — the schema is identical (`{ status, ssid?, ip? }`). ## How the backend bridge differs from ESP32's The ESP32 path runs Espressif's QEMU build with a `-nic user,model=esp32_wifi` slirp NIC. QEMU does the IP/TCP termination internally; the backend just shovels stdio. For the Pico W there's no QEMU — the chip lives in JavaScript. The backend bridge therefore **terminates Layer 2 itself** in pure Python: parses Ethernet frames, demuxes by ethertype, and fans out to host TCP/UDP sockets. Concretely: | Layer | Implementation | |---|---| | Ethernet / ARP | `picow_net/protocols.py` + `arp.py` | | IPv4 (header + checksum) | `protocols.py` + `checksums.py` (RFC 1071) | | ICMP echo (ping) | `icmp.py` — synthesises Echo Reply for any Echo Request | | DHCP server | `dhcp.py` — DISCOVER → OFFER, REQUEST → ACK with proper option layout (RFC 2131) | | DNS proxy | `dns.py` — forwards A-record queries to host resolver via `asyncio.getaddrinfo` | | TCP NAT | `tcp_nat.py` — full RFC 793 state machine: SYN_RCVD → ESTABLISHED → CLOSE_WAIT/FIN_WAIT_1/2 → LAST_ACK, MSS option negotiation, modular sequence-number arithmetic, `asyncio.open_connection` for the host side | | UDP NAT | `udp_nat.py` — per-(chip_port, dst, dst_port) `DatagramTransport`, idle reaper | The bridge is fully self-contained — **zero subprocess overhead**, no QEMU, no libslirp dependency. It runs in the same FastAPI event loop the rest of the backend already uses. ## Test coverage | Layer | Suite | Status | |---|---|---| | Bit decoder | `test/test_Raspberry_Pi_Pico_W/test_code/tests/01_pio_decoder.test.ts` | ✅ 9/9 | | Bus handshake | `…/02_handshake.test.ts` | ✅ 6/6 | | SDPCM codec | `…/04_sdpcm.test.ts` | ✅ 7/7 | | IOCTL responses | `…/05_ioctl.test.ts` | ✅ 5/5 | | Full lifecycle | `…/06_full_lifecycle.test.ts` | ✅ 3/3 | | Real 100-days projects | `…/07_picow_iot_projects.test.ts` | ✅ 10/10 | | Performance budgets | `…/08_viability.test.ts` | ✅ 9/9 | | Frontend integration | `frontend/src/__tests__/picow-cyw43-integration.test.ts` | ✅ 6/6 | | Backend bridge | `test/backend/integration/test_picow_net_bridge.py` | ✅ 18/18 | | **Total** | — | **73 passed, 0 failed** | The backend integration suite includes a **real TCP round-trip**: the test stands up an in-process HTTP server, drives a SYN through the bridge, walks through the full RFC 793 handshake, sends a `GET /hello` request, asserts the response carries `HTTP/1.1 200 OK` and `hello`, then closes cleanly. Likewise UDP (echo server) and DNS (localhost resolution) are exercised end-to-end against real sockets. End-to-end with real MicroPython firmware is gated on a UF2 fixture that CI does not bundle (see `…/03_pico_w_blink.test.ts`); drop a `fixtures/RPI_PICO_W-*.uf2` to enable. ## What's intentionally NOT supported - **Bluetooth.** The CYW43439 also does BT 5.2; Velxio doesn't model it (Wokwi doesn't either). Calls into `bluetooth.BLE()` will raise. - **ESP-NOW / raw 802.11.** Slirp only forwards TCP/UDP. A virtual 802.11 hub like `mac80211_hwsim` would be needed to bridge two emulated boards at the MAC layer. - **WPA2/WPA3 verification.** Any password "works" against the synthetic AP — same trade-off as Wokwi. - **Bit-perfect timing.** The chip is modelled by behaviour, not by cycle-accurate firmware execution. Real-world `time.sleep_us()`-ish guarantees aren't enforced. ## See also - `docs/wiki/picow-cyw43-emulation.md` — implementation post-mortem, what was tried, what was discarded, what's next. - `docs/ESP32_WIFI_BLUETOOTH.md` — the ESP32 path that this mirrors. - `test/test_Raspberry_Pi_Pico_W/autosearch/` — the upstream research dossier (8 markdown files) with citations and the layered design. - Reference upstream: - Reference driver: (MIT, cloned at `third-party/picowi/`) - Reference SPI program: