# xteink-web-emulator — architecture & roadmap **Goal:** an offline, static-asset web page where you upload an xteink X3/X4 firmware `.bin` and it runs *client-side* — real ESP32-C3 machine code, with the e-ink screen on a canvas, on-screen buttons, and a virtual SD card. No server, no proprietary engine. ## Why QEMU (and why not the alternatives) | Approach | Runs any `.bin` | Open source | Offline in browser | |----------|:---:|:---:|:---:| | **Wokwi** | ✅ | ❌ (closed engine, licensed) | ❌ | | **Recompile firmware → WASM** (crosspoint-simulator + Emscripten) | ❌ source only | ✅ | ✅ | | **QEMU (espressif fork) → WASM** | ✅ | ✅ | ✅ | Only QEMU satisfies all three. `espressif/qemu` models the ESP32-C3 SoC and executes real firmware; `ktock/qemu-wasm` shows QEMU compiling to WASM and running in-browser from static files. The endgame merges those two. ## Status **The native foundation and WASM compile phase are done.** Unmodified [canonical firmware](https://github.com/crosspoint-reader/crosspoint-reader) auto-detects `xteink,variant=x3` through its real I²C fingerprint and boots the X3 home screen with SD support. `variant=x4` omits those chips, so the same firmware selects its SSD1677 driver and sends its command stream to a blank X4 panel stub. Reproduce: ```sh make qemu # clone espressif/qemu @ pinned commit, patch, build make firmware sdimage # 16 MB flash.bin + FAT32 sd.img from a pio build make run # X3 (default) make run VARIANT=x4 # X4 detection + blank panel stub ``` QMP screenshots confirmed the X3 home screen and 528×792 orientation; captured X4 SPI traffic confirmed the firmware selected its SSD1677 driver. `make qemu-wasm` produces the Emscripten module, WASM binary, and pthread worker under `dist/wasm/`; Node smoke checks confirm `xteink` is registered and its browser framebuffer/input bridge initializes. `make web` serves a dependency-free UI for firmware, canvas, buttons, logs, SD files, and debug metrics. Real firmware boots in WASM and drives the X3 panel. ### Device selection (X3 vs X4) X3 and X4 share one ESP32-C3 binary and pick a profile at runtime via an I²C fingerprint (BQ27220 gauge + DS3231 RTC + QMI8658 IMU on SDA20/SCL0), with an NVS override (`cphw/dev_ovr`: 1=X4, 2=X3). **The emulator picks X3 vs X4 at launch** with one machine property: `-machine xteink,variant=x3|x4`. X3 attaches minimal BQ27220/DS3231/QMI8658 targets; X4 leaves the I²C bus empty. This drives both firmware detection and panel wiring without modifying the firmware. ## The interfaces (native now → browser later) Each peripheral is modelled behind a QEMU-native seam that the WASM front-end reuses unchanged — no bespoke abstraction layer: | Peripheral | Native seam | Browser binding | |------------|-------------|---------------------------| | e-ink panel | X3 UC8253 (528×792) or blank X4 SSD1677 stub (480×800) → QEMU graphical console | `` from the same display surface | | SD card | `ssi-sd` + `sd-card-spi` backed by QEMU block layer (`-drive if=sd`) | OPFS files → browser-built 64 MiB FAT32 image → raw MEMFS block device | | Buttons + battery | ADC QOM props `adci[1]`/`adci[2]`; GPIO QOM `input-level[3]` for Power | DOM buttons → `qom-set` equivalent | | Firmware image | flash via `-drive if=mtd` | uploaded `.bin` written to emulated flash | ## Phase 1 device models (in `qemu/patches/0001-xteink-machine.patch`) - **`esp32c3.adc`** — SAR ADC: oneshot completes immediately; per-channel value is settable live via QOM (`adci[*]`). Feeds battery and the two resistor-ladder button groups. Replaced the original always-done register shim. - **`esp32_gpio`** — real OUT/ENABLE/IN registers with 32 named input + output lines and live `input-level[*]` QOM controls. Outputs read high while a pin is input-configured (matches the board's pull-ups). - **`ssi.esp32c3.spi2`** — the general-purpose SPI2 controller (CPU-buffer transactions on an SSI bus). DMA path deliberately unimplemented until needed. - **`xteink-x3-eink`** — UC8253 panel: decodes DTM1/DTM2 planes (52 272 B each), drives BUSY, renders to a QEMU console. Protocol from `chip/eink-x3.chip.c`. - **`ssi-sd` fix** — track card idle state so CMD58 reports ready after ACMD41 (the 9.2 fork hardcoded idle, which SdFat rejects). - **`esp32.i2c` + fingerprint targets** — C3 opcode support and minimal BQ27220/DS3231/QMI8658 register responses for stock X3 detection. - **`xteink` machine** — `variant=x3|x4` controls fingerprint presence and panel type; shared panel/SD wiring stays in one machine. - **X4 panel stub** — blank 480×800 console with X4 idle-low BUSY; accepts the SSD1677 command stream without implementing it. ## Remaining phases 1. **Full X4 panel** — implement SSD1677 RAM/window/update commands when visible X4 rendering is needed; the selection, dimensions, wiring, and stub exist. ## Repo layout | Path | Role | |------|------| | `flake.nix` | espressif-qemu release pkg (`nix run .#run-upstream`) + full QEMU build devShell | | `Makefile` | `qemu` (source build), `firmware`, `sdimage`, `run`, `chip` | | `scripts/build-qemu.sh` | clone pinned espressif/qemu, apply patch, build riscv32-softmmu | | `qemu/patches/` | xteink devices plus the qemu-wasm host port, applied onto pinned Espressif QEMU | | `qemu/wasm/Dockerfile` | Minimal pinned Emscripten dependency environment | | `scripts/build-qemu-wasm.sh` | X3/X4-only WASM build → `dist/wasm/` | | `chip/eink-x3.chip.c` | legacy Wokwi model — the reverse-engineered X3 e-ink protocol reference | | `web/` | Static frontend: firmware upload, canvas, variant, and buttons | ## Debugging notes - Headless capture: run with `-qmp unix:…` and use `scripts/qmp-screendump.py`; the console is 528×792 (physical portrait), rotate not needed. - Inject a button: `scripts/qmp-qom.py set /machine/adc "adci[1]" 2760` (ladder-1 midpoints: Back 3512 / Confirm 2694 / Left 1493 / Right ~5; ladder-2 Up 2242 / Down ~5). Value > 3900 = no button. - Symbolize a panic PC: `llvm-addr2line -f -C -e firmware.elf 0x4210d286`. - Flash must be 16 MB — the partition table spans 16 MB even though the app fits under 4 MB. Espressif QEMU only accepts 2/4/8/16 MB mtd images. - Trace SD/panel: flip `DEBUG_SSI_SD` in `hw/sd/ssi-sd.c` or add an `info_report` in `xteink_x3_eink.c` (both off by default in the patch).