Two Node.js tests that hit the simulation backend WebSocket directly
and inject minimal MicroPython programs via raw-paste REPL:
- test_micropython_i2c_minimal: smallest possible repro. I2C(0)+scan+
single-byte writeto. Used to prove the bug is NOT in the basic I2C
layer — this test passes both before and after the fix.
- test_micropython_i2c_ssd1306_repro: walks the full SSD1306 init
sequence (25-cmd init loop + 6x addr writes + writevto 8B + writevto
1024B). Used to prove the bug is NOT in the cmd sequence or the
writevto path — this test also passes both before and after.
These two tests refuted the original "missing TRANS_DONE IRQ"
hypothesis and pointed the investigation toward the file-load vs
raw-REPL difference, which led to identifying the per-byte _emit
bottleneck in esp32_worker.py.
Run with:
node --experimental-websocket test/test_micropython_i2c_minimal/test.mjs \
--backend=http://localhost:3080 --timeout=120
Requires the velxio container (or local backend with QEMU libs) on
the given backend URL.
|
||
|---|---|---|
| .. | ||
| README.md | ||
| test.mjs | ||
README.md
test_micropython_i2c_minimal
Phase 1 reproduction test for the MicroPython + I2C reboot bug on ESP32 QEMU.
Why this exists
The example 100d-esp32-oled-smart-ui-eyes-animation-time-and-weather-micropython
reboots the ESP32 silently when MicroPython touches machine.I2C(0, ...).
Arduino C++ + Wire.h works on the same OLED. Hypothesis (see
velxio-prod/project/phase-06-esp32-micropython-i2c-fix.md): the picsimlab
QEMU emulation never raises the tx_done IRQ that the ESP-IDF i2c_master
driver waits on → MicroPython hangs → watchdog timeout → soft reset.
This test runs the smallest possible MicroPython program that hits the hardware I2C peripheral, with NO ssd1306 driver and NO helper libraries, so we can confirm the bug is at the QEMU / firmware boundary (not in the OLED driver or the example's code).
What it does
- Downloads MicroPython v1.20.0 firmware (same one the velxio frontend ships).
- Builds a 4 MB flash image with the firmware at offset 0x1000.
- Opens a WebSocket to
BACKEND/api/simulation/ws/<session>. - Sends
start_esp32with the firmware + a registered SSD1306 slave at 0x3C. - Once the REPL prompt appears, injects the minimal program via raw-REPL + Ctrl+D:
from machine import Pin, I2C print("velxio_i2c_pre") i2c = I2C(0, scl=Pin(22), sda=Pin(21)) print("velxio_i2c_ctor_ok") devs = i2c.scan() print("velxio_i2c_scan_ok", devs) try: i2c.writeto(0x3C, b"\xA0") print("velxio_i2c_write_ok") except OSError as e: print("velxio_i2c_write_err", e) print("velxio_i2c_done") - Watches the serial output + system events. Reports the LAST marker
reached (
pre,ctor_ok,scan_ok,write_ok/write_err,done) to localize exactly which I2C call triggers the reboot.
Expected outcomes
| Before the fix | After the fix |
|---|---|
Markers stop at ctor_ok or scan_ok |
All 4 markers + done reached |
system: {event: reboot} event arrives |
No reboot event |
| WebSocket closes with code 1006 within ~5s of running | Test completes cleanly |
How to run
Backend must be running at http://localhost:8001 (default) — on the prod
server with docker compose up -d.
cd /home/dave/velxio-prod/velxio
node test/test_micropython_i2c_minimal/test.mjs
Optional flags:
--timeout=60(default 60s)--backend=http://localhost:8001
Exit code 0 = full success (all markers + done). Non-zero = reboot or incomplete. The report block at the end summarizes which marker was the LAST one reached, which localizes the bug.
Related files
velxio/test/backend/e2e/test_micropython_esp32.mjs— the upstream template this is based on (boots MicroPython + injects a sanity check; doesn't touch I2C).velxio/backend/app/services/esp32_worker.py—_on_i2c_eventcallback (line ~928) that QEMU invokes per I2C event.velxio/backend/app/services/esp32_i2c_slaves.py—I2CWriteSink(line ~309) is what the SSD1306 slave registration uses.velxio-prod/project/phase-06-esp32-micropython-i2c-fix.md— phase tracking + plan.