velxio/test/test_epaper/autosearch/05_velxio_existing.md

4.0 KiB

05 — How Velxio currently wires SPI/I2C displays (the pattern to follow)

We already have two display emulators that Phase 1 ePaper should mirror.

SSD1306 — I2C OLED

  • Web Component: wokwi-ssd1306 (third-party, comes from @wokwi/elements)
  • Simulation hook: frontend/src/simulation/parts/ProtocolParts.ts:312 registers as ssd1306 and listens on the simulator's I2C bus.
  • Render path: maintains a 1-bit GDDRAM buffer in JS, pushes it to the Web Component via setPixels(uint8Array, width, height).

ILI9341 — SPI TFT (the closest precedent for ePaper)

  • Web Component: wokwi-ili9341 (built-in canvas)
  • Simulation hook: frontend/src/simulation/parts/ComplexParts.ts:795 registers as ili9341. Plumbing:
const pinManager = (avrSimulator as any).pinManager;
const spi        = (avrSimulator as any).spi;
const pinDC      = getArduinoPinHelper('D/C');

pinManager.onPinChange(pinDC, (_, state) => { dcState = state; });

spi.onByte = (value) => {
  if (!dcState) processCommand(value);   // DC=LOW  → command opcode
  else          processData(value);      // DC=HIGH → parameter / pixel
  spi.completeTransfer(0xff);              // unblock the CPU immediately
};

The simulation maintains:

  • colStart, colEnd, rowStart, rowEnd, curX, curY — RAM window state
  • currentCmd, dataBytes[] — collecting parameters for the active command
  • A single ImageData buffer batched to requestAnimationFrame so we don't thrash the canvas on every WRITE_RAM byte.
  • A getArduinoPinHelper('D/C') lookup — pulls the DC pin number from the user's wiring in the diagram.

This is exactly what we need for ePaper, with these additions:

Concern ILI9341 today ePaper additions
DC pin yes yes (same)
RST pin optional, ignored mandatory (clear framebuffer)
BUSY pin n/a emulator drives it back to firmware
Frame model streaming pixel writes (visible immediately) latched — held off-screen until 0x20 ACTIVATION
Refresh latency none configurable (default 1 s) — drive BUSY high during
Framebuffer planes one (16-bit RGB) one or two (B/W, +R) or seven (Spectra/ACeP)
Wire-up Adafruit_GFX tft.begin() GxEPD2/Adafruit_EPD display.init()

What "drive BUSY back to the firmware" means in code

ESP32 boards: Esp32Bridge.sendPinEvent(busyGpio, state) exists today (used for DHT22/HC-SR04). For browser-side simulators (AVR, RP2040) the PinManager already supports triggerPinChange(gpio, state) and the simulator's GPIO peripheral picks it up as an INPUT read.

ePaper emulator pseudocode:

function flushFrameAndPulseBusy() {
  // 1. Compose the latched RAM planes into the canvas
  ctx.putImageData(composeBwAndRedPlanes(), 0, 0);
  // 2. Drive BUSY HIGH so the firmware's GxEPD2 _PowerOn() blocks
  pinManager.triggerPinChange(busyGpio, true);
  // 3. After REFRESH_MS, drive BUSY LOW
  setTimeout(() => pinManager.triggerPinChange(busyGpio, false), REFRESH_MS);
}

REFRESH_MS is 50 ms by default (snappy emulation) but settable per-panel attribute for users who want to feel the real ~12 s Spectra 6 latency.

Where the new files will live

frontend/src/components/velxio-components/EPaperElement.ts   ← Web Component (per CLAUDE.md §6a)
frontend/src/components/velxio-components/EPaper.tsx          ← React wrapper
frontend/src/components/simulator/BoardOnCanvas.tsx           ← (no changes — components flow through ComponentRegistry)
frontend/src/services/ComponentRegistry.ts                    ← register the metadataId(s)
frontend/src/simulation/parts/ComplexParts.ts                 ← OR a new EPaperPart.ts in parts/

Per CLAUDE.md §6a (added 2026-04), the visual component must be a Web Component with a pinInfo getter so wires snap to pin tips, not the corner. We'll author one Web Component (<velxio-epaper>) parameterised by a panel-kind attribute (epaper-1in54-bw, epaper-2in13-bwr, etc.) so all sizes share the same code.