feat: enhance wire offset calculation to maintain pin connections with L-shaped stubs

pull/10/head
David Montero Crespo 2026-03-05 21:33:02 -03:00
parent 96131e7451
commit 336f0460cd
6 changed files with 174 additions and 80 deletions

160
README.md
View File

@ -14,36 +14,72 @@ Every star counts and helps make this project better! You can also support the p
## Screenshots
![Arduino Emulator - Editor and Simulator](doc/img1.png)
![Raspberry Pi Pico ADC simulation with Serial Monitor](doc/img1.png)
Arduino emulator with Monaco code editor and visual simulator with wokwi-elements
Raspberry Pi Pico simulation — ADC read test with two potentiometers, Serial Monitor showing live output, and compilation console at the bottom.
![Arduino Emulator - Component Properties and Wire Editing](doc/img2.png)
![ILI9341 TFT display simulation on Arduino Uno](doc/img2.png)
Interactive component properties dialog and segment-based wire editing
Arduino Uno driving an ILI9341 240×320 TFT display via SPI — rendering a real-time graphics demo using Adafruit_GFX + Adafruit_ILI9341.
![Library Manager with full library list](doc/img3.png)
Library Manager loads the full Arduino library index on open — browse and install libraries without typing first.
![Component Picker with 48 components](doc/img4.png)
Component Picker showing 48 available components with visual previews, search, and category filters (Boards, Displays, Input, Motors, Output, Passive, Sensors).
## Features
### Code Editing
- **Monaco Editor** — Full C++ editor with syntax highlighting, autocomplete, minimap, and dark theme
- **Arduino compilation** via `arduino-cli` backend — compile sketches to `.hex` files
- **Arduino compilation** via `arduino-cli` backend — compile sketches to `.hex` / `.uf2` files
- **Compile / Run / Stop / Reset** toolbar buttons with status messages
- **Compilation console** — resizable output panel at the bottom of the editor showing full compiler output, warnings, and errors
### AVR8 Simulation (avr8js)
### Multi-Board Support
- **Arduino Uno** (ATmega328p) — full AVR8 emulation via avr8js
- **Raspberry Pi Pico** (RP2040) — full RP2040 emulation via rp2040js, compiled with arduino-pico core
- Board selector in the toolbar — switch boards without restarting
### AVR8 Simulation (Arduino Uno)
- **Real ATmega328p emulation** at 16 MHz using avr8js
- **Intel HEX parser** with checksum verification
- **Full GPIO support** — PORTB (pins 8-13), PORTC (A0-A5), PORTD (pins 0-7)
- **Timer0 peripheral** support
- **USART (Serial)** support
- **Timer0/Timer1/Timer2** peripheral support (enables `millis()`, `delay()`, PWM via `analogWrite()`)
- **USART (Serial)** — full transmit and receive support
- **ADC**`analogRead()` on pins A0-A5, voltage injection from UI components
- **SPI** — hardware SPI peripheral (enables ILI9341, SD card, etc.)
- **I2C (TWI)** — hardware I2C with virtual device bus (DS1307, temp sensor, EEPROM)
- **~60 FPS simulation loop** with `requestAnimationFrame` (~267k cycles/frame)
- **Speed control** — adjustable from 0.1x to 10x
- **Single-step debugging** API
- **External pin state injection** for input components (buttons, potentiometers)
- **PWM monitoring** — reads OCR registers each frame to drive PWM-capable components
### RP2040 Simulation (Raspberry Pi Pico)
- **Real RP2040 emulation** via rp2040js at 133 MHz
- **UART0** serial output captured and displayed in Serial Monitor
- **ADC** — 12-bit, 3.3V reference on GPIO 26-29 (A0-A3)
- **I2C** bus with virtual device support
- Automatic `#define Serial Serial1` injection at compile time to route serial output to emulated UART
### Serial Monitor
- **Live serial output** — displays characters as the sketch sends them via `Serial.print()`
- **Baud rate indicator** — automatically detects the speed set by `Serial.begin()` reading hardware registers in real time — no manual configuration needed
- **Send data** to the Arduino RX pin from the UI
- **Line ending selector** — None, Newline, Carriage Return, or Both
- **Autoscroll** with toggle
- **Resizable panel** — drag the handle to adjust height
### ILI9341 TFT Display Simulation
- **Full SPI command decoding** — CASET, PASET, RAMWR, SWRESET, DISPON, MADCTL, and more
- **RGB-565 pixel rendering** directly to an HTML Canvas element in real time
- **240×320 resolution** with full framebuffer support
- Compatible with Adafruit_GFX + Adafruit_ILI9341 libraries
### Component System (48+ Components)
- **48 electronic components** auto-discovered from wokwi-elements source code
- **Component picker modal** with search bar, category filtering, and live wokwi-element previews as thumbnails
- **9 component categories**: Boards (4), Sensors (6), Displays (3), Input (5), Output (5), Motors (2), Passive (4), Other (19)
- **Component picker modal** with search bar, category filtering (Boards, Displays, Input, Motors, Output, Passive, Sensors), and live wokwi-element previews as thumbnails
- **Dynamic component rendering** from build-time metadata (TypeScript AST parser extracts `@customElement` tags, `@property` decorators, and pin counts)
- **Drag-and-drop repositioning** on the simulation canvas
- **Component rotation** in 90° increments
@ -54,27 +90,38 @@ Interactive component properties dialog and segment-based wire editing
### Part Simulation Behaviors
- **LED** — pin state drives LED on/off
- **RGB LED** — digital HIGH/LOW mapped to individual R/G/B channels
- **Pushbutton** — press/release events inject active-LOW pin state into simulation
- **Potentiometer** — reads element value (0-1023), converts to voltage, injects into ADC channel
- **LCD 1602 & LCD 2004** — Full HD44780 controller emulation:
- 4-bit mode protocol (high nibble first, then low nibble)
- DDRAM with proper line address mapping
- Commands: Clear Display, Return Home, Entry Mode Set, Display On/Off, Cursor/Display Shift, Function Set
- Initialization sequence handling
- Enable pin falling-edge detection for data latching
- **RGB LED** — digital HIGH/LOW and PWM (`analogWrite`) mapped to R/G/B channels
- **Pushbutton / 6mm pushbutton** — press/release events inject active-LOW pin state
- **Slide switch** — toggle between HIGH and LOW
- **DIP Switch 8** — 8 independent toggles, each driving its own pin
- **Potentiometer** — reads slider value (0-1023), converts to voltage (5V for AVR, 3.3V for RP2040), injects into ADC
- **Slide potentiometer** — same as rotary, with configurable min/max range
- **Analog joystick** — two ADC axes (VRX/VRY) + button press
- **Photoresistor sensor** — injects mid-range voltage on AO pin; reacts to element input events
- **Servo** — reads Timer1 OCR1A/ICR1 registers to calculate pulse width and maps to 0-180° angle
- **Buzzer** — Web Audio API tone generation, frequency derived from Timer2 OCR2A/TCCR2B registers
- **LED Bar Graph** — 10 individual LEDs driven by pins A1-A10
- **7-Segment Display** — segments A-G + DP driven by individual pins
- **LCD 1602 & LCD 2004** — full HD44780 controller emulation in 4-bit mode (RS, E, D4-D7 pins)
- **ILI9341 TFT** — full SPI display simulation (see above)
### Wire System
- **Wire creation** — click a pin to start, click another pin to connect
- **Real-time preview** — dashed green wire with L-shaped orthogonal routing while creating
- **Orthogonal wire rendering** — no diagonal paths
- **Segment-based wire editing** — hover to highlight, drag segments perpendicular to their orientation
- **Smooth dragging** with `requestAnimationFrame`
- **8 signal-type wire colors**: Red (VCC), Black (GND), Blue (Analog), Green (Digital), Purple (PWM), Gold (I2C), Orange (SPI), Cyan (USART)
- **Automatic overlap offset** — parallel wires are offset symmetrically (6px spacing)
- **Auto-update positions** — wire endpoints recalculate when components move
- **Grid snapping** (20px grid)
### Library Manager
- **Loads full library index on open** — no need to type first; shows spinner while fetching
- **Live search with debounce** — filter while typing (400ms delay)
- **Install libraries** directly from the UI via arduino-cli
- **Installed tab** — see all installed libraries with versions
- **Cross-reference** — installed libraries show "INSTALLED" badge in search results
### Example Projects
- **8 built-in example projects** with full code, components, and wire definitions:
@ -92,10 +139,16 @@ Interactive component properties dialog and segment-based wire editing
- **Examples gallery** with category and difficulty filters
- **One-click loading** — loads code, components, and wires into the editor and simulator
### UI / Layout
- **Resizable panels** — drag the vertical divider between editor and simulator
- **Resizable bottom panels** — Serial Monitor and compilation console share the same draggable handle; both start at the same height (200px)
- **Compilation console at the bottom** — output appears below the code editor, not between the toolbar and the code
- **Serial Monitor** opens automatically when simulation starts
### Wokwi Libraries (Local Clones)
- **wokwi-elements** — 48+ electronic web components (Lit-based Web Components)
- **avr8js** — AVR8 CPU emulator
- **rp2040js** — RP2040 emulator (cloned, for future use)
- **rp2040js** — RP2040 emulator
- **Build-time metadata generation** — TypeScript AST parser reads wokwi-elements source to generate component metadata automatically
## Prerequisites
@ -131,6 +184,11 @@ arduino-cli core update-index
arduino-cli core install arduino:avr
```
**For Raspberry Pi Pico support:**
```bash
arduino-cli core install rp2040:rp2040
```
## Installation
### Option A: Docker (Recommended)
@ -214,14 +272,15 @@ The frontend will be available at:
## Usage
1. Open http://localhost:5173 in your browser
2. Write Arduino code in the editor (a Blink example is loaded by default)
3. Click **Compile** to compile the code via the backend
4. Click **Run** to start real AVR8 CPU simulation
5. Watch LEDs, LCDs, and other components react in real time
6. Click on components to view properties or assign pin mappings
7. Double-click components to open the pin selector
2. Select a board (Arduino Uno or Raspberry Pi Pico) from the toolbar
3. Write Arduino code in the editor (a Blink example is loaded by default)
4. Click **Compile** to compile the code via the backend
5. Click **Run** to start simulation — the Serial Monitor opens automatically
6. Watch LEDs, LCDs, TFT displays, and other components react in real time
7. Click components to view properties or assign pin mappings
8. Click pins to create wires connecting components
9. Browse **Examples** to load pre-built projects (Blink, Traffic Light, Simon Says, LCD, etc.)
9. Use the **Library Manager** to install Arduino libraries
10. Browse **Examples** to load pre-built projects
## Project Structure
@ -232,17 +291,16 @@ openwokwi/
│ │ ├── components/
│ │ │ ├── ComponentPickerModal.tsx # Component search & picker
│ │ │ ├── DynamicComponent.tsx # Generic wokwi component renderer
│ │ │ ├── components-wokwi/ # Legacy React wrappers
│ │ │ ├── editor/ # Monaco Editor + toolbar
│ │ │ ├── examples/ # Examples gallery
│ │ │ └── simulator/ # Canvas, wires, pins, dialogs
│ │ │ ├── editor/ # Monaco Editor + toolbar + compilation console
│ │ │ └── simulator/ # Canvas, wires, pins, Serial Monitor, dialogs
│ │ ├── simulation/
│ │ │ ├── AVRSimulator.ts # AVR8 CPU emulator wrapper
│ │ │ ├── PinManager.ts # Pin-to-component mapping
│ │ │ └── parts/ # Part behaviors (LED, LCD, etc.)
│ │ │ ├── RP2040Simulator.ts # RP2040 emulator wrapper
│ │ │ ├── PinManager.ts # Pin-to-component mapping + PWM
│ │ │ └── parts/ # Part behaviors (LED, LCD, ILI9341, servo, buzzer...)
│ │ ├── store/ # Zustand state management
│ │ ├── services/ # API clients & ComponentRegistry
│ │ ├── types/ # TypeScript types (wires, components)
│ │ ├── types/ # TypeScript types (wires, components, metadata)
│ │ ├── utils/ # Hex parser, wire routing, pin calc
│ │ └── pages/ # EditorPage, ExamplesPage
│ └── public/
@ -251,14 +309,14 @@ openwokwi/
├── backend/ # FastAPI + Python
│ └── app/
│ ├── main.py # Entry point, CORS config
│ ├── api/routes/compile.py # POST /api/compile, GET /api/compile/boards
│ ├── api/routes/compile.py # POST /api/compile
│ ├── api/routes/libraries.py # Library search, install, list
│ └── services/arduino_cli.py # arduino-cli subprocess wrapper
├── wokwi-libs/ # Cloned Wokwi repositories
│ ├── wokwi-elements/ # 48+ Web Components (Lit)
│ ├── avr8js/ # AVR8 CPU Emulator
│ ├── rp2040js/ # RP2040 Emulator (future)
│ └── wokwi-features/ # Features and documentation
│ └── rp2040js/ # RP2040 Emulator
├── scripts/
│ └── generate-component-metadata.ts # AST parser for component discovery
@ -266,8 +324,10 @@ openwokwi/
├── doc/
│ ├── ARCHITECTURE.md # Detailed architecture documentation
│ ├── WOKWI_LIBS.md # Wokwi integration documentation
│ ├── SETUP_COMPLETE.md # Project status overview
│ └── examples/ # Example screenshots
│ ├── img1.png # Screenshot: RP2040 + Serial Monitor
│ ├── img2.png # Screenshot: ILI9341 TFT simulation
│ ├── img3.png # Screenshot: Library Manager
│ └── img4.png # Screenshot: Component Picker
├── CLAUDE.md # AI assistant guidance
└── update-wokwi-libs.bat # Update local Wokwi libraries
@ -282,7 +342,6 @@ openwokwi/
- **Monaco Editor** — Code editor (VS Code engine)
- **Zustand** 5 — State management
- **React Router** 7 — Client-side routing
- **Axios** — HTTP client
### Backend
- **FastAPI** — Python web framework
@ -291,17 +350,16 @@ openwokwi/
### Simulation & Components
- **avr8js** — Real AVR8 ATmega328p emulator (local clone)
- **rp2040js** — RP2040 emulator (local clone)
- **wokwi-elements** — 48+ electronic web components built with Lit (local clone)
- **rp2040js** — RP2040 emulator (local clone, for future use)
## Planned Features
- **Serial Monitor** — UI for reading USART output from the simulation
- **Project Persistence** — Save/load projects with SQLite
- **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
- **More boards** — ESP32, Arduino Mega, Arduino Nano
## Update Wokwi Libraries
@ -337,9 +395,9 @@ See [WOKWI_LIBS.md](doc/WOKWI_LIBS.md) for more details about Wokwi integration.
- Check CORS logs in browser console
### Compilation errors
- Check backend console for arduino-cli logs
- Check the compilation console output at the bottom of the editor
- Make sure Arduino code is valid
- Verify you have the `arduino:avr` core installed
- Verify you have the correct core installed (`arduino:avr` for Uno, `rp2040:rp2040` for Pico)
### LED doesn't blink
- Check port listeners are firing (browser console logs)
@ -349,6 +407,10 @@ See [WOKWI_LIBS.md](doc/WOKWI_LIBS.md) for more details about Wokwi integration.
- Ensure `avrInstruction()` is being called in the execution loop
- Check hex file was loaded correctly
### Serial Monitor shows nothing
- Make sure your sketch calls `Serial.begin()` before `Serial.print()`
- Check the baud rate indicator appears in the Serial Monitor header after the simulation starts
## Contributing
This is an open-source project. Suggestions, bug reports, and pull requests are welcome!
@ -362,6 +424,6 @@ MIT
- [Wokwi](https://wokwi.com) — Project inspiration
- [avr8js](https://github.com/wokwi/avr8js) — AVR8 emulator
- [wokwi-elements](https://github.com/wokwi/wokwi-elements) — Electronic web components
- [rp2040js](https://github.com/wokwi/rp2040js) — RP2040 emulator
- [arduino-cli](https://github.com/arduino/arduino-cli) — Arduino compiler
- [Monaco Editor](https://microsoft.github.io/monaco-editor/) — Code editor

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 136 KiB

BIN
doc/img3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
doc/img4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@ -218,44 +218,76 @@ export function calculateWireOffsets(wires: Wire[]): Map<string, number> {
}
/**
* Apply offset to wire points (perpendicular to wire direction)
* Apply offset to wire points (perpendicular to wire direction).
*
* Instead of moving the endpoints (which would visually disconnect the wire from its
* pins), we keep the true pin positions fixed and insert short stub segments that
* travel from each pin to the offset path, forming an L-shaped attachment at both ends.
*
* Example (horizontal wire, offset = +6):
* Before: pin pin
* After: pin (stub)
*
* (stub) pin
*/
export function applyOffsetToWire(wire: Wire, offset: number): Wire {
if (offset === 0) return wire;
// Clone the wire
const offsetWire: Wire = {
...wire,
start: { ...wire.start },
end: { ...wire.end },
controlPoints: wire.controlPoints ? [...wire.controlPoints] : [],
};
// Determine primary direction from the first segment of the path
const firstControlOrEnd =
wire.controlPoints && wire.controlPoints.length > 0
? wire.controlPoints[0]
: wire.end;
// Apply offset to start and end points
// Determine primary direction (first segment)
const firstPoint = offsetWire.start;
const secondPoint = offsetWire.controlPoints && offsetWire.controlPoints.length > 0
? offsetWire.controlPoints[0]
: offsetWire.end;
const isHorizontalFirst =
Math.abs(firstControlOrEnd.x - wire.start.x) >=
Math.abs(firstControlOrEnd.y - wire.start.y);
const isVertical = Math.abs(secondPoint.x - firstPoint.x) < Math.abs(secondPoint.y - firstPoint.y);
// True pin positions (never moved)
const pinStart = { x: wire.start.x, y: wire.start.y };
const pinEnd = { x: wire.end.x, y: wire.end.y };
// Apply offset perpendicular to direction
if (isVertical) {
// Vertical wire: offset in X
offsetWire.start.x += offset;
offsetWire.end.x += offset;
offsetWire.controlPoints?.forEach(point => {
point.x += offset;
});
} else {
// Horizontal wire: offset in Y
offsetWire.start.y += offset;
offsetWire.end.y += offset;
offsetWire.controlPoints?.forEach(point => {
point.y += offset;
});
// Offset intermediate points perpendicular to the primary direction
const shiftedControlPoints = (wire.controlPoints || []).map(cp => ({
...cp,
x: isHorizontalFirst ? cp.x : cp.x + offset,
y: isHorizontalFirst ? cp.y + offset : cp.y,
}));
// Compute where the offset path actually starts/ends
// (the point on the parallel track immediately after the pin stub)
const offsetStart = isHorizontalFirst
? { x: pinStart.x, y: pinStart.y + offset }
: { x: pinStart.x + offset, y: pinStart.y };
const offsetEnd = isHorizontalFirst
? { x: pinEnd.x, y: pinEnd.y + offset }
: { x: pinEnd.x + offset, y: pinEnd.y };
// Build new control points:
// stub from pinStart → offsetStart, then the shifted intermediates, then stub from offsetEnd → pinEnd
// We only need to add extra stubs when they are non-zero length.
const newControlPoints: typeof wire.controlPoints = [];
// Leading stub end-point (where the offset path begins)
if (offsetStart.x !== pinStart.x || offsetStart.y !== pinStart.y) {
newControlPoints.push({ id: `${wire.id}-stub-s`, ...offsetStart });
}
return offsetWire;
// Shifted original control points
for (const cp of shiftedControlPoints) {
newControlPoints.push(cp);
}
// Trailing stub start-point (where the offset path rejoins the pin)
if (offsetEnd.x !== pinEnd.x || offsetEnd.y !== pinEnd.y) {
newControlPoints.push({ id: `${wire.id}-stub-e`, ...offsetEnd });
}
return {
...wire,
start: { ...wire.start, x: pinStart.x, y: pinStart.y },
end: { ...wire.end, x: pinEnd.x, y: pinEnd.y },
controlPoints: newControlPoints,
};
}