420 lines
18 KiB
Markdown
420 lines
18 KiB
Markdown
# 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 (A1–A10) -> `.values` array |
|
||
| `7segment` | Output | 8 segments (A–G + 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 (0–1023) -> ADC voltage injection; also a SPICE resistor in analog mode |
|
||
| `analog-joystick` | Input | VRX/VRY (ADC) + SW (digital) |
|
||
| `servo` | Output | Polls OCR1A/ICR1 -> angle 0–180° |
|
||
| `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)
|