velxio/docs/ARCHITECTURE.md

420 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Project Architecture — Velxio
## Overview
Velxio is a **fully local, multi-board emulator and electronics simulator** that runs in the browser and on the desktop. Five CPU backends, one analog (ngspice-WASM) engine, 150+ catalog components, a custom-chip SDK, and a Tauri desktop shell — all tied together by a Zustand state core and a thin FastAPI compile + bridge backend.
```text
+---------------------------------------------------------------------+
| USER (Browser or Tauri) |
| Web: http://localhost:3080 (Docker) or velxio.dev |
| Desktop: velxio-desktop (Tauri shell) |
+--------------------------------+------------------------------------+
|
v
+---------------------------------------------------------------------+
| FRONTEND (React 19 + Vite 7 + TypeScript) |
| |
| +-----------------+ +------------------+ +-------------------+ |
| | Monaco Editor | | Zustand Stores | | SimulatorCanvas | |
| | Multi-file C++ | | Editor + Sim | | Components+Wires | |
| | / .py / .chip.c | | Electrical+VFS | | Pin overlay | |
| +--------+--------+ +--------+---------+ +---------+---------+ |
| | | | |
| v v v |
| +---------------------------------------------------------------+ |
| | 5 CPU backends, one PinManager facade | |
| | - AVRSimulator (avr8js, browser) | |
| | - RP2040Simulator (rp2040js, browser, WFI fast-forward) | |
| | - Esp32Bridge (WebSocket -> QEMU Xtensa/RISC-V worker) | |
| | - RaspberryPi3Bridge (WebSocket -> QEMU raspi3b) | |
| | - RiscVCore (TypeScript ISA, Vitest only) | |
| +-------------------------------+-------------------------------+ |
| v |
| +---------------------------------------------------------------+ |
| | PinManager + PartSimulationRegistry | |
| | Digital/PWM/Analog listeners, 16+ registered output parts, | |
| | event-driven input parts (buttons, switches, encoders, ...) | |
| +---------------------------------------------------------------+ |
| v |
| +---------------------------------------------------------------+ |
| | Electrical engine (lazy, ~39 MB ngspice WASM bundle) | |
| | NetlistBuilder (Union-Find on wires) -> SPICE cards | |
| | useElectricalStore + analog instruments (V/A/scope/funcgen) | |
| +---------------------------------------------------------------+ |
| v |
| +---------------------------------------------------------------+ |
| | Component Layer | |
| | wokwi-elements (Lit) + Velxio-native Web Components | |
| | DynamicComponent renderer, ComponentRegistry (metadata.json) | |
| | ComponentPickerModal, property dialog, pin selector | |
| +---------------------------------------------------------------+ |
| |
| +---------------------------------------------------------------+ |
| | Wire System | |
| | Orthogonal routing, segment edit, 8 signal colors, | |
| | overlap offset, pin overlay, 20px grid snap | |
| +---------------------------------------------------------------+ |
| |
| Custom Chips runtime |
| WASI shim, SPI/I2C bus bridges, ChipRuntime per instance |
+--------------------------------+------------------------------------+
| HTTP (Axios) + WebSocket
v
+---------------------------------------------------------------------+
| BACKEND (FastAPI + Python 3.12) |
| http://localhost:8001 |
| |
| +-------------------------------------------------------------+ |
| | /api/compile/ sync + async Arduino / RP2040 compile | |
| | /api/compile/chip/ Custom-chip WASM compile | |
| | /api/libraries/ arduino-cli library search + install | |
| | /ws/sim/{id} QEMU bridge (Xtensa, RISC-V, ARM Pi) | |
| | /api/iot/ HTTP proxy to simulated ESP32 servers | |
| | /api/mcp/, /sse/ MCP server (stdio + SSE) | |
| +-------------------------------+----------------------------+ |
| v |
| +-------------------------------------------------------------+ |
| | Services | |
| | arduino_cli espidf_compiler qemu_manager esp32_worker | |
| | wasm_chip_runtime gpio_shim | |
| +-------------------------------+----------------------------+ |
+--------------------------------+------------------------------------+
v
arduino-cli ESP-IDF libqemu-xtensa.so
(subprocess) (subprocess) libqemu-riscv32.so
qemu-system-aarch64 (Pi)
```
The backend is **stateless** by design — no database, no auth, no per-user files. Compilation results are returned over HTTP and discarded; QEMU instances live only as long as the WebSocket session.
> **Production overlay (velxio-prod):** accounts, public profiles, persistent project URLs, the admin panel, analytics, and Pro features (custom-chip cloud build, premium components, AI assistant) live in a private repo and are layered onto the Docker image at deploy time. The OSS image you build from this repo is the same shell minus those routes. See [README.md](../README.md) §License.
---
## Data flow
### 1. Code editing
```text
User writes code
v
Monaco Editor (.ino / .cpp / .h / .py / .chip.c)
v
useEditorStore.files[] (per-board multi-file workspace)
v
Active file -> editor; file group switches on board selection
```
`useEditorStore` holds:
```typescript
interface WorkspaceFile { id: string; name: string; content: string; modified: boolean; }
files: WorkspaceFile[]
activeFileId: string
openFileIds: string[]
fileGroups: Record<string, string[]> // boardInstanceId -> file IDs
```
Operations: `createFile`, `deleteFile`, `renameFile`, `setFileContent`, `markFileSaved`, `openFile`, `closeFile`, `setActiveFile`, `loadFiles` (for `.vlx` import).
### 2. Compilation
```text
Click "Compile"
v
EditorToolbar -> compileCode({ files, board_fqbn })
v
Axios POST -> http://localhost:8001/api/compile/
v
Backend dispatch (by FQBN prefix):
arduino:avr:* -> ArduinoCLIService.compile()
rp2040:rp2040:* -> same + earlephilhower core
esp32:esp32:* -> ESP-IDF compile (full project, ccache + persistent build dir)
v
First .ino promoted to sketch.ino (RP2040 Serial redirect applied to sketch.ino only)
v
Returns hex_content / bin_payload + compile log
v
useSimulatorStore.setCompiledProgram(boardId, payload) -> auto-loadHex/loadBin
```
### 3. Simulation
#### Browser backends (AVR, RP2040)
```text
Click "Run"
v
useSimulatorStore.startSimulation(boardId)
v
Per-board simulator instance.start()
v
requestAnimationFrame loop @ ~60 FPS
v
Each frame: Math.floor(cyclesPerSecond / 60 * speed) cycles
v
For each cycle: instruction() + cpu.tick()
v
Port writes -> AVRIOPort / RP2040 IOReg listeners
v
PinManager.updatePort(portName, newValue, oldValue)
v
Per-pin callbacks fire -> PartSimulationRegistry.onPinStateChange()
v
Web Component property updates (LED color, LCD frame buffer, NeoPixel grid, ...)
```
#### QEMU backends (ESP32, ESP32-C3, Pi family)
```text
Click "Run"
v
useSimulatorStore.startSimulation(boardId)
v
WebSocket connect -> /ws/sim/{boardId}
v
Backend spawns QEMU process (or attaches libqemu lib)
v
QEMU loads firmware/OS image (qcow2 overlay for Pi)
v
QEMU emits GPIO/UART/I2C/SPI events -> backend serializes -> WebSocket
v
Frontend bridge dispatches into PinManager (same as browser backends)
```
### 4. Input components
```text
User presses pushbutton on canvas
v
Web component fires 'button-press' event
v
DynamicComponent forwards to PartSimulationRegistry.attachEvents handler
v
Simulator.setPinState(arduinoPin, LOW)
v
For browser backends: AVRIOPort.setPin(bitIndex) injects external state
For QEMU backends: pin-state command goes over WebSocket to QEMU
```
### 5. Wire creation
```text
Click pin on component A -> startWireCreation(endpoint)
v
Mouse move -> updateWireInProgress(x, y)
v
WireInProgressRenderer shows dashed green L-shape preview
v
Click pin on component B -> finishWireCreation(endpoint)
v
Wire object stored in useSimulatorStore.wires[]
v
WireLayer renders SVG orthogonal path (with overlap offset)
v
Components subscribe to Arduino pin via wire lookup (DynamicComponent)
```
### 6. Electrical co-simulation (when toggled on)
```text
Each frame (debounced):
v
NetlistBuilder runs Union-Find on wires[] -> electrical nodes
v
For each component, componentToSpice() emits SPICE cards
v
MCU digital pins -> Thevenin sources (V = port driver, R_out = driver model)
v
Lazy-loaded ngspice WASM (.op transient) computes node voltages
v
Voltmeter / ammeter / oscilloscope read probes
ADCs read their node voltage on the next analogRead()
```
---
## State stores
| Store | File | Purpose |
|-------|------|---------|
| `useEditorStore` | `frontend/src/store/useEditorStore.ts` | Multi-file workspace, file groups per board |
| `useSimulatorStore` | `frontend/src/store/useSimulatorStore.ts` | Boards, components, wires, simulators, running state |
| `useProjectStore` | `frontend/src/store/useProjectStore.ts` | Current loaded project metadata (id, slug, name) for `.vlx` export |
| `useElectricalStore` | `frontend/src/store/useElectricalStore.ts` | SPICE engine state, node voltages, probe readings |
| `useVfsStore` | `frontend/src/store/useVfsStore.ts` | Virtual File System for Pi family (Python files mounted at boot) |
| `useAuthStore` (overlay) | `pro/frontend/src/pro/store/useAuthStore.ts` | Lives only in velxio-prod overlay — OSS has no auth |
---
## Component plugin system
`PartSimulationRegistry` decouples simulation logic from rendering. A part registers two optional hooks:
```typescript
interface PartSimulation {
onPinStateChange?(pinName: string, state: PinState, element: HTMLElement): void;
attachEvents?(element: HTMLElement, simulator: Simulator, pinHelper: PinHelper): () => void;
}
```
**Registered parts (16+):**
| Part | Type | Key behavior |
|------|------|--------------|
| `led`, `wokwi-led` | Output | Pin -> `element.value` |
| `rgb-led` | Output | Digital + PWM on R/G/B -> `ledRed/Green/Blue` |
| `led-bar-graph` | Output | 10 LEDs (A1A10) -> `.values` array |
| `7segment` | Output | 8 segments (AG + DP) -> `.values` array |
| `pushbutton`, `pushbutton-6mm` | Input | Press/release -> `setPinState(pin, LOW/HIGH)` |
| `slide-switch` | Input | Change event -> pin state |
| `dip-switch-8` | Input | 8 independent switches |
| `potentiometer`, `slide-potentiometer` | Input | Value (01023) -> ADC voltage injection; also a SPICE resistor in analog mode |
| `analog-joystick` | Input | VRX/VRY (ADC) + SW (digital) |
| `servo` | Output | Polls OCR1A/ICR1 -> angle 0180° |
| `buzzer` | Output | Web Audio API, reads Timer2 registers |
| `lcd1602`, `lcd2004` | Output | Full HD44780 4-bit protocol |
| `neopixel-*` | Output | RMT-decoded 24-bit GRB frame -> per-LED color |
| `epaper-*` | Output | Waveshare driver, full-buffer refresh |
| `oscilloscope`, `voltmeter`, `ammeter`, `function-gen` | Velxio-native | Read/write the ngspice node graph |
Adding a new behavior: write the handler, register it in `PartSimulationRegistry`, ship. No edits to `SimulatorCanvas` needed.
---
## Custom chips runtime
User-defined chips compile from C to WebAssembly via `backend/app/api/routes/compile_chip.py` and run inside `frontend/src/simulation/customChips/ChipRuntime.ts`. The runtime exposes:
- **Digital GPIO** — pin watch, edge detection
- **Analog out (DAC)** — sets a node voltage that ngspice picks up
- **I2C slave, SPI slave, UART** — bus bridges that look like real peripherals to the MCU side
- **Timers, framebuffer, log** — utility hooks
- **WASI shim** — minimal subset for printf/clock
30+ example chips ship in `frontend/src/components/customChips/examples/` (Intel 4004, 8080, 8086, Z80, 74HC595, 24LC256, MCP3008, PCF8574, DS3231, …). See [CUSTOM_CHIPS.md](./CUSTOM_CHIPS.md) and [Custom Chips ESP32 Runtime](./wiki/custom-chips-esp32-backend-runtime.md).
---
## Persistence
OSS Velxio has **no server-side state**. Projects are saved as `.vlx` files — a single JSON snapshot of:
- All boards (kind, position, board options, file groups)
- All components (type, position, properties, rotation)
- All wires (endpoints, color, signal type)
- Code for every file group
- VFS contents (Pi Python files)
- Electrical-sim toggle state, instrument configurations
`frontend/src/utils/vlxFile.ts` handles export and import. The format is versioned for forward compatibility.
The Save button is registered through `frontend/src/lib/proSaveAction.ts` — OSS default is "download `.vlx`"; the velxio-prod overlay replaces it with a "Save to your account" modal.
---
## Routes
| Route | Page | Purpose |
|-------|------|---------|
| `/` | `LandingPage` | Marketing landing page |
| `/editor` | `EditorPage` | Main multi-board editor + canvas |
| `/circuit` | `CircuitSimulatorPage` | Pure analog mode (no MCU) |
| `/electronics` | `ElectronicsSimulatorPage` | Alt analog entry point |
| `/arduino`, `/arduino-mega`, `/attiny85` | Board-specific simulator pages | Direct deep-link entry |
| `/esp32`, `/esp32-c3`, `/esp32-s3` | Board-specific ESP32 pages | Same |
| `/raspberry-pi-3` | Pi 3 simulator page | Same |
| `/custom-chip` | `CustomChipSimulatorPage` | Custom-chip editor + sandbox |
| `/examples` | `ExamplesPage` | Filterable gallery (380+ projects) |
| `/examples/:id` | `ExampleDetailPage` | Single example with run-now |
| `/docs` | `DocsPage` | In-app docs reader |
The pro overlay injects `/account`, `/login`, `/:username`, `/project/:slug`, and `/admin` routes at runtime via `frontend/src/lib/proRoutes.ts`.
---
## Apps and integrations
| Surface | Status | Doc |
|---------|--------|-----|
| **Web (OSS + Pro)** | Stable, deployed at velxio.dev | This repo |
| **Desktop (Tauri)** | Pro — 30-day trial, license-gated | [desktop-app.md](./desktop-app.md) |
| **MCP server** | OSS — stdio + SSE entry points | [MCP.md](./MCP.md) |
| **IoT gateway** | OSS — HTTP proxy real browser <-> simulated ESP32 web server | `backend/app/api/routes/iot_gateway.py` |
---
## Technology Stack
### Frontend
| Layer | Tech |
|-------|------|
| Framework | React 19, Vite 7, TypeScript 5.9 |
| Editor | Monaco Editor |
| State | Zustand 5 |
| Routing | React Router 7 |
| Networking | Axios + native WebSocket |
| UI components | wokwi-elements (Lit) + Velxio-native Web Components |
| AVR sim | avr8js (npm) |
| RP2040 sim | rp2040js (npm) |
| ESP32 sim | WebSocket bridge to backend QEMU worker |
| Pi sim | WebSocket bridge to backend QEMU process |
| Electrical sim | eecircuit-engine (ngspice-WASM), lazy-loaded |
| Custom-chip runtime | WASI shim + ChipRuntime in-browser |
### Backend
| Layer | Tech |
|-------|------|
| Framework | FastAPI |
| Runtime | Python 3.12, uvicorn |
| Compile | `arduino-cli` (subprocess), ESP-IDF (subprocess), Emscripten (custom chips) |
| QEMU | lcgamboa fork (libqemu-xtensa.so, libqemu-riscv32.so) + upstream QEMU 8.1.3 (qemu-system-aarch64 for Pi) |
| Bridge | WebSockets (per-board) |
| MCP | `backend/mcp_server.py` (stdio), `backend/mcp_sse_server.py` (SSE) |
### Deploy
- **Docker** — `Dockerfile.standalone` builds a multi-stage image. Published to GHCR + Docker Hub by GitHub Actions on push to `master`.
- **Manual** — frontend + backend independently. ESP32 / Pi need QEMU `.so` libs.
- **velxio.dev** — Pro overlay layered on the base image. Lives in [velxio/velxio-prod](https://github.com/velxio/velxio-prod) (private).
---
## Architecture Advantages
- **Real emulation everywhere.** No board has a hand-rolled state machine — every backend executes the real ISA.
- **Co-simulated digital + analog.** ngspice runs in the same loop as the MCU. ADC reads return real solved voltages, not lookup-table approximations.
- **Plugin-based component behaviors.** New parts register a handler; no `SimulatorCanvas` edits.
- **Build-time component discovery.** TypeScript AST parser extracts metadata from wokwi-elements source. Override file for Velxio-native parts.
- **Stateless backend.** Compile is HTTP; QEMU is per-WebSocket. No DB, no migrations, no user state in OSS.
- **Open-core split.** OSS is fully self-hostable, anonymous, single-user. Pro features layer in via overlay without touching the OSS code path.
- **Portable projects.** `.vlx` files round-trip every detail of a canvas. No server needed to share work.
---
## Planned
- More boards (STM32, RP2350, AVR-DA family)
- Wire validation (short detection, missing-GND warnings)
- Undo/redo for canvas operations
- Streaming compile (incremental ESP-IDF builds)
- WebGPU acceleration for the canvas at >200 components
See [roadmap.md](./roadmap.md) for the full list.
---
## References
- [README](../README.md)
- [Emulator Architecture](./emulator.md)
- [Components Reference](./components.md)
- [Custom Chips Guide](./CUSTOM_CHIPS.md)
- [Electrical Simulation User Guide](./wiki/electrical-simulation-user-guide.md)
- [MCP Server](./MCP.md)
- [Desktop App](./desktop-app.md)
- Upstream: [wokwi-elements](https://github.com/wokwi/wokwi-elements), [avr8js](https://github.com/wokwi/avr8js), [rp2040js](https://github.com/wokwi/rp2040js), [lcgamboa/qemu](https://github.com/lcgamboa/qemu)