S3 Lab ESP32-S3 emulator · Rust → WASM battery 120/0/0

Bench manual

Everything on the bench page, and how the same runs are reproduced headlessly for validation.

Run firmware Speed controls MicroPython REPL Serial in and out Pin header Fixtures Validate

Run firmware

Pick a gallery example or load your own .merged.bin (arduino-cli produces one per sketch). Press Run — the emulator steps both cores in a background worker (never on the UI thread), and output appears in the console as the firmware prints. Stop halts stepping; Reset reloads the same image from its first boot instruction. The steps-per-frame slider sets the base batch the worker scales adaptively each frame; the MIPS readout shows smoothed emulated instructions per wall second.

Speed controls

Clock selects the emulated time rate: Accurate ticks cycle-exactly, Balanced (default) batches the same ratio with fewer overheads, Turbo runs 2× virtual time so delay-heavy boots finish sooner (timing-sensitive demos may misread — switch back to Accurate if a demo looks off). Engine selects Single (both cores serialized in one worker, stable) or Dual (virtual-device dispatch offloaded to a second worker, experimental). Transport selects how frames reach the page: Auto uses zero-copy SharedArrayBuffer rings when the page is cross-origin isolated, else postMessage. Local SAB development needs COOP/COEP headers — serve with python3 tools/serve.py instead of plain http.server.

MicroPython REPL

The MicroPython preset in the firmware rail boots the stock v1.29.0 GENERIC_S3 interpreter to an interactive >>> prompt — no build needed. Press ▶ REPL: the page loads the bundled tools/firmware/ESP32_GENERIC_S3-20260824-v1.29.0.bin (same-origin, works offline), pads flash to 3 MiB, appends the littlefs vfs partition the firmware mounts (same recipe as tools/micropython_repl.sh), and loads it. Press Run and wait for >>> (tens of seconds: the interpreter is a ~35M-instruction boot). The preset flips the serial port to UART0 — the REPL listens there, not on USB-CDC — and offers print(6*7) as a first line; type any Python and press Enter. The URL box accepts any other image (a local copy served beside the page, or Load merged.bin). Upstream micropython.org serves no CORS header, so a cross-origin fetch fails in the status line — self-host the file in that case.

Serial in and out

The console merges USB-CDC (Serial) and UART output. Search, copy, and clear act on the scrollback buffer, which holds the last 5000 lines. To send keystrokes, choose the port your firmware reads — USB-CDC for Serial sketches, UART1 for the echo example — type, and press Enter. Lines are sent with a trailing newline unless you hold Shift, matching what readStringUntil and REPL readers wait for. Long pastes are chunked under the 128-byte hardware FIFO depth, the way silicon drops overrun bytes.

Pin header

The header shows live levels for GPIO0–39. Amber means driven high, dim means low, blue marks pins switched to input view. Click any pin to flip its display between output and input. The levels come from the same GPIO matrix the firmware drives, including peripheral-routed signals such as PWM, RMT, and I2S — so a blinking LED sketch visibly blinks here.

Fixtures

Some gallery entries need a stand-in for hardware that cannot exist in a browser tab. The manifest declares them and the bench applies them before boot — the same values the headless harness feeds through environment variables:

TOUCH_INJECT=3:1877        touch pad 3 reads counter 1877
ADC_INJECT_MV=825           ADC1_CH3 (GPIO4) reads 825 mV
TEMP_C=25                   internal temperature sensor reads 25 °C
WIFI_SCAN_APS=...           fixture access points for the scan sketch
WIFI_STA_CONN=1             complete the station association
SPI_SDSPI=1                 attach the virtual SD card over SPI
FLASHENC_KEY=...            boot the image through XTS flash encryption

Nothing about the firmware changes — fixtures only answer the reads real hardware would answer.

Validate

The battery replays all 124 cases headlessly: build (or reuse) each merged image, boot it through run_flash or the Node bridge, and assert serial markers plus internal state. Run a slice locally with ./tools/run_battery.sh wifi_scan wifi_sta, or the whole suite with ./tools/run_battery.sh. The freshness guard (tools/check_firmware_freshness.py) keeps committed bins in sync with their sketch sources, gallery copies in sync with the bins (python3 tools/sync_gallery.py refreshes them), and tools/webui_playwright.py drives the real page in Chromium — 32 checks covering boot, MIPS, echo, UART round-trip, SD mount, touch read, worker boot, Wi-Fi/ESP-NOW/L3, BLE, MicroPython, gateway panels — failing loudly on any page error.