23 KiB
Project Architecture - Velxio Arduino Emulator
Overview
This project is a fully local Arduino emulator using official Wokwi repositories for maximum compatibility. It features real AVR8 CPU emulation, 48+ interactive electronic components, a comprehensive wire system, and a build-time component discovery pipeline.
┌─────────────────────────────────────────────────────────────────────┐
│ USER (Browser) │
│ http://localhost:5173 │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ FRONTEND (React 19 + Vite 7) │
│ │
│ ┌────────────────┐ ┌──────────────────┐ ┌───────────────────┐ │
│ │ Monaco Editor │ │ Zustand Stores │ │ SimulatorCanvas │ │
│ │ (Code Edit) │ │ (Editor+Sim) │ │ (Components+ │ │
│ │ C++ / Arduino │ │ │ │ Wires+Pins) │ │
│ └────────┬───────┘ └────────┬─────────┘ └────────┬──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ AVRSimulator (avr8js) │ │
│ │ CPU 16MHz · Timer0/1/2 · USART · ADC · PORTB/C/D │ │
│ │ ~60fps · 267k cycles/frame · Speed 0.1x-10x │ │
│ └──────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ PinManager + PartSimulationRegistry │ │
│ │ Digital/PWM/Analog listeners · 16 registered parts │ │
│ │ LED · RGB · Button · Pot · LCD · Servo · Buzzer · etc. │ │
│ └──────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 48+ wokwi-elements (Lit Web Components) │ │
│ │ DynamicComponent renderer · ComponentRegistry (metadata) │ │
│ │ ComponentPickerModal · Property dialog · Pin selector │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Wire System │ │
│ │ Orthogonal routing · Segment editing · 8 signal colors │ │
│ │ Overlap offset · Pin overlay · Grid snapping (20px) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────────────┘
│ HTTP (Axios)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ BACKEND (FastAPI + Python) │
│ http://localhost:8001 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ POST /api/compile/ → Compile Arduino code to .hex │ │
│ │ GET /api/compile/boards → List available boards │ │
│ │ GET / → API info │ │
│ │ GET /health → Health check │ │
│ └──────────────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ArduinoCLIService │ │
│ │ Auto-installs arduino:avr core │ │
│ │ Temp directory + subprocess.run via asyncio.to_thread │ │
│ └──────────────────────────────┬──────────────────────────────┘ │
└─────────────────────────────────┼──────────────────────────────────┘
│
▼
┌──────────────────────┐
│ arduino-cli │
│ (Local system) │
└──────────────────────┘
Data Flow
1. Code Editing
User writes Arduino code
↓
Monaco Editor (C++, dark theme, autocomplete)
↓
Zustand useEditorStore
↓
State: { code, theme, fontSize }
2. Compilation
Click "Compile"
↓
EditorToolbar.tsx → compileCode()
↓
Axios POST → http://localhost:8001/api/compile/
↓
Backend: ArduinoCLIService.compile()
↓
arduino-cli compile --fqbn arduino:avr:uno --output-dir build/
↓
Reads build/sketch.ino.hex → returns hex_content
↓
Frontend: useSimulatorStore.setCompiledHex(hex)
↓
Auto-calls loadHex() → CPU + peripherals created
3. Simulation (Real AVR8 Emulation)
Click "Run"
↓
useSimulatorStore.startSimulation()
↓
AVRSimulator.start()
↓
requestAnimationFrame loop @ ~60fps
↓
Each frame: Math.floor(267000 × speed) cycles
↓
For each cycle:
├── avrInstruction(cpu) ← Execute AVR instruction
└── cpu.tick() ← Update peripherals/timers
↓
Port writes → AVRIOPort listeners
↓
PinManager.updatePort(portName, newValue, oldValue)
↓
Per-pin callbacks fire for changed pins
↓
PartSimulationRegistry.onPinStateChange()
↓
wokwi web components update visually
Additionally per frame:
pollPwmRegisters() → reads OCR0A/B, OCR1AL/BL, OCR2A/B
↓
PinManager.updatePwm(pin, dutyCycle)
4. Input Components Flow
User presses a pushbutton on canvas
↓
Web component fires 'button-press' event
↓
DynamicComponent catches event
↓
PartSimulationRegistry.attachEvents() handler
↓
AVRSimulator.setPinState(arduinoPin, LOW)
↓
AVRIOPort.setPin(bitIndex) injects external pin state
↓
CPU reads pin value in next instruction
5. Wire Creation Flow
Click pin on component A → startWireCreation(endpoint)
↓
Mouse move → updateWireInProgress(x, y)
↓
WireInProgressRenderer shows dashed green L-shape preview
↓
Click pin on component B → finishWireCreation(endpoint)
↓
Wire created with midpoint control point
↓
WireLayer renders orthogonal SVG path
↓
Components subscribe to Arduino pins via wire lookup
Key Components
Frontend
1. Stores (Zustand)
useEditorStore — Code editor state
| Property | Type | Default |
|---|---|---|
code |
string |
Blink example sketch |
theme |
'vs-dark' | 'light' |
'vs-dark' |
fontSize |
number |
14 |
Methods: setCode(), setTheme(), setFontSize()
useSimulatorStore — Simulation + components + wires state
| Property | Type | Description |
|---|---|---|
simulator |
AVRSimulator | null |
CPU emulator instance |
pinManager |
PinManager |
Pin-to-component mapping |
running |
boolean |
Simulation active |
compiledHex |
string | null |
Compiled hex content |
components |
Component[] |
All electronic components |
wires |
Wire[] |
All wire connections |
selectedWireId |
string | null |
Currently selected wire |
wireInProgress |
WireInProgress | null |
Wire being created |
Methods (20+):
- Simulation:
initSimulator(),loadHex(),startSimulation(),stopSimulation(),resetSimulation(),setCompiledHex(),setRunning() - Components:
addComponent(),removeComponent(),updateComponent(),updateComponentState(),handleComponentEvent(),setComponents() - Wires:
addWire(),removeWire(),updateWire(),setSelectedWire(),setWires() - Wire creation:
startWireCreation(),updateWireInProgress(),finishWireCreation(),cancelWireCreation() - Wire positions:
updateWirePositions(componentId),recalculateAllWirePositions()
Notable behaviors:
removeComponent()cascades: removes all connected wiresupdateComponent()auto-recalculates wire positions when x/y changessetCompiledHex()auto-callsloadHex()
2. Simulation Engine
AVRSimulator — Real ATmega328p emulation
- CPU: 16MHz clock, 32KB program memory (16K words)
- Timers: Timer0 (
timer0Config), Timer1 (timer1Config), Timer2 (timer2Config) - Serial: USART (
usart0Config) at 16MHz - ADC: Analog-to-digital converter (
adcConfig) - GPIO: PORTB (pins 8-13), PORTC (A0-A5), PORTD (pins 0-7)
- Simulation loop: ~60fps via
requestAnimationFrame,267000 × speedcycles/frame - Speed control: 0.1x – 10x multiplier
- PWM polling: Reads OCR0A/B, OCR1AL/BL, OCR2A/B each frame
- API:
loadHex(),start(),stop(),reset(),step(),setSpeed(),setPinState(),getADC()
PinManager — Pin state tracking and listener dispatch
- Digital:
onPinChange(pin, callback),updatePort(portName, newValue, oldValue),getPinState(pin) - PWM:
onPwmChange(pin, callback),updatePwm(pin, dutyCycle),getPwmValue(pin) - Analog:
onAnalogChange(pin, callback),setAnalogVoltage(pin, voltage) - Utility:
getListenersCount(),clearAllListeners()
PartSimulationRegistry — Plugin system for component behaviors
- Interface:
onPinStateChange(pinName, state, element)for outputs,attachEvents(element, simulator, pinHelper) → cleanupfor inputs - 16 registered parts:
| Part | Type | Key Behavior |
|---|---|---|
led |
Output | Pin A state → 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 |
Input | Press/release → setPinState(pin, LOW/HIGH) |
pushbutton-6mm |
Input | Same as pushbutton |
slide-switch |
Input | Change event → pin state |
dip-switch-8 |
Input | 8 independent switches |
potentiometer |
Input | Value (0-1023) → ADC voltage injection |
slide-potentiometer |
Input | Same via SIG/OUT pins |
photoresistor-sensor |
Input/Output | Default 2.5V on AO, monitors DO for LED |
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 |
Output | Full HD44780 4-bit protocol (16×2) |
lcd2004 |
Output | Full HD44780 4-bit protocol (20×4) |
3. Component System
ComponentRegistry — Singleton from /components-metadata.json
- 48 components across 8 categories
- Auto-generated at build time by
scripts/generate-component-metadata.ts(TypeScript AST parser) - Methods:
getAllComponents(),getByCategory(),getById(),search(),getCategories()
| Category | Count | Components |
|---|---|---|
| Boards | 4 | Arduino Uno, Mega, Nano, etc. |
| Sensors | 6 | DHT22, HC-SR04, PIR, photoresistor, etc. |
| Displays | 3 | LCD 1602, LCD 2004, 7-segment |
| Input | 5 | Buttons, switches, potentiometers, joystick |
| Output | 5 | LEDs, RGB LED, LED bar graph, buzzer |
| Motors | 2 | Servo, stepper |
| Passive | 4 | Resistor, capacitor, etc. |
| Other | 19 | Various components |
DynamicComponent — Generic web component renderer
- Creates DOM elements with
document.createElement(metadata.tagName) - Syncs React properties to web component properties
- Extracts
pinInfofrom web component DOM (100ms polling, 2s timeout) - Integrates with PartSimulationRegistry for simulation events
- Resolves Arduino pin from wire connections
- Handles visual state: selection border, rotation, cursor, labels
ComponentPickerModal — Component search and selection UI
- Search bar with real-time filtering
- Category tabs from registry
- Live wokwi-element thumbnails (actual web components at reduced scale)
- Component count, pin count, description badges
4. UI Components
SimulatorCanvas (~472 lines) — Main simulation canvas
- Arduino Uno board at fixed position
- Dynamic component rendering via DynamicComponent
- Component drag-and-drop with viewport→canvas coordinate conversion
- Click vs. drag detection (time <300ms, distance <5px threshold)
- Single-click: opens ComponentPropertyDialog
- Double-click: opens PinSelector
- Wire creation via pin clicks (crosshair cursor during creation)
- Wire auto-recalculation on component move (retries at 100/300/500ms)
- PinOverlay on all components (hidden during simulation)
- Keyboard shortcuts: Delete/Backspace (remove), Escape (cancel wire)
- PinManager subscriptions for output component state updates
- Status indicator: Running/Stopped + component count
- "+ Add Component" button → opens ComponentPickerModal
WireRenderer (~400 lines) — Interactive wire display and editing
- Orthogonal SVG path rendering
- 10px invisible hitbox for easy clicking
- Segment-based editing: hover highlights, drag perpendicular to orientation
requestAnimationFramesmooth drag with local preview state- Grid snapping (20px) applied on mouseUp
- Invalid wire styling (red dashed)
- Endpoint markers at start/end
WireLayer — SVG overlay with automatic offset calculation for overlapping wires
WireInProgressRenderer — Dashed green preview during wire creation (L-shaped routing)
PinOverlay — 12px cyan circles at each pin position; green on hover with scale animation
ComponentPropertyDialog — Shows pin roles, Arduino pin assignment, Rotate and Delete buttons
PinSelector — Modal for assigning D0-D13 and A0-A5 to component pins
CodeEditor — Monaco Editor wrapper (C++, dark theme, minimap, word wrap)
EditorToolbar — Compile/Run/Stop/Reset buttons with status messages
ExamplesGallery — Filterable card grid (category + difficulty filters)
5. Wire Utilities
| Utility | Purpose |
|---|---|
wirePathGenerator.ts |
L-shape and multi-segment orthogonal SVG path generation |
wireSegments.ts |
Segment computation, hit testing (8px tolerance), drag updates |
wireColors.ts |
8 signal-type colors + determineSignalType() |
wireOffsetCalculator.ts |
Parallel overlap detection (5px tolerance), symmetric offset (6px spacing) |
pinPositionCalculator.ts |
Pin coordinate conversion (element → canvas space), closest pin snap (20px) |
hexParser.ts |
Intel HEX parser with checksum verification |
captureCanvasPreview.ts |
SVG foreignObject preview image generation |
Backend
FastAPI app (port 8001) with CORS for ports 5173-5175
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | API info |
/health |
GET | Health check |
/api/compile/ |
POST | Compile Arduino code → hex content |
/api/compile/boards |
GET | List available boards |
ArduinoCLIService:
- Auto-installs
arduino:avrcore if missing - Creates temp sketch directory, runs
arduino-cli compileviaasyncio.to_thread(subprocess.run) - Reads
build/sketch.ino.hexoutput - Board listing via
arduino-cli board listall
Pages & Routing
| Route | Page | Layout |
|---|---|---|
/ |
EditorPage | Header + split panels: Editor (left) + Simulator (right) |
/examples |
ExamplesPage | Examples gallery with "Back to Editor" link |
Example Projects (8)
| ID | Title | Category | Difficulty |
|---|---|---|---|
blink-led |
Blink LED | basics | beginner |
traffic-light |
Traffic Light | basics | beginner |
button-led |
Button Control | basics | beginner |
fade-led |
Fade LED | basics | beginner |
serial-hello |
Serial Hello World | communication | beginner |
rgb-led |
RGB LED Colors | basics | intermediate |
simon-says |
Simon Says Game | games | advanced |
lcd-hello |
LCD 20x4 Display | displays | intermediate |
Each example includes full Arduino sketch, component definitions, and wire connections.
Wokwi Libraries (Local Clones)
| Library | Location | Purpose |
|---|---|---|
| wokwi-elements | wokwi-libs/wokwi-elements/ |
48+ Lit Web Components |
| avr8js | wokwi-libs/avr8js/ |
AVR8 ATmega328p emulator |
| rp2040js | wokwi-libs/rp2040js/ |
RP2040 emulator (future) |
| wokwi-features | wokwi-libs/wokwi-features/ |
Features documentation |
Vite Integration
Alias Configuration
// vite.config.ts
resolve: {
alias: {
'avr8js': path.resolve(__dirname, '../wokwi-libs/avr8js/dist/esm'),
'@wokwi/elements': path.resolve(__dirname, '../wokwi-libs/wokwi-elements/dist/esm'),
},
},
optimizeDeps: {
include: ['avr8js', '@wokwi/elements'],
}
This allows:
- Import from local repos as if they were npm packages
- Easy updates with
git pull - Modify source code if needed for debugging
Technology Stack
Frontend
| Technology | Version | Purpose |
|---|---|---|
| React | 19.2 | UI framework |
| Vite | 7.3 | Build tool & dev server |
| TypeScript | 5.9 | Static typing |
| Monaco Editor | 4.7 | Code editor (VS Code engine) |
| Zustand | 5.0 | State management |
| React Router | 7.13 | Client-side routing |
| Axios | 1.13 | HTTP client |
| wokwi-elements | local | 48+ electronic web components |
| avr8js | local | AVR8 CPU emulator |
Backend
| Technology | Version | Purpose |
|---|---|---|
| Python | 3.12+ | Runtime |
| FastAPI | 0.115 | Web framework |
| Uvicorn | 0.32 | ASGI server |
External Tools
| Tool | Purpose |
|---|---|
| arduino-cli | Arduino compiler (subprocess) |
| Git | Version control for Wokwi libs |
Architecture Advantages
Real Emulation
- True AVR8 CPU execution, not simulation mockups
- Same avr8js engine used by Wokwi.com
- Accurate timing with configurable speed
Plugin-Based Component Behaviors
- PartSimulationRegistry decouples simulation logic from rendering
- Easy to add new component behaviors
- Supports both input (event-driven) and output (pin-state-driven) components
Automatic Component Discovery
- Build-time TypeScript AST parser extracts metadata from wokwi-elements source
- No manual component registration needed
- New wokwi-elements components appear automatically after rebuild
Separation of Concerns
- Frontend: UI, visualization, simulation engine
- Backend: Compilation via arduino-cli
- Wokwi Libs: Emulation and components (maintained by Wokwi community)
Wokwi Compatibility
- Official repositories = same functionality as Wokwi.com
- Automatic updates with
git pull - New components available immediately after rebuild
Local Development
- No internet required after initial setup
- Local compilation with arduino-cli
- All simulation runs in the browser
Planned Improvements
- Serial Monitor — UI for USART output display
- Project Persistence — SQLite database for save/load
- Undo/Redo — Edit history for code and circuit changes
- Multi-board Support — Runtime board switching (Mega, Nano, ESP32)
- Wire Validation — Electrical validation and error highlighting
- Export/Import — Share projects as files