Commit Graph

153 Commits

Author SHA1 Message Date
David Montero 22de488de2 feat(microsd): SD-over-SPI card storage for AVR, RP2040 and ESP32
Add a working microSD card part backed by a FAT16 image, following the
Wokwi storage model: the project's own workspace files are auto-copied
onto the card (free), and an optional "SD Card" panel uploads extra
files (gated as a paid feature by the velxio.dev overlay; OSS default
allows it).

Frontend (in-browser AVR / RP2040):
- ProtocolParts.ts: rewrite the microsd-card part from a handshake stub
  into a real SD-over-SPI device (reply-first Ncr timing, SDSC byte
  addressing, single/multi-block read+write, CSD/CID, full CMD set).
- utils/fatImage.ts: dependency-free FAT16 super-floppy builder (8.3 + LFN).
- utils/sdCardFiles.ts: assemble the card image from workspace files plus
  uploaded files; base64 helpers.
- components/simulator/SdCardPanel.tsx + ComponentPropertyDialog: upload UI.
- DynamicComponent + useSimulatorStore: build and inject the image on run.
- lib/proSdCardGate.ts: overlay-installable gate for the upload action.
- data/examples-storage-microsd.ts: Arduino Uno + ESP32 gallery examples.

Backend (ESP32 via QEMU):
- services/esp32_sd_slave.py: synchronous SD-over-SPI slave (Python port of
  the browser part) with a sparse backing store, idle-state R1 tracking and
  real CRC16 on data blocks when the host enables CRC (CMD59) -- both
  required by ESP-IDF's sdspi driver.
- esp32_worker.py: route SPI bytes to the slave (returns MISO synchronously)
  and feed write-only bulk transfers.
- esp32_lib_manager.py + routes/simulation.py: forward the FAT image
  (sd_card.image_b64) from the start config into the worker.

Tested:
- frontend: protocol-parts, fat-image, sd-card-gate and microsd-real-firmware
  (real Arduino SD.h on avr8js) -- 86 passing.
- backend: test_esp32_sd_slave (10) covering the ESP-IDF init sequence and
  CRC16; validated end to end by running a real SD.h sketch in libqemu-xtensa
  (mount, directory listing, read and write-readback).
2026-06-11 03:59:53 +02:00
David Montero 8617d3b224 feat(library-manifest): per-board manifests + autocomplete
Library manifests are now PER-BOARD (each board carries its own velxio.json),
so two boards in one project can use different (even conflicting) libraries
without clashing — the multi-board extension of the no-clash guarantee.

- board.libraries on BoardInstance + serialisableBoard: rides in boards_json,
  so it round-trips, dirty-checks, autosaves and restores natively. This also
  removes the load-restore hacks (useLibraryManifestStore + applyProjectManifest
  deleted): the manifest is plain board state.
- loadProjectState now restores per-board boardOptions/spiffsFiles/libraries
  (it previously dropped them).
- EditorToolbar single + compile-all send the COMPILING board's libraries.
- Backend compile.py prefers the client's per-board request.libraries; the
  project-level libraries_json (now the union of all boards) is the fallback.
- buildLoadPayload migrates pre-per-board projects: seed each board with the
  project union so they keep compiling scoped.
- Library Manager 'In project' tab edits the ACTIVE board's velxio.json (shows
  the board name) and the add field is now an autocomplete (installed libs +
  index search) so users pick from a list instead of typing names.

Deletes useLibraryManifestStore.ts + applyProjectManifest.ts.
2026-06-07 06:07:48 +02:00
David Montero a68e7f8e94 feat(editor): rename boards & custom chips; show which target owns each file
Phase 2 of the run-system/UX work.

- BoardInstance gains an optional user ; boardDisplayName(board) resolver
  (name || kind label) routes every INSTANCE-label surface: file-explorer
  section header, compile console (EditorToolbar), canvas selector/tooltip/
  context-menu, Serial Monitor tabs, Oscilloscope board picker, Board Options
  subtitle. Board/component pickers keep the KIND label (they pick new boards).
- Inline rename on board AND chip section headers (double-click the name, or a
  hover pencil button). Board -> updateBoard(id,{name}); chip -> chipName in
  properties. Enter commits, Escape cancels (cancel-flag ref guards the
  unmount-fires-onBlur footgun), empty clears to the kind / 'Custom Chip'.
