velxio/docs/PICO_W_WIFI_EMULATION.md

11 KiB

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/<id>
         │  { 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 <WiFi.h> 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:

import\s+network\b      # MicroPython network module
network\.WLAN           # explicit WLAN constructor
#include\s*<WiFi\.h>    # 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