velxio/backend/app
David Montero Crespo e6df4ae8ac feat(flash): write compiled sketches to real USB boards (phases D1+D3)
Brings hardware flashing into Velxio Desktop. Per-board "Flash to
real board" entry in the canvas context menu opens a modal that
enumerates USB serial ports, lets the user pick one, then
streams arduino-cli upload output live until the board is flashed.

Backend (Phase D1) — backend/app/api/routes/flash.py (new):
  POST /api/flash/upload  (multipart: board_id, port, fqbn,
                           program_format, program)
  → SSE stream of {phase, line?, progress?} events
  → final {phase:'done', success, elapsed_ms, error?}

  - Wraps `arduino-cli upload -p <port> -i <file> --fqbn <fqbn> -v`
    so AVR (avrdude), ESP32 (esptool), RP2040 (picotool), SAMD
    (bossac) all share one code path — arduino-cli internally
    dispatches by FQBN.
  - Per-port asyncio.Lock prevents two simultaneous flashes from
    fighting over the same /dev/ttyACM0.
  - Allow-list of FQBN prefixes (arduino:avr, ATTinyCore:avr,
    rp2040:rp2040, esp32:esp32, arduino:samd) so a typo can't
    cause a confusing arduino-cli error.
  - Format allow-list (hex / bin / uf2 / elf) drives the temp
    file extension - arduino-cli uses the extension to route to
    the right uploader.
  - 8MB hard cap on the uploaded program (real sketches are
    well under that; protects against a runaway frontend).
  - X-Accel-Buffering: no header so nginx doesn't hold the SSE
    chunks until the flash completes.

Frontend (Phase D3):
  - frontend/src/services/flashService.ts (new):
      async generator streamFlash() yields parsed SSE events.
      Handles the base64-vs-text gotcha (compile returns hex_content
      as text but binary_content as base64; for binary formats we
      atob() into a Uint8Array before posting so the form upload
      sends actual bytes, not the base64 ASCII).
  - frontend/src/components/simulator/FlashModal.tsx (new):
      Three-state UI: picking (port dropdown), flashing (progress
      bar + live log), success/error (verdict + retry).
      Empty-ports state shows a Linux dialout-group hint.
  - SimulatorCanvas.tsx: board context menu gains "Flash to real
    board" entry, gated on isTauri() + presence of compiledProgram.
    Hidden in web (WebSerial is a separate sprint).
  - tauriBridge.ts: SerialPortInfo type + listSerialPorts() helper
    that invokes the Rust shell command added in Phase D2.

The sidecar already has arduino-cli on PATH (per
`pro/desktop/sidecar/main.py::_expose_bundled_arduino_cli`), so
no installer changes are needed — flash works the moment the
0.4.x desktop bundle ships with these commits.

Plan + remaining phase tracked in project/hardware-flashing/.
D2 (Rust serial enum) committed separately as a Tauri-shell-only
concern; D4 (manual smoke matrix with real boards) requires
physical hardware so it stays a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 00:20:20 -03:00
..
api feat(flash): write compiled sketches to real USB boards (phases D1+D3) 2026-05-27 00:20:20 -03:00
core Refactor code structure for improved readability and maintainability 2026-05-18 21:50:45 -03:00
database refactor(oss-split): remove auth/DB/admin stack from OSS 2026-05-14 13:36:31 -03:00
mcp
models refactor(oss-split): remove auth/DB/admin stack from OSS 2026-05-14 13:36:31 -03:00
schemas refactor(oss-split): remove auth/DB/admin stack from OSS 2026-05-14 13:36:31 -03:00
services fix(esp32): resolve libqemu path on every call + read VELXIO_QEMU_PATH 2026-05-27 03:44:09 +02:00
utils refactor(oss-split): remove auth/DB/admin stack from OSS 2026-05-14 13:36:31 -03:00
__init__.py
main.py feat(flash): write compiled sketches to real USB boards (phases D1+D3) 2026-05-27 00:20:20 -03:00