- FileTabs shows an owner badge naming the board/chip whose files are shown
  (resolved as a selector so it doesn't re-render on every sim pin toggle).
- CustomChipDialog no longer clobbers a user-given chipName: chip.json's name
  only seeds the blank defaults (My Chip / Custom Chip); loading an example
  relabels explicitly.
- Persistence: board name round-trips via projectPayload (+ dirty hash),
  vlxFile, ProjectByIdPage load + loadProjectState; chipName rides components_json.
- Drive-by: fixed a pre-existing rules-of-hooks violation in BoardOptionsModal
  (early return before a useCallback).

Reviewed by a 3-agent adversarial pass (completeness / persistence / correctness);
all major findings folded in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 06:22:25 +02:00
David Montero d5b9a9ceb5 feat(custom-chip): run custom chips with no board (general-purpose sim)
Velxio can now simulate one or more custom-chip CPUs with NO Arduino/ESP32
board on the canvas — a general-purpose electronics simulator, not an
MCU-only one.

- DynamicComponent: board-less parts get the real shared flat PinManager
  (instead of a no-op stub) so a custom chip's digital pin writes/reads reach
  the LEDs/inputs wired to it.
- CustomChipPart: the rAF tick respects board-less Run/Stop (freezes while
  the electrical sim is paused); board behaviour is unchanged.
- EditorToolbar.handleRun: board-less Run compiles each chip's WASM/ROM and
  re-attaches the parts (restartParts) so they pick up the fresh WASM, then
  resumes the solver.
- useSimulatorStore.restartParts(): bump hexEpoch to force part re-attach.
- New example "Z80 Larson Scanner (no board)": a programmable Z80 + 8 LEDs +
  the adjustable power-supply component, no MCU. The chip drives the LEDs
  through the synthetic-pin + ngspice path added earlier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:04:17 +02:00
David Montero c66a5b0514 fix(sim): reconcile running flag on board removal + clear multi-board residue
Three reported circuit bugs:

- Deleting the active/running board left the global `running` flag stale
  at true. That flag mirrors the active board, but removeBoard reassigned
  activeBoardId without re-deriving running, so the circuit looked
  "running" (toolbar stuck on Stop, canvas locked) and SimulatorCanvas's
  master-switch effect auto-started sibling remote boards. New Project
  hits the same path (it removes every board in a loop). removeBoard now
  re-derives running from the new active board (false if none remain).
- loadExample's single-board path called setBoardType when boards already
  existed but never dropped the extra boards a previous multi-board
  example had added, so they lingered as residue. It now removes every
  board past the first before retyping, matching the multi-board and
  board-less paths.

Adds board-removal-running-reconcile.test.ts (6 regression tests; full
suite 1917 passing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 21:19:46 +02:00
David Montero Crespo 6813891f91 feat(boards): add 6 STM32 boards (F4 Discovery, Olimex H405, Netduino 2/+2, Pill variants)
Adds stm32-f4-discovery, stm32-olimex-h405, stm32-netduino-plus2, stm32-netduino2, stm32-blackpill-f401 and stm32-bluepill-f103cb, mapped to existing qemu-lcgamboa machines (netduinoplus2, olimex-stm32-h405, netduino2, stm32vldiscovery). A generic inline board renderer (no SVG) draws the Discovery/Olimex/Netduino boards from a header pin layout; the Pill variants reuse the Blue/Black Pill SVGs. Per-board onboard-LED pin and polarity via STM32_LED. One blink+serial example per board.

tsc --noEmit clean; all new FQBN pnum variants present in STM32 core 2.12.0; worker smoke tests pass for the new machines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 03:19:26 -03:00
David Montero Crespo ca8dcedcc7 feat: STM32 (Blue Pill / Black Pill) QEMU emulation + Pro board gating
STM32 emulation (open-core, runs via libqemu-arm in the backend worker):
- backend: stm32_lib_manager + stm32_worker (GPIO, USART, I2C/SPI device models
  reusing the ESP32 slaves, live sensor updates), arduino_cli STM32 branch,
  start_stm32 simulation route.
- frontend: Stm32Bridge + Stm32BluePill(/BlackPill) web components (Wokwi SVGs),
  board kinds, Interconnect/boardPinMapping/boardProtocols wiring, example
  projects (blink, serial, I2C BMP280/MPU6050/DS1307/SSD1306/weather, 7-seg,
  RGB, button, switch, stepper, cross-board interconnect).
- Raspberry Pi 4/5 board elements + thumbnails.

Pro board gating (generic OSS->Pro seam; entitlement logic lives in the overlay):
- lib/proBoardGate.ts: isProBoardKind (STM32 + every QEMU Raspberry Pi),
  installBoardGateImpl/boardGateDecision, triggerProUpgradePrompt.
- PRO badge on those boards in the component picker; gate at the picker add +
  the run backstop (startBoard).
- backend/app/services/board_access.py: server-side enforcement seam for the
  simulation WebSocket; STM32/Pi unavailable -> Pro-framed message.
- desktop: generic QemuDownloadPrompt + Stm32QemuPrompt (download-behind-license,
  mirrors the ESP32 prompt).
- .gitignore: never ship libqemu-* binaries in the public image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:06:14 -03:00
David Montero d64eebc200 fix(stop): reset CPU to PC=0 on Stop (real-life power-cycle semantics)
The previous fix preserved display state on Stop so Resume could pick
up the multiplexed frame seamlessly — but that's Pause semantics, not
Stop. On a real Arduino, hitting the physical Stop is cutting power:
the next Run must boot from setup(), not continue at the saved PC.

User report on https://velxio.dev/example/uno-7segment :
  > empieza a contar, le doy stop en el 6, le doy run y sigue desde 6

stopBoard now:
  - calls sim.reset() (was sim.stop()) — CPU back to PC=0
  - calls hardResetPinStates() (was the soft resetPinStates) — clears
    cached states AND notifies listeners so 7-seg / NeoPixel / LCD
    blank out instead of freezing on whatever was lit.

Reset and Stop are now the same cold-boot semantics; Reset still
additionally clears serial output + baud rate. The soft
resetPinStates() helper stays for internal SPICE-classification-only
paths that don't want listener fan-out.
2026-05-26 21:28:41 +02:00
David Montero b2474bf5d1 fix(stop): preserve display state on Stop, only blank on Reset
Reporter feedback after 7aca3db: pressing Stop on the uno-7segment
example turned the 7-segment off, and pressing Start again left
random segments lit / no number at all. The previous fix made
resetPinStates() notify every listener with (pin, false) on both
Stop and Reset, which was right for Reset (full reboot) but wrong
for Stop:

  - On Stop the AVR CPU is just paused. Internally it still has
    PORTD=0xFF (or whatever the last drive was).
  - resetPinStates blanked the pinStates cache + fan-out LOW
    notifications. Display turns off, fine.
  - On Start the CPU resumes from where it paused. avr8js's port
    listener fires only for bits that CHANGED relative to its OWN
    oldValue (which still holds the pre-stop value). If oldValue
    matches the live register, no pinChange event fires for that
    bit, and the display has no signal telling it to come back on.

Split the API into two methods:

  resetPinStates()    — soft cleanup, drops outputPins only. Used by
                        stopBoard. Cached pinStates and visual state
                        stay so the resume picks up where it left off.

  hardResetPinStates() — full cleanup, drops outputPins + pinStates
                        and fan-outs (pin, false) to listeners.
                        Used by resetBoard (CPU starts at PC=0,
                        firmware re-drives every pin from setup()).

Updated the test helper clearAllPinManagerState to call
hardResetPinStates between tests so the same-state short-circuit in
triggerPinChange doesn't suppress fresh events.

All 32 vitest tests pass (AVRSimulator, interconnect-routing,
dual-arduino-software-serial, pin-position-rotation).
2026-05-26 18:54:17 +02:00
David Montero 7aca3db51c fix(reset): clear display state + don't clobber Interconnect on Reset
Two paired bugs that surfaced on the Reset button.

(1) 7-segment / NeoPixel / LCD freeze on last pattern after Reset.
    resetPinStates() was wiping the pinStates cache + outputPins set
    silently — no listener notifications fired, so visual components
    that update on pinChange kept rendering whatever segments were
    lit at the instant the user pressed Reset. Now we snapshot every
    pin that was HIGH before clearing and fan out a synthetic
    (pin, false) to each registered listener. Stateful displays
    redraw cleanly to all-off; passive listeners (analog sensors,
    debounce-only buttons) ignore the synthetic LOW and recover on
    their next real write.

(2) Cross-board serial silently dies after pressing Reset. resetBoard
    was unconditionally reassigning:
        sim.onSerialData = (ch) => appendSerial(boardId, ch);
    immediately after sim.reset(). The comment said "re-wire after
    reset" but reset() does NOT clear that property — the new USART's
    onByteTransmit chains through `this.onSerialData` which IS the
    Interconnect wrapper. The reassignment destroyed that wrapper and
    sibling-board UART forwarding (Uno TX → Nano RX) stopped working
    until a full page reload. Same root pattern as the initSimulator
    bug fixed in 5480052 — Interconnect's __icSerialHookInstalled
    flag is on the live sim, so once the wrapper is blown away
    nothing reinstalls it. Removed the reassignment and left a NOTE
    so the next person doesn't reintroduce it.

Verified the AVRSimulator + dual-arduino-software-serial +
interconnect-routing test suites still pass (26 tests).
2026-05-26 17:30:44 +02:00
David Montero 5480052379 fix(multi-board): initSimulator wiped Interconnect's UART wrapper
Cross-board UART forwarding silently broke for any project loaded
with > 1 board. User report: Arduino Uno → Arduino Nano serial echo
test where the Uno transmits fine but the Nano's Serial.available()
is never true.

Root cause traced live with chrome-devtools-mcp + temporary debug
logs in AVRSimulator.onSerialData setter and Interconnect:

  1. loadProjectState → addBoard(uno) → createSimulator → sim.onSerialData = appendSerial
  2. addBoard(nano) → same
  3. setWires → Interconnect.updateWires → ensureSerialHook(uno)
     wraps sim.onSerialData with a fan-out callback that ALSO pushes
     to the Nano's RX queue. __icSerialHookInstalled flag set.
  4. SimulatorCanvas mounts → useEffect calls store.initSimulator()
  5. initSimulator unconditionally did:
        simulatorMap.delete(boardId);
        const sim = createSimulator(...);   // ← brand-new sim
        simulatorMap.set(boardId, sim);     // ← Interconnect's wrapper is gone
     The new sim's onSerialData is just appendSerial. The old sim
     (where the wrapper lived) has been orphaned; Interconnect never
     re-installs because its flag was on the discarded sim.
  6. Run all boards → Uno.usart.onByteTransmit → this.onSerialData →
     appendSerial (Uno's monitor shows TX) but no fan-out call →
     Nano never receives anything.

initSimulator is a legacy single-board helper from the days when the
store only knew about one MCU. Multi-board flows already create
their sims in addBoard. Bail out early if a sim for the active
boardId already exists, so the legacy helper becomes a no-op when
the multi-board path has already done the work.

Verified the 3 related test suites still pass (AVRSimulator,
dual-arduino-software-serial, interconnect-routing).
2026-05-26 17:08:04 +02:00
David Montero 467ca4455f fix(canvas): wires off pins after rotation — wrapper offset was (4,6) instead of (6,6)
User report: "rotating components messes up their connections" — pressing R
on a placed component visibly slid every wire endpoint off its pin tip.

Root cause: the DynamicComponent wrapper has padding:4px + border:2px on
EVERY side, so the inner web-component element sits 6 px in from the
wrapper top-left on BOTH axes. The wire layer assumed an asymmetric
(4, 6) offset, baked into:

  * useSimulatorStore.updateWirePositions       — store.x + 4, store.y + 6
  * useSimulatorStore.recalculateAllWirePositions
      — start (startComp.x + 4, startComp.y + 6)
      — end   (endComp.x   + 4, endComp.y   + 6)
  * pinPositionCalculator.calculatePinPosition  — inverse: (componentX - 4, componentY - 6)

Unrotated the 2 px X bias was visible only as a very-slightly-off wire,
which nobody filed. When the user rotated the component, the bias
rotated WITH it — at 90° it became a 2 px Y offset (wires hanging below
the pin), at 180° a 2 px X offset on the other side, at 270° upward. UX
read as "wires disconnected".

Verified the real CSS box via chrome-devtools-mcp against several live
components on velxio.dev (RGB LED + 3 resistors + analog joystick): all
report padding-left/top = 4 px, border-left/top = 2 px, inner offset = 6
on both axes.

Fix: use (+6, +6) at every site, single source of truth in a comment
explaining padding+border arithmetic. Updated the rotation regression
test to match the corrected math (numbers shift by 2 px on every
expectation that referenced the old offset).

Pin position math, pivot derivation and the rotate-N×90° round trip
unchanged — only the offset constant moved.
2026-05-26 15:16:04 +02:00
David Montero 9e9a3e7800 fix(esp32): use JS template literals (backticks) for stub interpolation 2026-05-25 00:17:47 +02:00
David Montero 8a89322808 feat(esp32): smart WiFi/HTTP stubs so MicroPython examples actually work
Phase 7.7 follow-up. Previously the WiFi stub returned wlan.isconnected()=False
and ntptime.settime() raised OSError — sketches degraded gracefully but
features like the TIME and WEATHER screens in the smart-ui-eyes example
showed "Sync Failed" / "API Error" instead of real-looking data.

Smart stub now:
- wlan.isconnected() returns True after the first ~2 calls (simulates a
  ~1 second connection ramp)
- ntptime.settime() pre-loads machine.RTC() with the host's UTC datetime
  (captured at code-injection time), so localtime() returns real time
- urequests.get(url) returns a stubbed Response whose .json() decodes a
  payload routed by URL substring:
    "openweathermap"/"weather" → fake weather dict (temp/humidity/desc)
    "ipify"/"myip"             → fake public IP
    "worldtimeapi"             → fake ISO datetime
    everything else            → {}
- urequests.post/head also stubbed (return {"ok": True} / {})
- Both `urequests` and `requests` aliases registered

End result: smart-ui-eyes example shows real-looking time on TIME
screen and plausible weather data on WEATHER screen, no crashes.
Still no real internet (would need Phase 7 QEMU WiFi emulation), but
visually the example demos correctly.
2026-05-25 00:15:47 +02:00
David Montero 835ca6d7a8 fix(esp32): stub network + ntptime modules for MicroPython in QEMU
Inject a compat shim into the raw-REPL prelude that replaces
sys.modules["network"] and sys.modules["ntptime"] with no-op stubs
BEFORE user main.py runs.

Why: the picsimlab QEMU fork's esp32_wifi NIC emulation handles
Arduino's lightweight WiFi.h but not MicroPython's full esp_wifi_init
path. Calling network.WLAN(STA_IF) (which is what every
network-using MP sketch does) drives the firmware to wait on
peripheral status bits QEMU never sets, eventually tripping the
FreeRTOS task watchdog (TG1WDT_SYS_RESET ~26s after boot, or
TG0WDT ~14s if the NIC is partially attached).

With the stub:
  network.WLAN(STA_IF).isconnected() -> False
  network.WLAN(STA_IF).connect(...)  -> no-op
  ntptime.settime()                   -> raises OSError

Sketches that already have try/except around sync_time (which is
most of the 100-days examples) now degrade gracefully: WELCOME +
EYES screens run, TIME and WEATHER screens show their fallback
behaviour, no panic, no reboot.

Doesn't affect Arduino C++ — sketches that #include <WiFi.h> use
real WiFi.begin() and the existing esp32_wifi NIC handles those fine.

A proper fix is to extend the picsimlab WiFi emulation to support
the full ESP-IDF API, but that's a multi-day project. This stub
unblocks the 31 MicroPython examples shipping with network imports.
2026-05-23 23:13:20 +02:00
David Montero cf28b3b5ea fix(esp32): auto-enable WiFi NIC for MicroPython sketches using network module
The hasWifi auto-detection in useSimulatorStore.startBoard only matched
Arduino C++ patterns (#include <WiFi.h>, WiFi.begin). MicroPython
sketches that call `import network` or `network.WLAN(STA_IF)` were
not detected, so wifi_enabled stayed false and the backend never
attached the esp32_wifi NIC model to QEMU.

Symptom: any MicroPython ESP32 example that touches the network
module hangs in network.WLAN(STA_IF) (the constructor that triggers
esp_wifi_init internally) and the FreeRTOS task watchdog trips with
TG1WDT_SYS_RESET ~26 seconds after boot. The chip then reboot-loops.

Mirror the Pico W detector right below this one — it already handles
both Arduino and MicroPython patterns. Now ESP32 does too.

Affects 31 examples in examples-100-days.ts that use network.WLAN.
2026-05-23 22:54:10 +02:00
David Montero 1a0877f2af feat(esp32/uart): synthesize bit-level TX waveform on UART0 TX GPIO
Closes the same gap as the AVR / RP2040 commits — qemu-lcgamboa's UART
transmits the byte over the WebSocket as a 'serial_output' event with no
GPIO toggle, so an oscilloscope on the ESP32 TX pin saw nothing while
real silicon would render the 8N1 frame at the configured baud rate.

Two changes inside Esp32Bridge:

  * New `onPinChangeWithTime: (pin, state, timeMs) => void` callback
    that hooks the oscilloscope at parity with AVRSimulator /
    RP2040Simulator.  The 'gpio_change' event now also flows through it
    (timestamped with `performance.now()` — QEMU virtual time isn't
    surfaced across the wire, but at 1× sim speed the wall-clock skew
    is invisible on any practical sweep).  This also fixes the broader
    issue that ESP32 boards previously couldn't show ANY digital GPIO
    activity on the scope.

  * `emitUartTxFrame(byte, uart)` synthesizes start + 8 data LSB-first
    + stop transitions at `this.uartBaudRate` (default 115200) on the
    UART0 TX pin, mapped per board variant:
        esp32 / esp32-devkit-c-v4 / esp32-cam / wemos-lolin32-lite: GPIO1
        esp32-s3 / xiao-esp32-s3 / arduino-nano-esp32:               GPIO43
        esp32-c3 / xiao-esp32-c3 / aitewinrobot-esp32c3-supermini:   GPIO21

    Backend doesn't expose the live baud rate so we default to 115200
    (the Arduino default).  Override path:  bridge.uartBaudRate = N
    once we surface Serial.begin's argument via a backend event.

Wire-up: `bridge.onPinChangeWithTime = getOscilloscopeCallback(boardId)`
inside the three Esp32Bridge construction sites in useSimulatorStore
(setBoardType, addBoard, changeBoard).
2026-05-22 19:58:24 +02:00
David Montero Crespo ca1bf00597
Merge pull request #193 from davidmonterocrespo24/esp32-cleanup-broadcast-pwm
refactor(esp32): retire ledc_update + broadcastPwm + channelGpioMemo
2026-05-18 23:23:05 -03:00
davidmonterocrespo24 ba59fd4b4a refactor(esp32): retire ledc_update + broadcastPwm + channelGpioMemo
The SignalRouter path has been in prod through Phase 2.5 / Phase 3.3
deploys without regressions, so the temporary fallback shipped in
commit 77bf897 can come out. Closes #101.

Backend (esp32_worker.py + esp32_lib_manager.py):
- Stop emitting `ledc_update` from the 0x5000 LEDC callback and from
  the polling thread. Only `ledc_duty` (channel + duty_pct) and the
  GPIO matrix routing events ship now.
- Drop the channel→gpio reverse-lookup that fed the legacy event.

Frontend:
- Delete `PinManager.broadcastPwm` and `PinManager.pwmListenerPinCount`.
- Delete `makeLedcUpdateHandler` + its `channelGpioMemo`.
- Delete `Esp32Bridge.onLedcUpdate` field + the `case 'ledc_update':`
  message handler + the `LedcUpdate` type.
- Strip `this.onLedcUpdate = null` from 14 test mocks.
- Rewrite the `does not call broadcastPwm` guard in
  esp32-multi-servo-gpio-matrix.test.ts to assert the method itself
  no longer exists on PinManager (stronger regression guard than the
  spy version, and doesn't need vi).
- Remove the `PinManager.broadcastPwm fallback` describe block from
  esp32-servo-pot.test.ts — every test in it exercised the deleted
  fallback path.

Docs (ESP32_EMULATION.md):
- Replace `ledc_update` rows in the events / implementation tables
  with the SignalRouter trio (`ledc_duty`, `gpio_routing`,
  `gpio_routing_clear`).
- Update the visual flow diagram + the "why this matters" paragraph
  to past-tense the broadcastPwm bug.

Tests: 1886 frontend tests pass (the previously-failing
board-kinds-coverage test that needed the new Pi Zero/1/2 kinds is
also green). Backend unit suite: 279 pass, the 11 espidf_real_paths
prereq failures are environment-dependent (need arduino-cli libs in
the local shell) and unrelated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 04:05:34 +02:00
David Montero Crespo 81837eedb9 fix(spice+pipeline): LED visualization, INPUT_PULLUP, ESP32-C3, PWM fade, examples
End-to-end pipeline fixes uncovered while auditing the /examples gallery.
Each bug shipped past green unit + snapshot tests because none of those run
firmware + render LEDs. Added scripts/visual-led-test.mjs as a CDP-driven
visual harness that loads each example, runs the simulator, samples
`wokwi-led.brightness`, and asserts toggle / gradient / initial-off
invariants — exits non-zero on any regression.

Frontend simulator
- PinManager.updatePort: new optional ddrMask param. A pin is added to
  `outputPins` only if the DDR bit is set, so the PORTx write that
  enables INPUT_PULLUP (DDR=0, PORT=1) no longer falsely marks the pin
  as MCU output. AVRSimulator now reads DDRB/C/D (0x24/0x27/0x2A on
  Uno/Nano, 0x37 on ATtiny85, per-port table on Mega) and forwards it.
- AVRSimulator: pass DDR mask alongside every port-listener fire.
- BasicParts pushbutton{,-6mm}: seed pin HIGH in attachEvents so
  `digitalRead()` returns HIGH while idle. avr8js doesn't auto-simulate
  INPUT_PULLUP — without this the firmware reads LOW from boot and
  thinks the button is permanently pressed (the "LED is always on,
  pressing does nothing" UX bug).
- connectMcuEdgesToService: suppress synthetic digital edges on pins
  with active PWM, AND subscribe to onPwmChange to re-tick the netlist
  on duty changes. Fade-LED now produces a true gradient (6 distinct
  brightness levels across a fade cycle) instead of a binary 0/full
  toggle.
- CircuitSimulationService.handleMcuEdge: replace single-slot
  pendingMcuEdge with a per-pin Map. Multiple pins toggling during the
  same in-flight tick used to overwrite each other; now every pin's
  most-recent edge replays after the tick. Fixes Traffic-Light RED→
  YELLOW→GREEN sequencing.
- NetlistBuilder: new sanitizeSpiceId() helper replaces hyphens with
  underscores in V-source names. ngspice's interactive `alter` command
  treats `-` as an operator and silently no-ops on hyphenated source
  names, so mid-simulation MCU pin transitions stopped propagating
  after the first solve. MixedModeScheduler.onMcuPinChange and
  CircuitSimulationService self-heal use the same sanitizer so names
  stay consistent across emit/alter/lookup. Also added a regex-based
  fallback in step 2 so any board pin matching `GND.\d+` canonicalises
  to net "0" — ESP32-C3 dev kits expose up to 10 GND pins and the
  per-board `groundPinNames` list missed several, leaving wires
  floating instead of grounded.
- collectPinStates: emit V-sources only for pins in `outputPins`, not
  every wired board pin. Leaves INPUT pins (analog sensors on A0,
  pull-down dividers, etc.) free for the SPICE solver instead of being
  shorted to 0 V by an ideal MCU V-source.
- start.ts: extended __spiceDebug to also expose outputPinsByBoard +
  nodeVoltages + pinNetMapEntries for the visual harness.
- ESP32 / RP2040 / RISC-V / C3 simulators: pass `'mcu'` source flag to
  triggerPinChange / setPinState so the new outputPins tracking fires
  on those boards too (was AVR-only before).
- useSimulatorStore: stopBoard/resetBoard call pm.resetPinStates() so
  outputPins clears between runs; Esp32Bridge.onPinChange passes the
  `'mcu'` flag in all three places it's wired.
- types/board.ts: ATtiny85 FQBN `clock=internal16mhz` →
  `clock=16pll` (ATTinyCore 1.5.2 renamed the option).

Backend
- esp-idf-template/main/CMakeLists.txt: skip the
  `-DLED_BUILTIN=2` fallback for esp32c3 and esp32s3 targets. Both
  variants already define LED_BUILTIN in pins_arduino.h via a
  self-define macro (`#define LED_BUILTIN LED_BUILTIN` + `static const
  uint8_t LED_BUILTIN = ...;`). Pre-defining the symbol from the
  command line expanded the static-const declaration to
  `static const uint8_t 2 = ...;` — a syntax error that broke every
  ESP32-C3 / S3 build (`expected unqualified-id before numeric
  constant`).

Examples
- examples.ts: bulk-fix 72 wire endpoints that referenced
  `componentId: 'nano-rp2040'` / `'esp32-c3'` etc. (boards that don't
  exist on the canvas). Replaced with `'arduino-uno'` (the canvas
  board-id convention) and converted `D<n>` pin names to `GP<n>` for
  Pico-style boards. Affects pico-blink, pico-i2c-scanner,
  pico-i2c-rtc-read, pico-spi-loopback, c3-blink and others.

Tests
- scripts/visual-led-test.mjs: CDP-driven harness. Default suite covers
  Blink (single-pin), Button (idle-OFF invariant — catches the
  INPUT_PULLUP regression), Traffic-Light (multi-pin sequencing),
  Fade-LED (PWM gradient — ≥3 distinct levels), RGB-LED (≥3 PWM pins
  driven). Run via `npm --prefix frontend run test:visual` against a
  Chrome on `:9222` + vite on `:5174` + backend on `:8001`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 21:52:07 -03:00
David Montero Crespo 16f168ba7f feat(board-options): per-board options modal + persistence
Adds a new BoardOptionsModal accessible from the EditorToolbar that exposes
per-board options (currently used for board-specific compile flags). Wires
the modal through:

- types/boardOptions.ts            new BoardOptions shape
- types/board.ts                   BoardInstance gains `boardOptions` + `spiffsFiles`
- store/useSimulatorStore.ts       boardOptions persisted in loadProjectState
- components/editor/EditorToolbar.tsx     button to open the modal
- components/simulator/BoardOptionsModal.{tsx,css}  the modal itself
- components/simulator/SimulatorCanvas.tsx  passes the options through
- utils/projectPayload.ts          board options serialised in saved projects
- pages/ProjectByIdPage.tsx        re-includes the by-id loader needed for
                                   project URLs that reference boards with
                                   their persisted options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:50:45 -03:00
davidmonterocrespo24 18a582455c feat(pi): Phase 3.3 — Pi Zero / Pi 1 / Pi 2 armhf simulators
Closes the deferred Phase 3.3. Root-causes the Pi 2 "Attempted to
kill init" panic as `mount /dev/vda` failing with EINVAL — Debian
armmp does not have ext4 builtin (only fuseblk in /proc/filesystems).

- qemu_manager: PI_CONFIGS gains raspberry-pi-zero / -1 / -2 entries.
  All three use the armmp armhf kernel + Cortex-A7 CPU + the mmio
  virtio transport (arm-32 virt PCI fails -75 due to missing reg DT
  property). Pi Zero / Pi 1 get the small 1-core / 512 MB profile;
  Pi 2 gets 4-core / 1 GB. QEMU command builder branches on cfg.bus
  for virtio-blk-pci vs virtio-blk-device (and serial likewise).
- manifest.json: new `raspberry-pi-armhf` image_set wiring three
  assets (kernel + initramfs + zstd rootfs).
- Frontend BoardKind gains the three new kinds + an isPiBoardKind()
  helper. Replaces the eight scattered `=== 'raspberry-pi-3' ||
  === 'raspberry-pi-4' || === 'raspberry-pi-5'` branches in
  useSimulatorStore, Interconnect, loadExample, boardProtocols.
  ComponentRegistry gets three new picker entries.
- board-kinds-coverage test: ACCEPTED_UNCOVERED gains the new kinds
  (backend boards have no canvas examples).

The matching armhf build-pi-kernel.sh / build-pi-rootfs.sh changes
live in velxio-prod's scripts/ (private overlay) — the upstream
kernel build script only knows about arm64; armhf is built in the
private repo because the assets ship through the license endpoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:23:48 +02:00
davidmonterocrespo24 db5e3a8623 feat(pi3 phase 3.1+3.2): Pi 3/4/5 family via PI_CONFIGS
Backend: extract per-board config into a PI_CONFIGS dict keyed by
board_type. Pi 3/4/5 share the same arm64 image set (kernel +
initramfs + rootfs) and differ only in QEMU -cpu and -m:

  raspberry-pi-3 → cortex-a53  + 1G  (BCM2837, ARMv8 64-bit)
  raspberry-pi-4 → cortex-a72  + 2G  (BCM2711, ARMv8 64-bit)
  raspberry-pi-5 → cortex-a76  + 2G  (BCM2712, ARMv8 64-bit)

PiInstance now carries board_type so the per-board lookup happens
once at start_instance time. Unknown board_type falls back to
DEFAULT_PI_BOARD ('raspberry-pi-3') instead of erroring out (for
back-compat with older clients).

Pre-warm hook walks every unique image_set in PI_CONFIGS so the
provider only downloads each set once even when several Pi models
are registered.

Frontend:
- BoardKind union gains 'raspberry-pi-4' and 'raspberry-pi-5'.
- BOARD_KIND_LABELS + BOARD_KIND_FQBN entries for both new boards
  (FQBN null since they use the Pi VFS + Python toolchain like Pi 3).
- ComponentRegistry inserts two new component metadata entries
  cloning the Pi 3 board art with different thumbnail colours.
  Tag name reused so the same velxio-raspberry-pi-3 web element
  draws the board on the canvas — the 40-pin GPIO layout is
  identical across Pi 3/4/5.
- boardProtocols.ts: Pi 3/4/5 share the BCM physical→GPIO table
  (PI3_BCM) since the 40-pin header layout is identical.
- loadExample.ts: where 'raspberry-pi-3' is special-cased (VFS
  ingest, .cpp vs .ino filename), now matches Pi 3/4/5 alike.
- Interconnect.isPi3Bridge() recognises all three Pi family members
  so Arduino↔Pi serial routing keeps working.
- RaspberryPi3Bridge constructor gained a boardKind parameter
  defaulting to 'raspberry-pi-3'. The WebSocket 'start_pi' message
  now ships the actual board kind so the backend knows which
  PI_CONFIGS entry to use.
- useSimulatorStore.addBoard wires bridge construction for all
  three Pi family members.

Pi Zero/Pi 1/Pi 2 (armhf) come in Phase 3.3 — separate kernel
package + armhf rootfs build, no change here.

Smoke-tested inside the prod container:
  Pi 4 (cortex-a72) → reached agetty login on hvc0
  Pi 5 (cortex-a76) → reached agetty login on hvc0
Both show 'aarch64' in uname -m.
2026-05-18 15:41:29 +02:00
David Montero Crespo 32f5b407af fix(simulator): underscore-separated component ids for SPICE safety
The user reported the default editor canvas — Arduino Uno + LED +
220Ω resistor — was correctly powered (1.84 V at the LED anode,
14 mA through the diode) but the LED visual stayed dark. Only the
built-in pin-13 LED on the wokwi-arduino-uno element lit up.

Root cause: ngspice's WASM build truncates branch-current vector
keys at the first hyphen. A sense source named V_led-builtin_sense
ends up exposed under a key like v_led#branch rather than the
expected v_led-builtin_sense#branch. CircuitSimulationService and
BasicParts.ts both look up the FULL key, miss, and the LED's
brightness update treats raw as undefined → digital-fallback path
runs but the SPICE memo timestamp is fresh so HOLD keeps zero
brightness. Visible symptom: a perfectly conducting LED that never
lights.

Fix in two places:
  - Default canvas (useSimulatorStore.ts): rename 'led-builtin' /
    'r-builtin' to 'led_builtin' / 'r_builtin' (and the matching
    wire ids).
  - DynamicComponent.tsx makeNewComponent: the id template was
    'metadata.id-timestamp-rand' producing hyphens for every
    user-added component too. Switched to underscores, AND replace
    any hyphens already in metadata.id (e.g. 'led-bar-graph') so
    the prefix doesn't reintroduce the bug.

Existing saved projects whose ids contain hyphens are not migrated
here — those will keep the visual bug until either the operator
edits the components or we add a sanitisation step inside
componentToSpice + BasicParts. The next follow-up commit can add
that if you confirm this default-canvas fix works.
2026-05-18 10:01:48 -03:00
David Montero Crespo 8a6d72aa50 fix(editor): default Blink circuit needs a series 220Ω resistor
The default canvas (Arduino Uno + LED on pin 13) wired the LED
directly between pin 13 and GND. Two consequences:

  1. Real-world: that's a short across a forward-biased diode,
     blowing the LED in seconds.
  2. Simulator: ngspice can't find a steady-state branch current
     for an unprotected diode (returns NaN / indeterminate), so
     the LED visual never lights up. Only the wokwi-arduino-uno
     element's BUILT-IN LED (rendered internally by the element,
     not via wire+pinManager) was visible.

Fix: insert a 220Ω resistor between pin 13 and the LED anode,
cathode straight to GND. Same circuit every introductory Arduino
book teaches. SPICE converges, LED blinks visually on the canvas.

Reported by a user trying Blink on a fresh /editor visit.
2026-05-18 00:00:45 -03:00
davidmonterocrespo24 0f05544ca8 feat(esp32): SignalRouter — model the GPIO Matrix as first-class
Replaces the per-peripheral ad-hoc `_ledc_gpio_map` cache with a
proper signal-routing abstraction that mirrors the ESP32 SoC's
IO_MUX + GPIO Matrix exactly. Same idea as real silicon: signal
sources (LEDC channels, RMT, MCPWM, ...) → 40-entry routing table
→ GPIO pins.

Motivation (from user bug report in
velxio.dev/project/5218f9e3-136d-43b3-bba1-6cebde21e1a4): two
ESP32 servos on a solar-tracker visibly oscillated between two
positions instead of moving smoothly when the user changed LDR
sliders. Commit 77bf897 patched it (per-channel gpio memo +
broadcast guard) but the user requested a proper hardware-fidel
architecture, not patches.

Backend:
* `app/services/signal_router.py` — SignalRouter class. Forward
  index (gpio → signal_id) + reverse index (signal_id → set of
  gpios). `replace_snapshot()` returns the diff for the polling-
  fallback path; future C plugin hook becomes a push without
  touching this code.
* `app/services/esp32_signals.py` — Signal id constants from
  ESP32 TRM (LEDC HS 72-79, LS 80-87) + `ledc_signal_for_channel()`
  helper.
* `app/services/esp32_worker.py` — `_ledc_gpio_map` is gone;
  `_refresh_ledc_gpio_map` replaced by `_refresh_signal_routing`
  which emits `gpio_routing {gpio, signal_id}` events on diff.
  The 0x5000 LEDC callback and the LEDC poll thread now emit
  `ledc_duty {channel, duty_pct}` (canonical, no gpio) alongside
  the legacy `ledc_update {channel, duty, gpio}` for back-compat
  during rollout.

Frontend:
* `simulation/SignalRouter.ts` — 1-to-1 TS mirror of the Python
  class. Same forward + reverse index; same `pinsForSignal` /
  `updateRouting` / `clearRouting` API.
* `simulation/esp32-signals.ts` — Signal id constants, mirror
  of the Python module.
* `simulation/Esp32Bridge.ts` — new `onLedcDuty`, `onGpioRouting`,
  `onGpioRoutingClear` callbacks; handlers for the new event types.
* `store/useSimulatorStore.ts` — `makeLedcDutyHandler` looks up
  pins via `router.pinsForSignal(ledcSignalForChannel(channel))`
  and dispatches per pin. `makeGpioRoutingHandler` /
  `makeGpioRoutingClearHandler` keep the mirror in sync. Per-board
  `signalRouterMap` parallels `pinManagerMap` in lifecycle.
  `makeLedcUpdateHandler` (and its memo workaround from 77bf897)
  stays wired for back-compat during rollout; removed in a
  follow-up commit once prod is verified stable on the new path.

Tests:
* `test/backend/unit/test_signal_router.py` (20 tests) covers
  update/clear semantics, idempotency, multi-pin routing,
  snapshot diff, channel↔signal-id helpers, and the multi-servo
  regression scenario.
* `frontend/src/__tests__/SignalRouter.test.ts` (17 tests) is the
  mirror — same scenarios on the TS side.
* `frontend/src/__tests__/esp32-multi-servo-gpio-matrix.test.ts`
  (6 tests) drives the end-to-end SignalRouter handler pipeline,
  asserts that two servos on GPIO 13/12 via LEDC channels 0/1
  move independently (no mirroring), that re-routing carries
  cleanly, and — critically — that `PinManager.broadcastPwm` is
  never called.

Totals: +700 LOC, 1876 frontend tests pass (was 1853), 278 backend
unit tests pass (was 259).

Docs: ESP32_EMULATION.md §9.2 rewritten with the new architecture
diagram + a runbook for adding future peripherals through the
SignalRouter.

The C plugin hook in qemu-lcgamboa that would push gpio_out_sel
writes synchronously (eliminating the polling race window entirely)
is the next step — kept as a follow-up because the polling-fallback
path here already resolves the routing before each duty event
fires, so the bug is fixed end-to-end. The plugin work removes the
race condition fundamentally.
2026-05-17 05:00:53 +02:00
davidmonterocrespo24 77bf8971ff fix(esp32): multi-servo blink — don't broadcast LEDC duty across consumers
User-reported bug (project 5218f9e3, solar-tracker with 2× ESP32
servos): when LDR values change the servos visibly oscillate between
two positions instead of moving smoothly.

Root cause in useSimulatorStore.makeLedcUpdateHandler. When the
backend emits a ledc_update with gpio=-1 (the per-channel gpio_out_sel
map isn't populated yet on the very first duty change after attach),
the handler called PinManager.broadcastPwm(duty). broadcastPwm fans
the same duty out to ALL registered PWM consumers — for a project
with two servos both subscribed in the 0.01-0.20 duty range, each
broadcast made BOTH servos mirror whichever channel was last
written. Result: servoPan→91° and servoTilt→87° alternating writes
would visibly snap both servos to 87°, then 91°, then 87°…

Two-part fix:

1. PinManager grows `pwmListenerPinCount()` — number of distinct
   pins with at least one PWM consumer registered.

2. makeLedcUpdateHandler now keeps a per-board memo of
   {ledc_channel → last-known-good-gpio}. On a gpio=-1 update:
     - if the channel has a remembered gpio, route there;
     - else, only broadcast when there's at most ONE consumer
       (single-LED / single-servo setups still work);
     - otherwise drop the update — the backend's GPIO out_sel poll
       repopulates the map within a few ms and the next ledc_update
       arrives with a real gpio.

The drop is correct because the same LEDC channel keeps emitting
duty changes every Servo.write() call (~33 Hz at 30 ms loop delay),
so missing one transient gpio=-1 frame is invisible.

Tests: 1853 pass. The existing esp32-servo-pot tests already cover
the gpio>=0 happy path; the new memo path is exercised indirectly
through that handler.
2026-05-17 04:22:11 +02:00
davidmonterocrespo24 9ace6b7476 fix(store): addBoard promotes itself to active when none is valid
addBoard appended the new board to the boards[] array but never
touched activeBoardId. The default INITIAL_BOARD_ID points at a
board the picker injects on first load — but a fresh anonymous
session (or a project that landed in a state without that initial
board) can have activeBoardId pointing at nothing.

When the agent does add_board('arduino-uno') → compile_sketch, step
2 then fails with "no active board on the canvas" and the model
burns a turn on set_active_board.

The fix: at addBoard time, if activeBoardId doesn't resolve to any
existing board, promote the new board to active. If there IS a
valid active board, leave it alone — manual placements of additional
boards via the picker still keep focus on whatever the user was
working on.
2026-05-15 03:38:34 +02:00
David Montero Crespo 81ac2283c6 fix(canvas): wires follow components on rotation
Rotating a part with the 90° button used to leave every wire pinned to
the pre-rotation pixel coordinates — the component visually unhooked
from its cables. Two paths were missing:

  1. useSimulatorStore.updateComponent only triggered updateWirePositions
     for x/y changes. A rotation went through properties.rotation, so
     wires never recomputed.
  2. calculatePinPosition didn't know about rotation. Even when called,
     it returned the unrotated offset, so the new endpoints would still
     have been wrong.
  3. recordRotate (undo/redo) skipped updateWirePositions on both legs,
     so Ctrl+Z after a rotate left the canvas inconsistent.

Fix:

  - calculatePinPosition gets a 5th `rotation` argument. When non-zero,
    it finds the .dynamic-component-wrapper ancestor in the DOM, reads
    its offsetWidth/Height (layout-only, immune to CSS transforms) to
    locate the wrapper centre, and applies a 2D rotation matrix around
    that pivot. The wrapper top-left is recovered as (componentX - 4,
    componentY - 6) to match the offset convention updateWirePositions
    already uses.
  - updateWirePositions and recalculateAllWirePositions read the per-
    component rotation and thread it through.
  - updateComponent recomputes wires whenever properties.rotation
    changes, mirroring the existing x/y path.
  - recordRotate.execute and .undo both call updateWirePositions so
    Ctrl+Z keeps the canvas coherent.

Tests (pin-position-rotation.test.ts, 6 cases): unrotated identity,
90° (left edge → bottom), 180° (point reflection), 360° round-trip,
negative angles, and a store-level integration that rotates a fake
component and asserts wires[0].start moves to the rotated coordinate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:03:32 -03:00
David Montero Crespo 36ad2bef3f feat(i2c): cross-board bridging across all velxio boards (AVR/RP2040/ESP32 xtensa+riscv)
Closes the remaining gaps in cross-board I2C so any topology of
supported boards (Uno↔ESP32, two ESP32s, Uno↔Uno↔Uno, ESP32-C3
connected to anything, etc.) works end-to-end with all I2C
components including write-only sinks (SSD1306, PCF8574, LCD-I2C).

Implementation (6 phases):

1. **BFS routing in I2CBusManager**: connectToSlave + handleExternalConnect
   walk the bridge graph with a visited Set so multi-hop chains
   (A↔B↔C with the device on C) resolve transparently.  A new
   forwarder-device shim is installed at intermediate hops so the
   existing handleExternalWrite/Read/Stop machinery routes
   through without per-method visited tracking.

2. **Per-peer proxy ownership in Esp32BridgeShim**: replaces the
   global _proxiedAddrs Set with _proxiedByPeer Map so concurrent
   bridges to the same ESP32 (e.g. wired to both Uno and Pico)
   don't wipe each other's proxies on teardown.  Interconnect's
   per-wire teardown calls clearProxiesForPeer(peerBus) instead of
   clearAllProxies.

3. **BFS-aware proxy sync**: syncProxyFromPeer now walks the peer
   bus + its transitive bridges, so an ESP32 sees devices on
   boards two or more hops away.  _peerDeviceLookup keeps a flat
   addr → device map for write-forwarding and resync.

4. **Periodic resync (250 ms)**: Esp32BridgeShim runs a setInterval
   while any proxy is live, re-dumping each device with
   dumpRegisters() and pushing updateProxyI2c only when an XOR-
   stride hash changes.  This keeps RTC time advancing visible to
   ESP32 firmware without flooding the WS pipe with static
   calibration dumps.  Hash is primed during initial sync so the
   first tick doesn't push a redundant identical buffer.

5. **Write-forwarding ProxySlave → peer**: backend ProxySlave
   buffers write bytes during the transaction and emits a
   `proxy_i2c_complete` event on STOP / repeated-START.  Frontend
   Esp32Bridge dispatches the event to a new onProxyI2cComplete
   callback; the shim replays the byte sequence on the actual
   peer I2CDevice via writeByte() + stop().  Makes ESP32 firmware
   writes to peer SSD1306 actually repaint the OLED, peer PCF8574
   latch updates, peer I2CMemoryDevice register mutations propagate.

6. **ESP32-C3 routed as bridge**: Interconnect.isBrowserSim no
   longer claims c3/xiao-c3/c3-supermini — they were already
   going through Esp32Bridge per the store's ESP32_RISCV_KINDS
   routing, but Interconnect was treating them as browser sims
   which broke proxy install.  isEsp32Bridge now correctly
   includes c3 family + ESP32-S3 + Arduino Nano ESP32.

Defensive: addBoard now disposes any existing shim's proxies
before overwriting simulatorMap entry so test reruns don't leak
timers.

Tests:
- 4 BFS multi-hop tests (i2c-multi-board-slave-gap.test.ts)
- 11 cross-board scenarios + per-peer + write-forward + resync
  (i2c-esp32-multiboard-bridge.test.ts)
- 1 real-firmware E2E for write-forward via QEMU (compile +
  load + observe proxy_i2c_complete arriving with the byte)
- New sketch fixture: esp32_i2c_write_to_peer.ino

Result: 90 test files / 1295 tests pass / 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:51:39 -03:00
David Montero Crespo 71616e580d Add end-to-end tests for ESP32 I2C functionality and circuit verification
- Implemented `i2c-esp32-real-firmware.test.ts` to test ESP32 I2C communication via backend and WebSocket.
- Created `load-example-transitions.test.ts` to ensure proper loading of examples between board-less and board-based contexts.
- Added `CircuitVerificationModal.tsx` to display circuit verification results before running simulations.
- Developed `circuitVerifier.ts` to perform pre-flight checks for circuit safety, identifying potential issues like short circuits and component overloads.
- Introduced minimal ESP32 I2C master sketch `esp32_i2c_writer.ino` for testing I2C transactions.
2026-05-12 16:55:15 -03:00
naweiss f9ed3b3d44 Make wire connected to ground black even when ending in ground 2026-05-11 08:54:56 +03:00
davidmonterocrespo24 9c93d99802 fix(micropython-esp32): write helper .py files to flash before main.py
loadMicroPythonProgram only forwarded main.py (or files[0]) to the
bridge for raw-paste injection. Any auxiliary module the project
imported (mylib.py, drivers, etc.) never reached the device, so
`import mylib` died with ModuleNotFoundError.

Build a Python prelude that writes every other .py file to the
MicroPython filesystem via raw REPL, then runs main.py in the same
paste. JSON.stringify produces an ASCII-safe Python-compatible string
literal for the file body, which keeps the prelude inside the existing
chunked-UART path Esp32Bridge already uses to feed the 128-byte FIFO.

The RP2040 path was already multi-file via sim.loadMicroPython(files),
so it stays untouched.

Reproduces with the project shared in the bug report:
  https://velxio.dev/project/ac7e285c-8dc3-4d51-8751-b4aba9912f9e
2026-05-10 01:42:11 +02:00
David Montero Crespo 99ed22ba5d feat(canvas): undo/redo command pattern + history slice
Adds the foundation for canvas undo/redo. UI wiring, keyboard shortcuts,
toolbar buttons and agent-tools refactor land in follow-up commits.

useSimulatorStore.ts:
- New CanvasCommand type — { description, execute, undo }.
- HISTORY_MAX = 50 (oldest entries dropped on overflow).
- New state: history[] + historyIndex (-1 = empty).
- New APIs: pushCommand(cmd, {applyNow?}), undo, redo, canUndo, canRedo,
  clearHistory.
- New "recorded" actions that wrap raw mutators with a CanvasCommand:
  recordAddComponent, recordRemoveComponent, recordMove, recordRotate,
  recordSetProperty, recordAddWire, recordRemoveWire, recordUpdateWire.
- recordRemoveComponent captures both the component AND any wires that
  cascade with it, so undo restores both atomically.
- recordMove also re-runs updateWirePositions on undo/redo so wire
  endpoints follow the component back/forward.
- setComponents and setWires (project-load / clear paths) now call
  clearHistory inline — leaving stale commands pointing at IDs that no
  longer exist would crash on undo.

Why custom Command pattern over zundo / travels:
- The store has 30+ ephemeral fields (simulator instances, serialOutput
  growing byte-by-byte, hexEpoch counter, wireInProgress that ticks 60×/s
  on drag). Snapshot/diff middleware would either burn memory tracking
  them or need a fragile partialize allow-list.
- Per-op descriptions ("Add LED", "Move resistor") for tooltips come for
  free with this approach; zundo/travels would need to infer them.

Tests: 15/15 in src/__tests__/undo-redo.test.ts — covers cap-at-50,
redo-truncation, cascade undo of remove-component, move/rotate/property
round trips, bulk-setter clearing.

Raw mutators (addComponent / removeComponent / updateComponent / addWire /
removeWire / updateWire) are unchanged. UI handlers can keep using them
during live drags for preview frames without spamming history; the
record* actions are what drag-end, click-finish and agent tools should
call going forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:03:17 -03:00
David Montero Crespo c068612077
Merge pull request #137 from davidmonterocrespo24/esp32-cam
Esp32 cam
2026-05-02 22:40:06 -03:00
David Montero Crespo 8b1433deae refactor(spi): unify SPI bus interface across all simulators
Previous fix added an ESP32-specific code path inside ili9341Simulation
to subscribe to the QEMU worker's spi_event stream. That made the LCD
work on ESP32-CAM but left the underlying issue unsolved: every other
SPI part (custom chips, future SD-card emulators, the SSD168x ePaper
already in the codebase) would also need its own per-board branching.

The right shape: every simulator exposes a `.spi` member matching the
SAME SpiBusLike interface, and SPI parts hook .spi.onByte without
caring which board they're attached to. AVRSimulator already had
this — now everything else does too.

  frontend/src/simulation/SpiBus.ts (new)
    Defines the contract — `onByte: (mosi) => void | null` plus
    optional `completeTransfer(miso)`. Documents the single-listener
    semantics that AVR has had since day one.

  frontend/src/store/useSimulatorStore.ts
    Esp32BridgeShim gets a lazy `.spi` getter that wraps
    bridge.onSpiByte (the per-byte WS event from the QEMU worker).
    completeTransfer is a no-op because the worker drives MISO via
    its own _spi_response global. Covers ESP32 (Xtensa), ESP32-S3,
    ESP32-CAM, ESP32-C3 — every kind that routes through Esp32Bridge.

  frontend/src/simulation/RP2040Simulator.ts
    Adds a lazy `.spi` getter that re-routes rp2040.spi[0].onTransmit
    through the adapter. Default loopback (the prior behaviour) is
    preserved when no part has accessed `.spi` yet — only consumers
    that opt in see their handler invoked. Covers Pico and Pico W.

  frontend/src/simulation/parts/ComplexParts.ts
    ili9341Simulation no longer has an ESP32 special case. Single
    code path: `simulator.spi.onByte = handler`. Works on AVR,
    RP2040, all ESP32 variants. Same pattern is now available to
    every future SPI part — ssd1306, sd-card, oled, etc.

The Esp32Bridge.ts spi_event field-name fix from 6afa62e (msg.data.event
instead of the non-existent msg.data.data) stays in place — that's what
makes the per-byte stream actually arrive in the bridge.

Verified: ILI9341 + ESP32-CAM gallery example renders the live webcam
preview after a hard refresh. The same simulation code works on Arduino
Uno + ILI9341 (the existing ili9341-test-sketch in example_zip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:36:29 -03:00
David Montero Crespo e73c1d341c feat: ESP32-CAM emulation with webcam frame bridge
First open-source end-to-end emulation of the AI-Thinker ESP32-CAM
in QEMU, paired with a browser webcam → firmware bridge so users can
develop camera sketches without hardware. Status: esp_camera_init()
returns ESP_OK; OV2640 chip-id verifies (PID/VER/MIDH/MIDL exactly
match the datasheet); GPIO 25 VSYNC NEGEDGE interrupt enabled by
the upstream driver. Final piece (cam_task accepting frames) is in
progress — descriptor walker fix landed in this commit.

Backend (Python/FastAPI):
- simulation.py: camera_attach/frame/detach WS handlers
- esp32_worker.py: ctypes binding to velxio_push_camera_frame +
  feature-detection fallback for older DLLs
- esp32_lib_manager.py: forward camera commands to the worker stdin
- esp-idf-template/main/CMakeLists.txt: esp32-camera headers added
  via add_prebuilt_library + REQUIRES driver (resolves i2c_master_*
  symbols). LED_BUILTIN=2 fallback for sketches that hardcode it.

Frontend (React/TS):
- EditorToolbar.tsx: ESP32-CAM (and the rest of the ESP32 family)
  added to isQemuBoard list — Run button now starts the QEMU bridge
  for these boards instead of falling through to the AVR path
- useWebcamFrames.ts: getUserMedia → OffscreenCanvas →
  toBlob('image/jpeg') → base64 → WS at ~10 fps
- CameraToggle.tsx: header button with status colors + frame counter
- SimulatorCanvas.tsx: render CameraToggle for esp32-cam boards
- Esp32Bridge.ts: sendCameraAttach/Frame/Detach + chunked btoa
- useSimulatorStore.ts: diagnostic log on compileBoardProgram
- components-metadata.json: regen including esp32-cam component

Submodule pointer:
- wokwi-libs/qemu-lcgamboa → ff8eee0 (camera devices commit on
  davidmonterocrespo24/qemu-lcgamboa branch picsimlab-esp32)

Investigation + tests in test/test-esp32-cam/:
- 13 autosearch markdown docs (overview, SOTA, OV2640 spec, DVP/I2S
  spec, build blueprint, blockers resolved, descriptor walker fix)
- 5 sketches (camera_init, sccb_probe, dma_smoke, frame_roundtrip,
  webcam_demo) + 8 live + WS regression tests
- README with the user-facing flow

.gitignore:
- libqemu-*.dll.{pre-camera,new,bak} (rollback points, regenerated)
- wokwi-libs/esp32-camera/ (clone consumed by arduino-esp32 path,
  not part of this repo)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:29:01 -03:00
David Montero Crespo 81f3563b9f
Merge pull request #127 from naweiss/fix/oscilloscope
Fix Oscilloscope
2026-05-02 19:15:34 -03:00
David Montero Crespo 6a88375bc0 feat: persist multi-board projects + add auto-save
The project save/load pipeline only persisted a single `board_type`, so
multi-board workspaces silently lost every board except the active one
on save, and wires referencing the dropped boards' IDs orphaned to the
canvas corner on reload. An audit of the production backup found 74/306
projects (24%) with at least one orphaned wire and 174/301 non-trivial
projects whose code was still the default Blink template — strong signal
that users save once and never re-save.

Backend
- Add `boards_json` column on `projects` with idempotent ALTER TABLE in
  the lifespan migration list.
- New `FileGroup` schema + `file_groups` array on
  ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat.
- `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via
  `read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on
  read; legacy single-list `files` only updates the active group, leaving
  other boards' files intact.
- `_persist_files_from_body` honors file_groups → files → code priority.

Frontend
- `useSimulatorStore.addBoard` accepts an optional `explicitId` so
  saved board IDs can be restored verbatim (wires reference IDs literally).
- New `loadProjectState({boards, fileGroups, components, wires,
  activeBoardId})` action: tears down current boards, recreates from the
  payload, restores file groups atomically, recalculates wire positions
  on the next frame, and refreshes the Interconnect.
- `useEditorStore.replaceFileGroups` for atomic multi-group restore.
- `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through
  `buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects
  by synthesising a default board from `board_type`).

Auto-save (#useAutoSaveProject hook)
- 2.5s debounced silent PUT triggered ONLY when an authenticated user
  has a `currentProject` with a UUID. State hash detects real changes
  vs. UI-only churn; baseline is reset on project load so the just-loaded
  state isn't immediately re-saved.
- `beforeunload` flush via `fetch keepalive: true` (supports PUT +
  credentials, survives unload).
- Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error).

Backfill script (one-off, idempotent)
- `backend/scripts/backfill_boards_2026_05.py` populates `boards_json`
  for legacy projects. Heuristic per project, based on which board IDs
  the wires reference:
    Case A — wires only ref 'arduino-uno' but board_type ≠ uno:
             rename id→board_type and rewrite wire endpoints.
    Case B — single-board normal: keep verbatim.
    Case C — multi-board: recreate one board per distinct ref, infer
             kind by stripping trailing -N suffix.
  Also moves any flat files into the active board's group subdir.
  Stdlib-only, runs from host or `docker exec`.

Docker
- `Dockerfile.standalone` now copies `backend/scripts/` into the image
  so the backfill is callable via `docker exec velxio-app python
  /app/scripts/backfill_boards_2026_05.py --apply`.

Verified locally on the restored production backup (363 projects):
33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans.
Re-running the script after apply skips all 363 (idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:43:33 -03:00
naweiss ec730fb463 Bug fix: missing boardId in getOscilloscopeCallback 2026-04-30 08:28:17 +03:00
David Montero Crespo bcc7129aa3 feat: Add support for SSD168x ePaper panels
- Introduced EPaperPanels.ts to define configurations for various ePaper panels including dimensions, refresh rates, and controller details.
- Implemented SSD168xDecoder.ts to handle the decoding of SPI commands for the SSD168x family of ePaper displays.
- Created EPaperPart.ts to manage the simulation of ePaper panels, integrating with the existing simulator architecture and handling events.
- Added example sketches for 2.13", 2.9", 4.2", and 7.5" ePaper displays, demonstrating basic functionality and text rendering.
- Ensured compatibility with AVR, RP2040, and ESP32 platforms, with appropriate pin configurations for each.
2026-04-29 22:23:00 -03:00
David Montero Crespo 175b248108 feat(epaper): Add SVG layouts and emulation plan for ePaper panels
- Introduced SVG layout dimensions for Phase 1 (B/W mono) and Phase 2 (colour) ePaper panels, detailing active areas, bezels, and pin layouts.
- Developed a phased emulation plan outlining the architecture and deliverables for different panel types, including SSD168x and UC81xx.
- Created a canonical "Hello, World!" sketch for the 1.54" ePaper panel, ensuring compatibility across ESP32, Raspberry Pi Pico, and Arduino Uno.
- Implemented a pure Python SSD168x decoder to validate SPI command sets and framebuffers against specifications.
- Added tests for compiling the hello-world sketch across supported boards and for the SSD168x protocol to ensure correct framebuffer behavior.
2026-04-29 02:33:59 -03:00
David Montero Crespo 8e769f8a4e feat(multi-board): add wire-aware cross-board interconnect router
Fixes the user-reported bug where two RPi Pico W boards wired GP0↔GP1
running SerialPassthrough don't communicate. Replaces the broken
broadcast-style cross-board logic in addBoard (only routed AVR↔Pi3B,
ignored wires entirely, no RP2040↔anything path) with a wire-aware
Interconnect singleton.

Architecture: digital pin transitions are the lowest-common-denominator
abstraction. Each simulator's hardware peripherals (UART/I2C/SPI) and
bit-banging libraries (SoftwareSerial, software I2C) decode the
transitions naturally — propagate the pin and the protocols come for
free. For cross-process boards (ESP32 backend QEMU, Pi3B QEMU) a
byte-level shortcut is additionally enabled on hardware-UART pin
pairs to handle high-baud links over WebSocket latency.

Implementation:
- New simulation/Interconnect.ts singleton subscribes to wire/board
  changes via the Zustand store. Handlers per tier: browser-sim →
  pinManager.onPinChange, ESP32 → Esp32Bridge.sendPinEvent, Pi3B →
  bridge.sendPinEvent. Re-entrancy guard via per-(board,pin) Set.
- New utils/boardProtocols.ts classifies pins (uart-tx, i2c-sda, etc.)
  per board kind, used as optimization hint for the byte shortcut.
- types/wire.ts: added signalType field, exports WireSignalType /
  WireColorMap (fixes a pre-existing TS import error in wireColors).
- Deleted the bridgeMap/simulatorMap broadcast forEach blocks in
  addBoard. Initial board + future boards register with Interconnect
  via setInterconnectRuntime + store subscription.
- PinManager.resetPinStates() helper for test isolation.

Tests (16 new files, 96 tests, all passing):
- Per-pair × per-protocol matrix: dual-arduino-digital,
  dual-pico-digital, arduino-pico-digital, triple-pico-digital-chain,
  dual-arduino-hw-uart, dual-arduino-software-serial,
  arduino-pico-mixed-uart, arduino-esp32-uart, dual-esp32-uart,
  pi3-pico-uart, arduino-pico-i2c, arduino-arduino-spi,
  interconnect-routing, dual-arduino-multi-protocol (UART+I2C+SPI+
  digital + concurrent), dual-pico-multi-protocol (UART0+UART1 alt+
  I2C0+I2C1+SPI0+digital + 3-Pico star topology)
- Updated dual-pico-serial-passthrough to assert correct behaviour
- Backend test/multi_board_esp32/test_dual_esp32_serial.py for two
  real QEMU instances (skip-graceful when lcgamboa lib absent)

Verified: 1107/1107 tests pass, zero regressions, vite build OK.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 19:47:40 -03:00
David Montero Crespo 212ecd1bcb refactor: rename components and update prefixes to 'velxio-' for consistency
- Modified the index file to reflect the new naming convention for Velxio components.
- Changed JSX declarations to use 'velxio-' prefix for various components.
- Updated component overrides to replace 'wokwi-' with 'velxio-' for logic gates and other components.
- Adjusted SVG generation script to use 'velxio-' prefix for BMP280 and Raspberry Pi components.
- Marked submodules as dirty in QEMU and RP2040 libraries.
- Added .prettierignore and .prettierrc.json for consistent code formatting.
- Introduced InstrumentComponent with support for Voltmeter and Ammeter, including pin information handling.
2026-04-21 16:45:45 -03:00
David Montero Crespo 9cc9cfebd6 Add end-to-end tests for ammeter, voltmeter, and capacitor charging behavior
- Implement `ammeter-waveform.test.ts` to validate AC readings from a sine wave source.
- Create `capacitor-charge-transient.test.ts` to test the charging response of an RC circuit driven by a microcontroller pin.
- Introduce `esp32-rectifier-integration.test.ts` for testing rectifier behavior using QEMU and ESP32.
- Add helper functions in `esp32RectifierE2E.ts` for the rectifier test harness.
- Develop `voltmeter-waveform.test.ts` to ensure correct AC and DC readings from a sine wave source.
- Implement unit tests for waveform statistics in `waveform-stats.test.ts` to validate RMS, mean, peak, and interpolation functions.
- Create `waveformStats.ts` to provide statistical functions for time-domain waveform analysis.
2026-04-21 02:17:30 -03:00
David Montero Crespo 137f9ab0a0 Add tests for serial batching and spice rectifier functionality
- Implement `serial-batching.test.ts` to verify the behavior of `createSerialBatcher`, ensuring it coalesces multiple appends into a single flush, preserves byte order, and groups by board.
- Create `spice-rectifier-integration.test.ts` to test the end-to-end functionality of the Half-Wave Rectifier example, covering the entire simulation pipeline from input building to circuit solving.
- Add `spice-rectifier-live-repro.test.ts` to reproduce a live-app failure scenario, tracing through each layer of the simulation to identify potential failure points.
- Introduce `spice-signal-generator-tran.test.ts` to validate the behavior of the signal generator and ensure correct analysis type switching based on circuit components.
- Establish `serialBatcher.ts` to implement a batching mechanism for USART output, reducing the frequency of store updates and preventing React's maximum update depth error.
2026-04-20 23:56:42 -03:00
David Montero Crespo a9fbe8b3dc feat: Activate default file group in useSimulatorStore; mark subproject commits as dirty in rp2040js and wokwi-elements 2026-04-13 11:16:43 -03:00
David Montero Crespo 74e25eb4b2 feat: Update MicroPython firmware handling for ESP32; add end-to-end test and mark subproject commits as dirty 2026-04-12 23:55:11 -03:00
David Montero Crespo a64b14c94e Merge branch 'master' into feature/micropython-rp2040
# Conflicts:
#	.gitignore
#	frontend/src/components/editor/EditorToolbar.tsx
#	frontend/src/components/simulator/SerialMonitor.tsx
#	frontend/src/types/board.ts
2026-04-08 00:11:23 -03:00
David Montero Crespo ca6520e48d feat: Update I2C event handling and improve MPU-6050 slave emulation; enhance README and tests 2026-04-08 00:02:54 -03:00
David Montero Crespo 0f2c39f23b feat: Add I2C sensor support and implement ESP32 I2C slave emulation
- Introduced I2C_SENSOR_MAP for pre-registering I2C sensors in the simulator store.
- Implemented I2C slave state machines for MPU6050, BMP280, DS1307, and DS3231 sensors in esp32_i2c_slaves.py.
- Added unit tests for I2C slave functionality covering BMP280, DS1307, DS3231, and I2CWriteSink.
- Updated the simulator store to handle I2C address resolution and sensor data management.
- Marked submodules as dirty in wokwi-libs for rp2040js and wokwi-elements.
2026-04-08 00:02:43 -03:00
David Montero Crespo 689f8e71db feat: Add I2C slave emulation for MPU-6050 and BMP280 sensors
- Implemented _MPU6050Slave and _BMP280Slave classes for I2C communication.
- Enhanced main function to register these sensors and handle I2C events.
- Updated sensor management to support MPU-6050, BMP280, DS1307, DS3231, SSD1306, and PCF8574.
- Added frontend examples for BMP280 weather station and SSD1306 OLED display.
- Modified Esp32Bridge to handle new I2C transaction events.
- Updated ProtocolParts to support ESP32 path for I2C devices.
- Enhanced useSimulatorStore to manage I2C transaction listeners.
2026-04-07 15:43:09 -03:00
David Montero Crespo f43c9d019d fix: update generatedAt timestamp, improve wire properties, and mark subproject commits as dirty 2026-04-07 13:27:54 -03:00
David Montero 32acf80e0d fix: propagate has_wifi from compiler to startBoard for reliable WiFi detection
Frontend WiFi detection via file-content scanning was unreliable because
fileGroups[board.activeFileGroupId] could be an empty array (not null),
bypassing the ?? fallback to editorState.files.

Fix: the ESP-IDF compiler now returns has_wifi:bool in its compile response.
The frontend stores this on the BoardInstance and uses it in startBoard()
instead of scanning file contents. The file-content scan is kept as a
fallback for boards that haven't been compiled in this session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 22:14:40 +02:00
David Montero 7c755ca3d1 fix: use board's file group for WiFi auto-detection instead of legacy global files
startBoard() was reading useEditorStore.getState().files (the legacy global
array with the default Arduino sketch) instead of the board's specific file
group. This caused hasWifi to always be false for ESP32 boards, so QEMU
never received the -nic flag and WiFi never connected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:36:04 +02:00
David Montero f29e964e91 fix: ESP32 Run button auto-compiles and recovers firmware after page refresh
- handleRun now auto-compiles for ESP32/QEMU boards when no firmware is
  available (same behavior as AVR/RP2040 boards)
- startBoard now reloads compiledProgram into the bridge if _pendingFirmware
  was lost (e.g. after a page refresh between compile and run)
- Adds Esp32Bridge.hasFirmware() helper used by the store check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 18:47:15 +02:00
David Montero Crespo 4b8e342a71
Merge pull request #88 from davidmonterocrespo24/feature/wifi-esp32
Feature/wifi esp32
2026-04-01 01:40:09 -03:00
David Montero Crespo 54f8f2782b feat: add ESP32 WiFi/BLE emulation with ESP-IDF compilation pipeline
Replace arduino-cli with ESP-IDF 4.4.7 for ESP32 compilation — Arduino-compiled
firmware crashes in QEMU (9-28 reboots) while ESP-IDF boots cleanly (0 reboots).
The new espidf_compiler translates Arduino WiFi/WebServer sketches to native
ESP-IDF C code, compiles with cmake+ninja, and merges into 4MB flash images.

Key changes:
- ESP-IDF compiler: translates WiFi.begin/WebServer to esp_wifi/esp_http_server
- ESP-IDF project template with QEMU-optimized sdkconfig (DIO, 40MHz, no WDT)
- WiFi status parser for ESP-IDF serial logs (wifi_status, ble_status events)
- IoT Gateway HTTP reverse proxy for ESP32 web servers
- WiFi/BLE auto-detection from sketch content + visual status icons
- Static IP 192.168.4.15 matching slirp DHCP first-client range
- Docker: new espidf-builder stage with ESP-IDF 4.4.7 toolchain
- 157 tests covering WiFi/BLE for both ESP32 (Xtensa) and ESP32-C3 (RISC-V)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 20:53:56 -03:00
David Montero Crespo 7c9fd0bf2b feat: add MicroPython support for ESP32/ESP32-S3 boards via QEMU bridge
Extends MicroPython support (Phase 2) to ESP32 Xtensa boards running on
QEMU. Firmware is downloaded from micropython.org, cached in IndexedDB,
and loaded into QEMU. User code is injected via the raw-paste REPL
protocol after the MicroPython REPL boots.

- Create Esp32MicroPythonLoader.ts for firmware download/cache
- Add raw-paste REPL injection (Ctrl+A → Ctrl+E → code → Ctrl+D) to Esp32Bridge
- Extend loadMicroPythonProgram in store for ESP32 path
- Add ESP32 default MicroPython content (GPIO 2 blink)
- Simplify SerialMonitor Ctrl+C/D to work for all MicroPython boards
- Bundle fallback firmware for ESP32 and ESP32-S3
- Add all ESP32 board variants to SerialMonitor tab maps

Closes #3 (Phase 2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 23:12:24 -03:00
David Montero Crespo 990ae4be8c feat: add MicroPython support for RP2040 boards (Pico / Pico W)
Implements MicroPython emulation for Raspberry Pi Pico boards running
entirely in the browser using rp2040js. Users can toggle between
Arduino C++ and MicroPython modes via a language selector dropdown.

Key changes:
- Add LanguageMode type and BOARD_SUPPORTS_MICROPYTHON to board types
- Create MicroPythonLoader.ts: UF2 firmware parser, LittleFS filesystem
  builder (via littlefs-wasm), IndexedDB firmware caching
- Extend RP2040Simulator with loadMicroPython() method using USBCDC for
  serial REPL instead of UART
- Add setBoardLanguageMode and loadMicroPythonProgram store actions
- Update EditorToolbar with language toggle and MicroPython compile flow
- Enhance SerialMonitor with REPL label, Ctrl+C/D support
- Bundle MicroPython v1.20.0 UF2 firmware as fallback in public/firmware/
- Update useEditorStore to create main.py default for MicroPython mode

Closes #3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 21:27:41 -03:00
David Montero Crespo 5dac9bca5c feat: allow removing boards from workspace
Closes #14

- Right-click context menu on boards with "Remove board" option
- Delete/Backspace key removes the active board (when no component selected)
- Confirmation dialog before removal showing connected wire count
- Removing a board also removes all connected wires and cleans up
  file groups, simulators, bridges, and VFS
- Cannot remove the last remaining board (button disabled + guard)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 01:55:10 -03:00
David Montero Crespo 0290b24a47 feat: remove unnecessary logging and diagnostic dumps in ESP32 worker; update WS2812 event handling in simulator store 2026-03-24 14:38:08 -03:00
David Montero Crespo 01f75cf313 feat: implement eager scan for LEDC GPIO mapping and add tests for race condition fix 2026-03-24 13:54:44 -03:00
David Montero Crespo aa6522fd60 feat: Enhance DHT22 sync handling, improve ADC support for ESP32, and update component metadata 2026-03-23 10:37:06 -03:00
David Montero Crespo 083d8d69a8 feat: Add HC-SR04 sensor support and enhance DHT22 response timing in simulation 2026-03-22 18:39:09 -03:00
David Montero Crespo 6a55f58e46 feat: Implement generic sensor registration for ESP32 and RP2040
- Added board-agnostic sensor registration methods in RP2040Simulator.
- Enhanced ComplexParts to handle LEDC PWM duty updates for ESP32.
- Updated ProtocolParts to check if the simulator handles sensor protocols natively, delegating to backend if applicable.
- Introduced pre-registration of sensors in useSimulatorStore for ESP32 to prevent race conditions.
- Added tests for ESP32 DHT22 sensor registration flow, ensuring proper delegation and fallback mechanisms.
- Created tests for ESP32 Servo and Potentiometer interactions, verifying PWM subscriptions and ADC handling.
2026-03-22 18:03:17 -03:00
David Montero Crespo f5257009cd feat: implement DHT22 sensor support with attach, update, and detach functionality in ESP32 simulation 2026-03-22 15:17:44 -03:00
David Montero Crespo 84370e6f8d feat: enhance simulator interfaces and add ESP32 bridge shim for improved component interaction 2026-03-22 02:25:58 -03:00
David Montero Crespo 765a3ff711 feat: remove RiscVBoard references and related code from various components 2026-03-18 23:33:20 -03:00
David Montero Crespo 7053b6f2c8 feat: update ESP32-C3 simulator and add emulation tests
- Updated components-metadata.json with new generated timestamp.
- Refactored Esp32C3Simulator.ts to remove unnecessary debug variables and logging, and added support for additional ROM functions.
- Modified useSimulatorStore.ts to clarify bridge usage for ESP32 boards.
- Updated submodules for QEMU and other libraries to indicate dirty state.
- Added test_esp32c3_emulation.py for end-to-end testing of ESP32-C3 emulation, including compilation, flash image merging, and GPIO event checking.
2026-03-18 23:30:45 -03:00
David Montero Crespo fdbc37b69b feat: enhance WebSocket error handling and cleanup logic in simulation and ESP32 libraries 2026-03-17 02:28:08 -03:00
David Montero Crespo c19a6cb848 feat: update PWM handling in SimulatorCanvas and enhance I2C Scanner example with SSD1306 OLED integration 2026-03-16 22:15:52 -03:00
David Montero Crespo d35ad2d2f7 Refactor code structure for improved readability and maintainability 2026-03-16 18:31:46 -03:00
David Montero Crespo 20ccd87d1b feat: enhance ESP32 emulation with GPIO mapping and ADC support 2026-03-16 13:11:59 -03:00
David Montero Crespo bc31ee76c1 feat: enhance oscilloscope and simulator functionality
- Added `onPinStateChange` method to `ChipParts` for handling pin state changes in 7-segment displays.
- Updated `useOscilloscopeStore` to allow independent monitoring of multiple boards by adding `boardId` to channels and modifying `addChannel` method.
- Modified `getOscilloscopeCallback` in `useSimulatorStore` to filter channels based on `boardId` and pin number.
- Adjusted component and board position calculations in `useSimulatorStore` to account for wrapper offsets.
- Updated submodule references for `qemu-lcgamboa`, `rp2040js`, and `wokwi-elements` to indicate dirty states.
2026-03-16 10:31:02 -03:00
David Montero Crespo 74e7ec58c1 Add bare-metal ESP32-C3 blink example with simulator support
- Implemented a simple LED blink program in C for the ESP32-C3, targeting RV32IMC architecture.
- Created a linker script to define memory layout for the ESP32-C3.
- Added build script to compile the blink program using the riscv32-esp-elf-gcc toolchain.
- Developed Esp32C3Simulator to emulate ESP32-C3 behavior in the browser, including GPIO and UART functionality.
- Implemented utility functions to parse ESP32 flash images and handle loading of binary data.
- Included disassembly output for debugging purposes.
- Added necessary test fixtures for the blink example.
2026-03-15 18:40:08 -03:00
David Montero Crespo 7ab0f132a3 feat: add visual components for ATtiny85, Raspberry Pi Pico W, and RISC-V Board
- Implemented ATtiny85 visual component with DIP-8 layout and built-in LED.
- Added Raspberry Pi Pico W web component using official SVG and pin mapping.
- Created RISC-V Board visual component with SOP-20 style and built-in LED.
- Introduced RiscVCore class for minimal RV32I ISA interpreter with memory model.
- Developed RiscVSimulator class for CH32V003-compatible simulator with UART and GPIO support.
- Updated subproject commits for qemu-lcgamboa, rp2040js, and wokwi-elements to dirty state.
2026-03-15 15:39:26 -03:00
David Montero Crespo c69094e7d7 feat: Implement Virtual File System for Raspberry Pi 3 with context menu and upload functionality
- Add VirtualFileSystem component for managing files and directories.
- Integrate useVfsStore for state management of the virtual file system.
- Implement context menu for file operations: New File, New Folder, Rename, Delete.
- Add upload functionality to send files to Raspberry Pi.
- Create default file structure for new Raspberry Pi boards.
- Enhance editor with board-aware features and compile/run orchestration.
- Introduce CompileAllProgress component for tracking compilation status across boards.
- Redesign SerialMonitor to support multiple boards with tabbed interface.
- Establish Raspberry Pi specific workspace with terminal and file system integration.
2026-03-15 14:04:01 -03:00
David Montero Crespo 405ff1f017 Refactor wire management and rendering logic
- Updated wire structure to replace control points with waypoints for better handling of wire paths.
- Introduced new utility functions for wire hit detection and rendering segments.
- Enhanced wire creation process to support dynamic waypoints and color assignment.
- Implemented a new ESP32 worker for improved simulation handling.
- Added utility functions for generating orthogonal paths and auto-coloring wires based on pin names.
- Improved compatibility with existing projects by ensuring backward compatibility with wire data structures.
2026-03-14 22:55:13 -03:00
David Montero Crespo b0b3a8763d V1 feat: Enhance ESP32 emulation support and logging
- Added detailed logging for GPIO changes, system events, and errors in simulation websocket.
- Improved ESP32 firmware handling by merging individual binaries into a single 4MB flash image.
- Updated ESP32 bridge to handle serial output and GPIO changes with appropriate logging.
- Introduced integration test for ESP32 emulation, covering compilation, WebSocket connection, and event handling.
- Enhanced examples to include ESP32 projects and updated the examples gallery to reflect new board types.
- Refactored simulator store to manage ESP32 bridge and simulator instances more effectively.
- Updated requirements to include esptool for ESP32 firmware management.
2026-03-14 16:57:22 -03:00
David Montero Crespo 4a7c9e2e55 feat: enhance ESP32 emulation with GPIO pinmap and improved QEMU initialization handling 2026-03-14 12:05:35 -03:00
David Montero Crespo dc03c28fff feat: Enhance ESP32 support in simulator and QEMU management 2026-03-13 22:55:37 -03:00
David Montero Crespo c3df484b4f feat: Implement ESP32 emulation via QEMU and multi-board support for Raspberry Pi and Arduino
- Added ESP32 emulation plan and architecture documentation.
- Created `esp_qemu_manager.py` for managing ESP32 QEMU instances.
- Modified backend API routes to support ESP32 firmware loading and GPIO handling.
- Introduced `Esp32Bridge.ts` for frontend communication with ESP32 instances.
- Refactored simulator store to support multiple boards, including Raspberry Pi and Arduino.
- Created `RaspberryPi3Bridge.ts` for WebSocket communication between frontend and backend for Raspberry Pi.
- Updated QEMU manager to handle multiple serial ports for Raspberry Pi GPIO communication.
- Enhanced SimulatorCanvas to render multiple boards and manage wire routing between them.
- Implemented board picker modal for selecting and adding boards to the canvas.
- Updated editor to support multiple file groups per board.
- Added migration logic for loading old project formats into the new multi-board structure.
- Ensured backward compatibility with existing components and functionality.
2026-03-13 20:35:48 -03:00
David Montero Crespo 17b1a0f058 feat: add board types and mappings for Raspberry Pi 3B
- Introduced new BoardKind types for Raspberry Pi 3B and updated BoardInstance interface.
- Added BOARD_KIND_LABELS and BOARD_KIND_FQBN mappings for new board types.
- Implemented physical to BCM GPIO mapping for Raspberry Pi 3B in boardPinMapping utility.
- Updated BOARD_COMPONENT_IDS to include Raspberry Pi 3B.
- Enhanced isBoardComponent function to support new board type.
- Modified boardPinToNumber function to handle pin mapping for Raspberry Pi 3B.
2026-03-12 23:39:04 -03:00
David Montero Crespo b89f85e5f7 Merge branch 'feat/raspberryPi_emuation' of https://github.com/davidmonterocrespo24/wokwi_clon into feat/raspberryPi_emuation 2026-03-12 08:26:56 -03:00
David Montero Crespo 13997ff491 feat: add documentation page and Arduino serial integration test
- Created a new DocsPage component for project documentation with links to GitHub and Discord.
- Added Arduino sketch for serial communication test between Raspberry Pi and Arduino.
- Implemented avr_runner.js to emulate ATmega328P and bridge serial communication over TCP.
- Developed a Python test script to validate the serial integration between the emulated Raspberry Pi and Arduino.
2026-03-12 08:17:29 -03:00
copilot-swe-agent[bot] ad0656b1cc feat: add built-in oscilloscope / logic analyzer panel
- Add useOscilloscopeStore with ring-buffer sample storage and channel management
- Add onPinChangeWithTime callback to AVRSimulator (fires on every bit transition with cycle-derived timestamp)
- Add onPinChangeWithTime callback to RP2040Simulator (fires on GPIO state change)
- Wire oscilloscope callbacks in useSimulatorStore (initSimulator + setBoardType)
- Create Oscilloscope React component with canvas-based waveform rendering
- Add oscilloscope panel to EditorPage (resizable bottom panel, same as SerialMonitor)
- Add 'Scope' toggle button to SimulatorCanvas toolbar

Co-authored-by: davidmonterocrespo24 <47928504+davidmonterocrespo24@users.noreply.github.com>
2026-03-11 15:09:24 +00:00
David Montero Crespo 1018609ed4 feat: add Arduino Mega support to simulator
- Introduced ArduinoMega component for rendering in the simulator.
- Updated SimulatorCanvas to handle Arduino Mega board type.
- Enhanced AVRSimulator to support ATmega2560 architecture, including PWM pin mapping and port management.
- Modified PinManager to accommodate Mega's non-linear pin mapping.
- Updated boardPinMapping utility to include Mega analog pins.
- Adjusted Wokwi import/export functionality to recognize and handle Arduino Mega.
- Updated useSimulatorStore to initialize AVRSimulator with the correct board variant.
2026-03-09 10:08:14 -03:00
David Montero Crespo faa6f6b7b3 feat: add ILI9341 Cap Touch display and related components; implement home screen and example sketches 2026-03-09 02:31:04 -03:00
David Montero Crespo 1206a34e64 feat: implement auto-pan feature for board visibility after project load and normalize pin names for component connections 2026-03-08 16:30:49 -03:00
David Montero Crespo 41dfd20583 feat: Add Arduino Nano support to simulator and update related components 2026-03-07 23:14:33 -03:00
David Montero Crespo f2b275c03d feat: Enhance board positioning by integrating boardPosition state in export/import functions and updating component dragging logic 2026-03-07 19:29:23 -03:00
David Montero Crespo 5f2abc8cd9 feat: Auto-generate control points for wires without them to ensure proper segment dragging 2026-03-07 14:13:35 -03:00
David Montero Crespo 9b8747349f feat: enhance RP2040 and AVR simulators with serial baud rate handling; update editor toolbar and library manager modal for improved state management and UI 2026-03-05 21:07:10 -03:00
David Montero Crespo 4ba2ccb877 Refactor simulator store to unify serial data handling and add board pin mapping utility
- Simplified serial data handling in `useSimulatorStore` for both AVR and RP2040 simulators.
- Introduced `boardPinMapping.ts` to map wokwi-element pin names to simulator GPIO/pin numbers for Arduino Uno and Nano RP2040.
- Added `compilationLogger.ts` to parse compile results into structured log entries for better console output.
2026-03-05 21:07:03 -03:00
David Montero Crespo 5d175abdcf feat: add Serial Monitor component and integrate with AVR simulator
- Implemented SerialMonitor component to display serial output and allow user input.
- Enhanced AVRSimulator to handle USART communication and transmit serial data.
- Updated useSimulatorStore to manage serial output state and toggle visibility of the Serial Monitor.
- Added example Arduino sketches for serial communication, including Serial Echo and Serial LED Control.
- Introduced I2CBusManager to manage virtual I2C devices and integrated with AVRSimulator.
2026-03-05 06:56:14 -03:00
David Montero Crespo 7944ce2de3 feat: add support for RP2040 board, including simulator and compilation enhancements 2026-03-04 19:28:33 -03:00
David Montero Crespo c2f07665b4 feat: add react-router-dom for routing and enhance wire rendering with automatic offsets 2026-03-04 18:03:54 -03:00
David Montero Crespo ef7e86bc1e feat: Enhance simulator with new components and event handling
- Updated components-metadata.json with new generation timestamp.
- Added event handling for button presses and releases in DynamicComponent.
- Improved ExamplesGallery with new styles for placeholders and previews.
- Introduced LCD 20x4 display example with corresponding code and wiring.
- Enhanced SimulatorCanvas to subscribe components to pin changes.
- Implemented PartSimulationRegistry for managing component simulation logic.
- Added basic and complex parts simulation including pushbuttons, LEDs, and LCDs.
- Created utility functions for capturing canvas previews and generating SVG previews for example projects.
2026-03-04 13:36:33 -03:00
David Montero Crespo b7e9bf64c4 feat: implement examples gallery and editor page with routing and example loading functionality 2026-03-03 22:25:52 -03:00