Component reference
Generated from the same part documentation the app ships: footprints, behavior notes, pin meanings, and sharp edges, by category.
Arduino
Arduino Nano (classic V3, real emulated ATmega328P) straddling the channel (anchor row e), 15 columns, full 30-pin header in silkscreen order with the mini-USB on the left. Row e: d1(TX1), d0(RX0), reset, gnd, d2-d12. Row f: vin, gnd2, reset2, 5v, a7, a6, a5, a4, a3, a2, a1, a0, aref, 3v3, d13. a4/a5 are REAL GPIOs that carry I²C (SDA=A4, SCL=A5 — wire I²C devices there; no dedicated sda/scl pins). A6/A7 are ADC-only pads (channels 6/7): perfect for analog sensors, but they have NO digital function at all — no pinMode, no digitalRead, no pull-up. Both reset pins are one net; pulling either low resets the board. Hardware SPI = d13(SCK)/d11(MOSI)/d12(MISO); LED "L" mirrors d13. Power via vin or 5v; 3v3 is the USB-bridge LDO output (weak, ~150 mA class). props.firmware = "example:blink" | the other 328P example ids | Intel HEX compiled for arduino:avr:nano (cpu=atmega328). Modeled: cycle-exact CPU with 2 KB SRAM, timers/PWM, ADC (all 8 channels), interrupts, UART TX+RX (serial monitor typing reaches Serial.read), EEPROM (persists across resets within a session), watchdog reset, and physical pin coupling (drive strength, pull-ups). Transaction-level: SPI and I²C move bytes/ACKs, not wire-level bits on the pins. Not modeled: bootloader and fuses (code runs straight from reset), the USB bridge as a device, USART pin waveforms.
Classic Arduino Uno (real emulated ATmega328P), full R3 header in silkscreen order: d0-d13, a0-a3 plus sda(A4)/scl(A5) (all six are real ADC inputs for analogRead), aref, reset (pull low to reset the board), ioref, gnd/gnd2/gnd3, vin/5v/3v3. Power via vin or 5v. props.firmware = "example:blink" | "example:serial-hello" | Intel HEX from the Arduino IDE (Sketch → Export Compiled Binary). Best placed free on the bench and wired pin-to-pin. Modeled: cycle-exact CPU with 2 KB SRAM, timers/PWM, ADC, interrupts, UART TX+RX (serial monitor typing reaches Serial.read), EEPROM (persists across resets within a session), watchdog reset, and physical pin coupling (drive strength, pull-ups). Transaction-level: SPI and I²C move bytes/ACKs, not wire-level bits on the pins. Not modeled: bootloader and fuses (code runs straight from reset).
Arduino Mega 2560 (real emulated ATmega2560): the big AVR board — 54 digital pins (d0-d53), 16 analog inputs (a0-a15, all real ADC channels incl. a8-a15 via the MUX5 bank), 4 hardware serial ports, 6 timers with 15 PWM pins (d2-d13, d44-d46), 8 KB SRAM, 256 KB flash, 4 KB EEPROM. Full header in silkscreen order: top d0-d21 (+aref/gnd3), bottom power (ioref/reset/3v3/5v/gnd/gnd2/vin) + a0-a15, right edge the 2x18 double-row header d22-d53 with 5v2/5v3/gnd4/gnd5 end pins. Power via vin or 5v. I2C is d20(SDA)/d21(SCL) — REAL digital pins, no dedicated sda/scl nodes; hardware SPI is d50(MISO)/d51(MOSI)/d52(SCK)/d53(SS). props.firmware = "example:mega-blink" | "example:mega-serial-hello" | Intel HEX compiled for arduino:avr:mega (256 KB images with extended-address records load fine). Onboard "L" LED mirrors d13 (PB7 on this chip). Modeled: cycle-exact CPU with the faithful 22-bit PC, timers 0-5 (compare units A/B/C), 16-channel ADC, INT0-INT7 + pin-change interrupts, UART0 TX+RX to the serial monitor, EEPROM, watchdog, physical pin coupling. Serial1 (d19/d18), Serial2 (d17/d16) and Serial3 (d15/d14) are fully modeled at the register/interrupt level so code using them runs correctly, but only Serial (USART0) reaches the serial monitor; USART pin-level waveforms are not modeled. Not modeled: bootloader/fuses, the XMEM external-memory interface.
SUPERSEDED — prefer the arduinonano part for new projects; this one stays valid forever so existing circuits keep loading. Bare ATmega328P module straddling the channel (anchor row e) — a 22-pin barebones breakout, not the full Arduino Nano (that is the arduinonano part, which has the complete 30-pin header incl. the ADC-only A6/A7). Pins d0-d13, a0-a3 (real ADC inputs for analogRead), sda(A4)/scl(A5). props.firmware = "example:<id>" (see FIRMWARE PROGRAMS below) or a full Intel HEX string. Wire vcc and gnd. Modeled: cycle-exact ATmega328P CPU with 2 KB SRAM, timers/PWM, ADC, interrupts, UART TX+RX (serial monitor typing reaches Serial.read), EEPROM (persists across resets within a session), watchdog reset, and physical pin coupling (drive strength, pull-ups). Transaction-level: SPI and I²C move bytes/ACKs, not wire-level bits on the pins. Not modeled: bootloader and fuses (code runs straight from reset).
Raspberry Pi
Raspberry Pi Pico (real emulated RP2040, 3.3 V logic!) straddling the channel, anchor row e, 11 columns. Wire gnd + vsys (5 V in); the 3v3 pin is a real 3.3 V output. props.firmware = "example:<pico id>" or "uf2:<base64>".
Raspberry Pi Pico W (real emulated RP2040 + a modelled CYW43439 Wi-Fi chip, 3.3 V logic) straddling the channel, anchor row e, 11 columns — the same 22-pin footprint as pipico (the radio hangs off internal GPIOs). Wire gnd + vsys (5 V in); the 3v3 pin is a real 3.3 V output. NO bundled firmware: props.firmware is either a user-uploaded "uf2:<base64>" image (Inspector upload — use the official RPI_PICO_W MicroPython build from micropython.org) or empty, and an empty/omitted firmware leaves the board idle. AI-authored documents therefore cannot ship a runnable Pico W — pick an ESP32 board for script projects and leave this part to users with their own UF2. With the MicroPython UF2 uploaded the serial monitor is a live Python REPL and network.WLAN works for real on the same virtual air as the ESP32 boards: scan, WPA2-PSK join, DHCP, sockets, board-to-board — networks come from props.wifi ("ssid:channel:rssi[:password]" comma list, editable live in the Inspector). The Pico W is station-only: `network.WLAN(network.AP_IF)` returns but does not create a visible access point, so use `props.wifi` virtual networks for board-to-board Wi-Fi. The onboard LED sits on the radio chip's GPIO0 — machine.Pin("LED") drives it, no RP2040 pin involved. Bluetooth LE works too: the same CYW43439 is an HCI LE controller behind stock MicroPython's BTstack host, so the `bluetooth` module (and aioble) advertises, scans, connects and carries GATT in BOTH roles on the same shared BLE air as the ESP32-C6 — interop both directions. BLE pairing/bonding works too — legacy Just-Works pairing encrypts the link (both hosts fire _IRQ_ENCRYPTION_UPDATE with a 16-byte key), bonds persist via the secret store, and it interoperates with the ESP32-C6 in both directions — BUT it needs a custom MicroPython build: the official RPI_PICO_W v1.25.0 build ships no gap_pair at all (AttributeError — modbluetooth is compiled without pairing/bonding), so with the stock UF2 gap_pair does not exist. Notify payloads clip at the default 20-byte ATT MTU. I²C0 = GP4/GP5, like the plain Pico.
ESP32
Classic ESP32 DevKit (real emulated dual-core Xtensa LX6 at 240 MHz, 3.3 V logic). Boots unmodified ESP-IDF apps, official MicroPython (ESP32_GENERIC), and compiled Arduino-ESP32 sketches. Wire gnd + vin (≥3.7 V; the onboard LDO makes the 3v3 output). Pins: gnd/vin/3v3/en + g2 (onboard LED), g4, g5, g18, g19, g21, g22, g23. props.firmware = "example:esp32classic-blink" | "bin:<base64 0xE9 image>" | "elf:<base64>". I²C defaults SDA=g21/SCL=g22; hardware SPI is FIXED on VSPI's pins SCK=g18/MOSI=g23/MISO=g19 — MicroPython: SPI(2) with no pin args (those ARE the port defaults); CS = any free GPIO you drive yourself; the byte-level seam attaches the microSD and SPI displays at bus speed. ADC on g2/g4 (ADC2, 0–3.3 V, 12-bit). Serial monitor = UART0. Wi-Fi radio initializes (wlan.active/scan/connect API works) but the ether is empty, like real hardware in a shielded room. MicroPython native code emitters work on this board: @micropython.native and @micropython.viper compile a function to machine code (measured 2-3x and 20x+ on Python-level integer loops) — worth it only for a small leaf function doing measured-hot integer or ptr8/ptr16 work, not for C-call glue.
ESP32-C3 module (real emulated RISC-V RV32IMC at 160 MHz, 3.3 V logic) straddling the channel, anchor row e, 11 columns. Wire gnd + vin (5 V in); the 3v3 pin is a real 3.3 V output. props.firmware = "example:esp32-blink" | "example:esp32-echo" | "bin:<base64 0xE9 image>" | "elf:<base64>". I²C is FIXED at SDA=g8/SCL=g9 (MicroPython: I2C(0, scl=Pin(9), sda=Pin(8))); hardware SPI (GPSPI2) is FIXED at SCK=g6/MOSI=g7/MISO=g2 — MicroPython: SPI(1) with no pin args (those ARE the port defaults, same pins as the C6); CS = any free GPIO you drive yourself. The byte-level SPI seam attaches the microSD and the SPI displays (ST7735/ST7789/MAX7219/e-paper) at bus speed — prefer it over SoftSPI on this board. Arduino sketches use the same pins via an explicit SPI.begin(6, 2, 7) (SCK, MISO, MOSI); Adafruit_ST7735 and SD.h then work unmodified. ADC1 ch0-4 on g0-g4. Serial monitor = UART0. Wi-Fi radio initializes (wlan.active/scan/connect API works) but the ether is empty, like real hardware in a shielded room. MicroPython native code emitters work on this board: @micropython.native and @micropython.viper compile a function to machine code (measured 2-3x and 20x+ on Python-level integer loops) — worth it only for a small leaf function doing measured-hot integer or ptr8/ptr16 work, not for C-call glue.
Real emulated ESP32-S3 (dual-core Xtensa LX7, 240 MHz). Boots unmodified ESP-IDF, Arduino-ESP32, and MicroPython firmware; same pin footprint as esp32c3 (gnd/vin/3v3 + g0-g10, g18-g21); I²C FIXED at SDA=g8/SCL=g9 (I2C(0, scl=Pin(9), sda=Pin(8))); hardware SPI (GPSPI2) is FIXED at SCK=g6/MOSI=g7/MISO=g2 — the S3's MicroPython SPI(1) defaults are off this footprint, so pass the pins explicitly: SPI(1, sck=Pin(6), mosi=Pin(7), miso=Pin(2)); CS = any free GPIO you drive yourself; the byte-level seam attaches the microSD and SPI displays at bus speed. ADC1 ch0-9 on g1-g10; serial console in the serial monitor. Wi-Fi radio initializes (wlan.active/scan/connect API works) but the ether is empty, like real hardware in a shielded room. MicroPython native code emitters work on this board: @micropython.native and @micropython.viper compile a function to machine code (measured 2-3x and 20x+ on Python-level integer loops) — worth it only for a small leaf function doing measured-hot integer or ptr8/ptr16 work, not for C-call glue.
ESP32-C6 module (real emulated single RISC-V RV32IMAC at 160 MHz, 512 KB SRAM, 3.3 V logic) straddling the channel, anchor row e, 11 columns. Wire gnd + vin (5 V in); the 3v3 pin is a real 3.3 V output. Boots unmodified ESP-IDF, Arduino-ESP32, and MicroPython firmware. Pins: gnd/vin/3v3 + g0-g9, g18-g20, g22, g23. props.firmware = "example:esp32c6-blink" | "bin:<base64 0xE9 image>" | "elf:<base64>". Zigbee coordinator firmware also ships as an asset: props.firmware = "asset:/firmware/arduino-zigbee-switch-c6.bin" makes the C6 an on/off ZCL coordinator — wire a pushbutton g9 → gnd (internal pull-up); after a sealed Zigbee appliance joins and binds (serial prints "Waiting for Light to bound", then the device short address), each press sends a real Toggle ("Toggling light" on serial). IMPORTANT for hand-authored docs: sealed Zigbee/Thread appliances need their props.firmware set EXPLICITLY to the asset id shown in their defaults — an omitted firmware boots a blink placeholder and the appliance never joins. I²C defaults SDA=g23/SCL=g22; hardware SPI (GPSPI2) is FIXED at SCK=g6/MOSI=g7/MISO=g2 — MicroPython: SPI(1) with no pin args (those ARE the port defaults); CS = any free GPIO you drive yourself. The byte-level SPI seam attaches the microSD and the SPI displays (ST7735/ST7789/MAX7219/e-paper) at bus speed — prefer it over SoftSPI on this board. Arduino sketches use the same pins via an explicit SPI.begin(6, 2, 7) (SCK, MISO, MOSI — the Arduino core's own defaults are NOT the contract pins); Adafruit_ST7735 and SD.h then work unmodified. ADC1 ch0-6 on g0-g6. An onboard WS2812 RGB LED sits on g8, exactly as on real C6 DevKits: rgbLedWrite(8, r, g, b) (Arduino) lights the pixel drawn on the board — no wiring, and g8 stays usable as a normal pin. Serial monitor: the C6 has TWO consoles and the panel merges both — compiled Arduino sketches are built with USB CDC on boot, so Serial is the USB-Serial-JTAG console and Serial0 is UART0; MicroPython and ESP-IDF mirror one console to both and show a single copy. All three radios are modeled: the Wi-Fi 6 station (scan/connect/DHCP/TCP on the virtual air), Bluetooth LE, and the 802.15.4 MAC that Thread and Zigbee run on (boards share the air; watch frames in the sniffer). MicroPython native code emitters work on this board: @micropython.native and @micropython.viper compile a function to machine code (measured 2-3x and 20x+ on Python-level integer loops) — worth it only for a small leaf function doing measured-hot integer or ptr8/ptr16 work, not for C-call glue.
Basics
props.resistance in ohms; props.maxPower rating in watts (0.25 default); exceed it long enough and it burns open.
Eight independent resistors in one DIP-16 straddling the channel (4116R-1-style isolated pack). Element k joins pin pk (bottom row, left to right) to pin p(17−k) — the pin directly above it. One shared props.resistance for the whole pack, like the real single-value part; each element is rated 0.25 W and burns open individually. Ideal for an 8-LED bank or seven-segment series resistors: one package instead of eight bodies.
props.inductance in henries (e.g. 0.01 = 10 mH). ~1 Ω winding resistance modeled; DC = near-short.
props.color: red|green|blue|yellow. Pin a = anode (+), k = cathode. ALWAYS use a series resistor (220–470 Ω at 5 V); sustained >30 mA burns it out.
props.capacitance in farads (100e-6 = 100 µF).
Text annotation with NO electrical function. Anchor on any free hole; props.text is the displayed string (multi-line allowed). Long text collapses to a one-line preview — clicking the label (or props.expanded=true) expands it into a wrapped block. Use to title boards or explain circuit sections.
Power
Anchor ON a "B+n" rail hole; the − lead lands on "B-n" below it. Provides the ground reference automatically. props.voltage 1.5–12 V.
props.currentRating in amps (0.5/1/2/5). Blows open on sustained over-current (i²t); place in series with the supply.
Linear 5 V regulator, pins in/gnd/out. Needs ≥7 V at in; out holds 5 V.
12 V wall supply. Anchor ON a "B+n" rail hole like the battery; − lands on "B-n". props.voltage.
DC-DC step-down: vin/gnd in, vout/gnd2 out. props.outputVoltage (default 5).
Input & control
Momentary SPST between pins a and b; the user clicks it to close.
Latching SPST toggle between pins a and b. props.closed (boolean, default false) is the lever position and persists with the document; the user clicks the lever to flick it.
props.resistance = track ohms, props.position 0–1 = wiper spot. Pins a / wiper / b.
4×4 matrix keypad: pressing key (row r, col c) connects pin r0-r3 to c0-c3, so standard MCU scanning firmware works. The user clicks keys on the canvas (click latches, click again releases).
Two-axis analog joystick (KY-023). Pins: vcc/gnd/vrx/vry/sw. VRx/VRy are wiper voltages (0..VCC) an ADC reads; sw closes to GND when pressed (add a pull-up). props.x, props.y (0–1), props.pressed set by the Inspector.
4 independent SPST switches: a0/b0 … a3/b3, straddling the channel. User toggles them.
Rotary encoder: quadrature a/b close to com as the user turns it. Add pull-ups on a and b.
Motors & output
SPDT. Energize the coil (~70 Ω, needs ~45 mA through coilA→coilB) to switch com from nc to no. The coil is inductive, so put a flyback diode across it (cathode to the driven side).
Common-cathode RGB LED: r/g/b anodes + shared k. Series resistor per channel.
props.kind: "active" (DC in → 2.4 kHz beep) or "passive" (follows drive frequency, e.g. from a 555).
RC servo: 50 Hz pulses on sig, 1–2 ms width = 0–180°.
Small DC motor (~20 Ω winding). Spins proportional to voltage.
4-pin 120 mm fan: gnd, v12 (needs 12 V!), tach (open-collector, add a pull-up), pwm control input. props.maxRpm.
2-wire 40 mm 5 V fan (pos, neg — ~25 Ω load): no PWM/tach pins, exactly like the real Pi-class part. Full speed across 5 V, stalls below ~1.5 V, never spins reversed; for speed control switch/PWM the supply side with a transistor. props.maxRpm.
NeoPixel strip, 8 addressable RGB LEDs. Wire din from an MCU pin and run firmware "example:neopixel" (uses D2). 5 V on vcc.
NeoPixel ring, 12 addressable RGB LEDs (5050) with pixel 0 beside the pads. Same WS2812 protocol as the strip: wire din from an MCU pin, 5 V on vcc. MicroPython: neopixel.NeoPixel(Pin(n), 12).
NeoPixel ring, 24 addressable RGB LEDs (5050) with pixel 0 beside the pads. Same WS2812 protocol as the strip: wire din from an MCU pin, 5 V on vcc. MicroPython: neopixel.NeoPixel(Pin(n), 24).
KY-008 laser emitter. Pins: s (signal), vcc, gnd. Power vcc (≥2.7 V) and drive s HIGH to emit; tie s to vcc for always-on. Pairs with a photoresistor as a break-beam tripwire.
Displays
Common-cathode SINGLE-DIGIT 7-segment, straddles the channel (anchor row e). Segment pins a–g + dp, common = com. Series resistors per segment.
4-digit common-cathode 7-segment module, 12 pins (5641AS pinout), straddles the channel. The 8 segment lines a–g+dp are SHARED across all four digits; each digit has its own common-cathode pin dig1–dig4. Firmware must multiplex: drive one digit's segments, pull only that digit's common LOW (others HIGH/input), hold ~1–2 ms, move on — the emulator integrates duty like the eye does, so a properly multiplexed display shows steady digits. Series resistors go on the 8 SEGMENT lines (one per segment, e.g. 220 Ω), never on the commons. Bottom row: e d dp c g dig4; top row: dig1 a f dig2 dig3 b.
SSD1306 128×64 OLED, I²C address 0x3C. Wire scl/sda to the MCU scl/sda pins and run firmware "example:oled-hello".
HD44780 16×2 character LCD, 4-bit mode. Pair with firmware "example:lcd-hello": D4–D7 = MCU d2–d5, e = d6, rs = d7.
SH1106 1.3" 128×64 OLED, I²C address 0x3C. Like the SSD1306 but page-addressed with a 2-column RAM offset: a correct SH1106 driver sets the column pointer to 2. Pins: gnd/vcc/scl/sda.
1.5″ 128×128 monochrome OLED (SH1107) on I²C at address 0x3D (so it coexists with a 0x3C SSD1306/SH1106). Page-addressed: 16 pages × 128 columns, no column offset; select page 0xB0+p, column via low nibble 0x00-0x0F + high 0x10-0x17, then write 128 data bytes. 0x20/0x21 are standalone addressing-mode commands (no argument, unlike the SSD1306). Init: 0xAE, config, 0xAF.
8×8 LED matrix driven by a MAX7219 over SPI. Pins: vcc/gnd/din/cs/clk. Clock 16-bit words MSB-first (register in high byte, data low byte); registers 1–8 are the 8 rows, 0x0C shutdown (send 1 for normal), 0x0F display-test. Use SoftSPI/bit-bang from MCU GPIOs. Powers up blank in shutdown; send 0x0C,0x01 first.
32×8 LED matrix: four cascaded MAX7219s (FC16 4-in-1 bar) over SPI. Pins: vcc/gnd/din/cs/clk. Shift 4×16-bit words per CS-low window, MSB first: the FIRST word addresses the LEFTMOST 8×8 block; register 0x00 is a no-op filler. Registers 1–8 are rows, 0x0C shutdown (send 1 per chip to wake), 0x0F test. MicroPython: NO max7219 module on the bench (import fails) — shift the 16-bit words yourself (hardware machine.SPI(1) on the ESP32-C6/C3/S3: din=g7, clk=g6 (S3: pass sck/mosi pins explicitly); machine.SPI(2) on the classic ESP32: din=g23, clk=g18; machine.SoftSPI elsewhere) + a CS GPIO; Arduino: the allowlisted MAX7219 matrix library works.
64×8 LED matrix: eight cascaded MAX7219s (8-in-1 bar) over SPI — same protocol as max7219x4 but 8×16-bit words per CS-low window (first word = leftmost block). Pins: vcc/gnd/din/cs/clk. Registers 1–8 are rows, 0x0C shutdown (send 1 per chip to wake), 0x0F test. MicroPython: NO max7219 module on the bench (import fails) — shift the words yourself (hardware machine.SPI(1) on the ESP32-C6/C3/S3: din=g7, clk=g6 (S3: pass sck/mosi pins explicitly); machine.SPI(2) on the classic ESP32: din=g23, clk=g18; machine.SoftSPI elsewhere) + a CS GPIO; Arduino: the allowlisted MAX7219 matrix library works. Wide enough for a readable scrolling ticker.
E-paper 1.54″ 200×200 (SSD1681), 4-wire SPI. Pins: vcc(3V3)/gnd/din/clk/cs/dc/rst/busy. Drive with SPI (hardware machine.SPI(1) on the ESP32-C6/C3/S3: din=g7, clk=g6 (S3: pass sck/mosi pins explicitly); machine.SPI(2) on the classic ESP32: din=g23, clk=g18; SoftSPI elsewhere) + GPIO for dc/cs/rst and poll busy (input): writes to RAM (0x24) show NOTHING until Master Activation (0x20) completes a ~1 s refresh (BUSY is high throughout). Standard init: rst pulse, 0x12 SW reset, 0x11 0x03 entry mode, 0x44/0x45 window, 0x4E/0x4F counters, stream 5000 bytes (MONO_HLSB, 1 = white), 0x22 0xF7, 0x20, wait BUSY. The image persists with power removed.
E-paper 2.13″ 250×122 (SSD1680), same 8-pin SPI header and command set as eink154. Controller RAM is PORTRAIT 122×250 (16 bytes × 250 rows, 4000 bytes); the panel mounts landscape (RAM y runs along the long edge), so draw into a 128×250 MONO_HLSB framebuf and rotate your content 90° like real drivers do. Refresh via 0x20 takes ~1 s (BUSY high); the image persists unpowered.
Color TFT 1.8″ 160×128 (ST7735), 4-wire SPI. Pins: vcc(3V3)/gnd/din/clk/cs/dc/rst. Unlike e-paper, pixels appear immediately. Set an address window with 0x2A CASET (xs_hi xs_lo xe_hi xe_lo) and 0x2B RASET (ys…), then stream RGB565 pixels (2 bytes, big-endian, 5-6-5) after 0x2C RAMWR; the counter wraps x then y across the window. 0x3A COLMOD selects the pixel format (0x05 = 16-bit), 0x36 MADCTL sets orientation. MicroPython: NO st7735 module on the bench (import fails) — send exactly this command set yourself; on the ESP32-C6/C3/S3 use HARDWARE machine.SPI(1) (din=g7, clk=g6 — the C6/C3 port defaults; the S3 needs them explicit: sck=Pin(6), mosi=Pin(7)) and on the classic ESP32 machine.SPI(2) (din=g23, clk=g18 — its defaults); frames stream at bus speed. Elsewhere machine.SoftSPI, with a GPIO for dc/cs/rst either way; Arduino: Adafruit_ST7735 (in the compile library set) works unmodified.
Color TFT 2.8″ 240×320 IPS (ST7789), 4-wire SPI — the same ST77xx command set as the ST7735 (CASET/RASET/RAMWR windows, RGB565 big-endian, MADCTL orientation, COLMOD pixel format) on a canvas with ~4.7× the pixels. Pins: vcc(3V3)/gnd/din/clk/cs/dc/rst. Arduino: Adafruit_ST7789 (in the compile library set) works unmodified. MicroPython: drive it with framebuf strips — on the ESP32-C6/C3/S3 over hardware machine.SPI(1) (din=g7, clk=g6; S3: pass the pins explicitly) or the classic ESP32 over machine.SPI(2) (din=g23, clk=g18), elsewhere over SoftSPI — a full 240×320 RGB565 frame is 150 KB, more than the heap wants in one piece.
Sensors
I²C air-quality sensor (Sensirion command set). Wire sda/scl to the MCU sda/scl pins and run firmware "example:i2c-sen55".
Bosch BME280 environment sensor, real I²C protocol at 0x76 (chip-id 0xD0 reads 0x60; calibration and compensation modeled). Arduino: the allowlisted BME280 library works. MicroPython: NO bme280 module on the bench and the raw compensation math is substantial — for a simple temperature readout prefer the TMP36 (tempsensor) on an ADC pin. Reads return the temperature/humidity/pressure configured in the Inspector. Pins: gnd/vcc(3V3)/scl/sda.
MPU-6050 6-axis IMU, real I²C protocol at 0x68. Arduino: standard MPU6050 libraries work. MicroPython: NO driver module — use raw registers: wake it by writing 0x00 to reg 0x6B, WHO_AM_I at 0x75 reads 0x68, accel X/Y/Z at 0x3B–0x40 (big-endian int16, 16384 LSB/g at the ±2g default). Reads return the acceleration/rotation configured in the Inspector. Pins: gnd/vcc(3V3)/scl/sda.
TMP36: out = 0.5 V + 10 mV/°C. props.tempC is the simulated temperature (user slider).
LDR: ~1 kΩ bright to ~1 MΩ dark. props.lightLevel 0–1 (user slider). Use in a divider.
Ultrasonic ranger: ≥10 µs pulse on trig → echo high 58 µs/cm. props.distanceCm 2–200 (user slider). Pair with firmware "example:ultrasonic" (trig=d9, echo=d8).
Single-wire temp/humidity sensor. props.tempC, props.humidity (user sliders). ARDUINO ONLY: pair with firmware "example:dht11" (data=d2). MicroPython's dht module cannot read this part on the bench (measure() raises ETIMEDOUT) — for a MicroPython temperature project use the TMP36 (tempsensor) on an ADC pin instead.
PIR motion sensor (HC-SR501). Pins: vcc/out/gnd. OUT is a ~3.3 V push-pull logic output that pulses high for props.holdSec seconds when motion is triggered (Inspector "Trigger motion" button).
MQ-2 gas/smoke sensor (LM393 breakout). Pins: vcc/gnd/aout/dout. aout ≈ 0.4 V clean air → 3.75 V dense smoke (ADC-readable); dout is push-pull and drops LOW when gas exceeds props.threshold. The heater warms up ~5 s after power (readings run high until settled; poll like real firmware does). props.gas 0–1 and props.threshold 0–1 (Inspector sliders).
Analog soil-moisture / water-level probe. Pins: vcc/gnd/aout. aout is an absolute analog voltage (~2.5 V dry → ~0.2 V saturated) an ADC reads. props.moisture 0–1 (Inspector slider).
Smart home
Zigbee smart bulb: a real emulated ESP32-C6 running genuine esp-zigbee light firmware inside a lamp. Wire gnd + vcc (5 V) only; the radio joins the bench's shared 802.15.4 air. Pair it with an esp32c6 running the Zigbee coordinator/switch firmware: it joins, binds, and real ZCL Toggle frames light it. Click it for the join state.
Zigbee smart plug: the bulb's sibling appliance — the hidden C6 decides on/off and the plug SWITCHES ITS OUTLET. Wire gnd + vcc (5 V) to power it; vout carries vcc only while the ZCL state is on (gnd2 = outlet ground), so a lamp or circuit wired to vout/gnd2 powers up when a coordinator toggles it. Click it for the join + outlet state.
Zigbee color bulb: like zigbeebulb but the sealed C6 runs color-dimmable-light firmware — ZCL On/Off, Level and Color commands tint the glass (full RGB). Wire gnd + vcc (5 V) only. Pair with a coordinator that sends color (the color remote firmware auto-cycles a wheel). Click it for join state and current color.
Zigbee contact sensor (door/window): a sealed C6 running esp-zigbee IAS-zone contact firmware. Wire gnd + vcc (5 V) to power it; c1/c2 are the contact terminals — short them (a wire, switch or button) = door CLOSED, open = door OPEN. Each change reports a genuine IAS zone status to the coordinator over the air. Click it for join + contact state.
Zigbee temperature sensor: a sealed C6 running esp-zigbee temperature firmware. Wire gnd + vcc (5 V); the probe pin is its ADC input — 0..3.3 V maps to -10..+50 °C (a trim pot wiper sweeps it). Reports ride the standard temperature-measurement cluster to a bound thermostat hub. Click it for join state + the live probe reading.
Zigbee smart button: a sealed COORDINATOR puck (esp-zigbee switch firmware). Wire gnd + vcc (5 V). It forms and opens the network; bulbs/plugs join and bind to it. CLICK AND HOLD IT briefly to press the internal button — a real ZCL Toggle to every bound light. Click (select) it for network state.
Thread sensor node: a sealed C6 running OpenThread. Wire gnd + vcc (5 V). It attaches to the Thread hub's network as a child (same fixed dataset) and reports its temperature over UDP to ff03::1:12345 every 5 s — watch the hub's serial tab and the 802.15.4 frames in the sniffer.
Matter-over-Thread smart bulb: a sealed C6 running the canonical ESP-Matter OnOff endpoint with the structured BBE1 reset lifecycle. Wire gnd + vcc (5 V) only; the bulb advertises over CHIPoBLE for the native commissioner and joins the virtual Thread fabric after commissioning. Its real Matter OnOff command drives GPIO8/WS2812 and the rendered lamp follows. Test credentials are disclosed in the firmware provenance record; this is not a certification or physical-fabric interoperability claim.
Matter controller: a sealed ESP32-S3 running the canonical ESP-Matter commissioner and Breadboard BBM1 command/event firmware. Wire gnd + vcc (5 V). It commissions Matter bulbs through real CHIPoBLE/PASE, routes operational traffic through a Thread Border Router, and invokes real CASE/OnOff commands. Use its Inspector for guided actions or the Serial panel for the preserved manual shell. Test credentials only; no certification or physical-fabric interoperability claim.
Powered wireless control surface for a Matter controller. Wire gnd + vcc (5 V); its green lamp follows the solved supply voltage and an unpowered button cannot send a command. Set props.matterTarget to the component id of a Matter bulb, color bulb, or plug. A press is accepted only after that endpoint is commissioned; it enters the real controller firmware through BBM1 and the endpoint changes only after the Matter OnOff command succeeds over CASE/Thread. This control does not claim a separate emulated radio or Generic Switch endpoint.
Matter-over-Thread color bulb: a sealed C6 running the canonical ESP-Matter extended-color-light endpoint with real OnOff/Level/Color clusters and the structured BBE1 reset lifecycle. Wire gnd + vcc (5 V). Real MoveToLevel/MoveToHueAndSaturation commands over CASE drive its internal GPIO8/WS2812 and the rendered lamp follows. Test credentials only; no certification or physical-fabric interoperability claim.
Matter-over-Thread smart plug: a sealed C6 running the canonical ESP-Matter on/off-plug-in-unit endpoint with the structured BBE1 reset lifecycle. Wire gnd + vcc (5 V); vout/gnd2 are the switched outlet — the firmware's real GPIO8 relay level ties vout to vcc when its Matter OnOff state is on, powering whatever is wired downstream. Test credentials only; no certification or physical-fabric interoperability claim.
Matter-over-Thread contact sensor: a sealed C6 running the canonical ESP-Matter contact-sensor endpoint with the structured BBE1 reset lifecycle. Wire gnd + vcc (5 V); c1/c2 are the contact terminals — short them = CLOSED. The controller reads the authoritative native Boolean State attribute (cluster 69) over real CASE; the app never invents the value. Test credentials only; no certification or physical-fabric interoperability claim.
Matter-over-Thread temperature sensor: a sealed C6 running the canonical ESP-Matter temperature-sensor endpoint with the structured BBE1 reset lifecycle. Wire gnd + vcc (5 V); the probe pin is its ADC input — 0..3.3 V maps to -10..+50 °C. The controller reads the authoritative native Temperature Measurement attribute (cluster 1026, centi-°C) over real CASE. Test credentials only; no certification or physical-fabric interoperability claim.
Modules
DS3231 real-time clock, I²C at 0x68 (BCD time registers). Time advances with simulation time from props.baseTime (ISO string), so it is deterministic. Pins: gnd/vcc(3V3)/scl/sda.
64 MB microSD card on a 6-pin SPI breakout (gnd, vcc, cs, sck, mosi, miso). HARDWARE SPI ONLY: on the Uno wire sck=d13, mosi=d11, miso=d12 (cs = any GPIO, d10 by convention); on the Pi Pico wire sck=GP18, mosi=GP19, miso=GP16 (cs = any GPIO, GP17 by convention); on the ESP32 C6/C3/S3 wire sck=g6, mosi=g7, miso=g2 (cs = any GPIO; MicroPython SPI(1) — the port defaults on C6/C3; on the S3 pass them explicitly: SPI(1, sck=Pin(6), mosi=Pin(7), miso=Pin(2))); on the classic ESP32 wire sck=g18, mosi=g23, miso=g19 (MicroPython SPI(2) — the VSPI defaults). The card ships pre-formatted FAT16, so Arduino SD.begin() and MicroPython os.mount() work immediately (Arduino on the C6/C3: call SPI.begin(6, 2, 7) before SD.begin(cs)); it answers the full SPI-mode command set (CMD0/8/55+ACMD41/58, CMD17/18/24/25) with real CRCs. Contents persist across power cycles and resets within the session; a page reload starts from a fresh formatted card. Card contents can be SEEDED from the document: props.files is a JSON manifest of up to 16 {"name","ref"} entries — name is an 8.3 file name (A–Z 0–9 _ only, max 8 chars + optional 3-char extension), ref is "asset:/firmware/<file>" (bundled) or "idb:<sha256>:<name>:<len>" (user upload); REFS ONLY, never inline bytes — and the engine writes those files into the FAT16 image at boot. The bundled photo assets asset:/firmware/photo1.565 … photo3.565 are 160×128 raw .565 images (u16 LE width + u16 LE height header, then RGB565 big-endian pixels — streams straight into an ST77xx RAMWR window; users add their own via the Inspector's Card contents panel, which converts images to .565/.o64 on upload — and video clips or animated GIFs via "Add video…", stored as a .565 stream (8-byte header: u16 LE width, height, fps, frame count; then concatenated RGB565 frames) that the example script plays paced at its header fps). See the "microSD photo frame" example for the full card→TFT pipeline.
MFRC522 RFID reader on a 7-pin SPI breakout (gnd, vcc, rst, cs, sck, mosi, miso) with one virtual MIFARE Classic 1K tag. Present or remove the tag with the Inspector's "Hold tag to reader" toggle; its UID and block contents are editable there and persist with the project. HARDWARE SPI ONLY (same fixed pins as the microSD): on the ESP32 C6/C3 wire sck=g6, mosi=g7, miso=g2 and use MicroPython SPI(1) (the port defaults; on the S3 pass sck=Pin(6), mosi=Pin(7), miso=Pin(2) explicitly); on the classic ESP32 wire sck=g18, mosi=g23, miso=g19 (SPI(2)); on the Uno wire sck=d13, mosi=d11, miso=d12; on the Pi Pico sck=GP18, mosi=GP19, miso=GP16. CS = any GPIO; RST can stay unwired (on-module pull-up). The register protocol is real (VersionReg 0x92, FIFO, CalcCRC coprocessor, Transceive/MFAuthent), so stock MFRC522 drivers run unmodified: REQA→ATQA 0x0004, anticollision→4-byte UID, SELECT→SAK 0x08, key auth (default FF FF FF FF FF FF), 16-byte block read/write, HALT. Auth is a plain key comparison (no Crypto1 cipher) and 7-byte-UID/NTAG variants are not modeled.
Semis & ICs
Silicon 1N4148, ~0.7 V drop. Pin a = anode, k = cathode; conducts a→k only.
props.breakdownVoltage (3.3/5.1/6.8/9.1/12). Wire CATHODE toward + for a shunt clamp at Vz.
Low-drop diode (~0.3 V). Pin a = anode, k = cathode.
2N2222-style BJT. Pin order on the board: e, b, c (left to right).
N-MOSFET. Pin order: g, d, s.
LM741 in a DIP-8 that must STRADDLE the channel: anchor on row e; the dy:-3 pins land on row f. Needs vcc and vee supplies.
555 timer, DIP-8 straddling the channel (anchor row e). Classic astable: see the examples.
Quad NAND, DIP-14 straddling the channel (anchor row e). Wire VCC and GND or outputs float.
Hex inverter, DIP-14 straddling the channel.
Quad AND, DIP-14 straddling the channel.
Quad OR, DIP-14 straddling the channel.
Quad XOR, DIP-14 straddling the channel.
Serial-in/parallel-out shift register, DIP-16 straddling the channel. Tie mr HIGH and oe LOW. Clock data on shcp, latch to q0–q7 with stcp. q7s cascades.
Instruments
Signal source. props: waveform (sine|square|triangle|sawtooth), frequency Hz, amplitude V, offset V. Square 0–5 V = amplitude 2.5, offset 2.5.
High-impedance meter between pos and neg; reading shows on the canvas.
Near-zero-ohm shunt in series; reading shows on the canvas.