feat: add support for RP2040 board, including simulator and compilation enhancements
parent
f139187382
commit
7944ce2de3
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
**/node_modules/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Build outputs (will be built inside Docker)
|
||||||
|
frontend/dist/
|
||||||
|
frontend/.vite/
|
||||||
|
wokwi-libs/avr8js/dist/
|
||||||
|
wokwi-libs/wokwi-elements/dist/
|
||||||
|
|
||||||
|
# IDE and OS
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Docker files (avoid recursion)
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile*
|
||||||
|
|
||||||
|
# Documentation (not needed in image)
|
||||||
|
*.md
|
||||||
|
doc/
|
||||||
|
LICENSE
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
github: [davidmonterocrespo24]
|
github: davidmonterocrespo24
|
||||||
|
custom:
|
||||||
|
- https://paypal.me/dmonterocrepoclub
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,4 @@ wokwi-libs/*/.cache/
|
||||||
!wokwi-libs/wokwi-features/
|
!wokwi-libs/wokwi-features/
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
.history/*
|
.history/*
|
||||||
|
.daveagent/*
|
||||||
34
README.md
34
README.md
|
|
@ -7,8 +7,10 @@ A fully local, open-source Arduino emulator inspired by [Wokwi](https://wokwi.co
|
||||||
If you find this project helpful, please consider giving it a star! Your support helps the project grow and motivates continued development.
|
If you find this project helpful, please consider giving it a star! Your support helps the project grow and motivates continued development.
|
||||||
|
|
||||||
[](https://github.com/davidmonterocrespo24/openwokwi/stargazers)
|
[](https://github.com/davidmonterocrespo24/openwokwi/stargazers)
|
||||||
|
[](https://github.com/sponsors/davidmonterocrespo24)
|
||||||
|
[](https://paypal.me/dmonterocrepoclub)
|
||||||
|
|
||||||
Every star counts and helps make this project better!
|
Every star counts and helps make this project better! You can also support the project financially through GitHub Sponsors or PayPal -- any contribution helps keep the development going.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
|
@ -131,13 +133,37 @@ arduino-cli core install arduino:avr
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### 1. Clone the repository
|
### Option A: Docker (Recommended)
|
||||||
|
|
||||||
|
The fastest way to get started. Requires only [Docker](https://docs.docker.com/get-docker/) and Docker Compose.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/davidmonterocrespo24/openwokwi.git
|
||||||
|
cd openwokwi
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Once running:
|
||||||
|
- **App**: http://localhost:3000
|
||||||
|
- **API**: http://localhost:8001
|
||||||
|
- **API Docs**: http://localhost:8001/docs
|
||||||
|
|
||||||
|
The Docker setup automatically installs `arduino-cli`, the AVR core, builds the wokwi-libs, and serves everything -- no other prerequisites needed.
|
||||||
|
|
||||||
|
To stop:
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Manual Setup
|
||||||
|
|
||||||
|
#### 1. Clone the repository
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/davidmonterocrespo24/openwokwi.git
|
git clone https://github.com/davidmonterocrespo24/openwokwi.git
|
||||||
cd openwokwi
|
cd openwokwi
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Setup Backend
|
#### 2. Setup Backend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
|
|
@ -152,7 +178,7 @@ venv\Scripts\activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Setup Frontend
|
#### 3. Setup Frontend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
# Install system dependencies and arduino-cli
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
&& curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh \
|
||||||
|
&& mv bin/arduino-cli /usr/local/bin/ \
|
||||||
|
&& rm -rf bin \
|
||||||
|
&& apt-get purge -y curl \
|
||||||
|
&& apt-get autoremove -y \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Initialize arduino-cli and install AVR core
|
||||||
|
RUN arduino-cli core update-index \
|
||||||
|
&& arduino-cli core install arduino:avr
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8001
|
||||||
|
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||||
|
|
@ -14,6 +14,8 @@ class CompileRequest(BaseModel):
|
||||||
class CompileResponse(BaseModel):
|
class CompileResponse(BaseModel):
|
||||||
success: bool
|
success: bool
|
||||||
hex_content: str | None = None
|
hex_content: str | None = None
|
||||||
|
binary_content: str | None = None # base64-encoded .bin for RP2040
|
||||||
|
binary_type: str | None = None # 'bin' or 'uf2'
|
||||||
stdout: str
|
stdout: str
|
||||||
stderr: str
|
stderr: str
|
||||||
error: str | None = None
|
error: str | None = None
|
||||||
|
|
@ -29,6 +31,8 @@ async def compile_sketch(request: CompileRequest):
|
||||||
return CompileResponse(
|
return CompileResponse(
|
||||||
success=result["success"],
|
success=result["success"],
|
||||||
hex_content=result.get("hex_content"),
|
hex_content=result.get("hex_content"),
|
||||||
|
binary_content=result.get("binary_content"),
|
||||||
|
binary_type=result.get("binary_type"),
|
||||||
stdout=result.get("stdout", ""),
|
stdout=result.get("stdout", ""),
|
||||||
stderr=result.get("stderr", ""),
|
stderr=result.get("stderr", ""),
|
||||||
error=result.get("error")
|
error=result.get("error")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import base64
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,10 +12,10 @@ class ArduinoCLIService:
|
||||||
|
|
||||||
def _ensure_core_installed(self):
|
def _ensure_core_installed(self):
|
||||||
"""
|
"""
|
||||||
Ensure Arduino AVR core is installed
|
Ensure Arduino AVR core is installed.
|
||||||
|
RP2040 core is optional (installed separately by the user).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Check if core is installed
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[self.cli_path, "core", "list"],
|
[self.cli_path, "core", "list"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
|
|
@ -23,7 +24,6 @@ class ArduinoCLIService:
|
||||||
|
|
||||||
if "arduino:avr" not in result.stdout:
|
if "arduino:avr" not in result.stdout:
|
||||||
print("Arduino AVR core not installed. Installing...")
|
print("Arduino AVR core not installed. Installing...")
|
||||||
# Install AVR core
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[self.cli_path, "core", "install", "arduino:avr"],
|
[self.cli_path, "core", "install", "arduino:avr"],
|
||||||
check=True
|
check=True
|
||||||
|
|
@ -33,6 +33,10 @@ class ArduinoCLIService:
|
||||||
print(f"Warning: Could not verify arduino:avr core: {e}")
|
print(f"Warning: Could not verify arduino:avr core: {e}")
|
||||||
print("Please ensure arduino-cli is installed and in PATH")
|
print("Please ensure arduino-cli is installed and in PATH")
|
||||||
|
|
||||||
|
def _is_rp2040_board(self, fqbn: str) -> bool:
|
||||||
|
"""Return True if the FQBN targets an RP2040/RP2350 board."""
|
||||||
|
return any(p in fqbn for p in ("rp2040", "rp2350", "mbed_rp2040", "mbed_rp2350"))
|
||||||
|
|
||||||
async def compile(self, code: str, board_fqbn: str = "arduino:avr:uno") -> dict:
|
async def compile(self, code: str, board_fqbn: str = "arduino:avr:uno") -> dict:
|
||||||
"""
|
"""
|
||||||
Compile Arduino sketch using arduino-cli
|
Compile Arduino sketch using arduino-cli
|
||||||
|
|
@ -85,7 +89,40 @@ class ArduinoCLIService:
|
||||||
print(f"Stderr: {result.stderr}")
|
print(f"Stderr: {result.stderr}")
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
# Read compiled hex file
|
print(f"Files in build dir: {list(build_dir.iterdir())}")
|
||||||
|
|
||||||
|
if self._is_rp2040_board(board_fqbn):
|
||||||
|
# RP2040 outputs a .bin file (and optionally .uf2)
|
||||||
|
# Try .bin first (raw binary, simplest to load into emulator)
|
||||||
|
bin_file = build_dir / "sketch.ino.bin"
|
||||||
|
uf2_file = build_dir / "sketch.ino.uf2"
|
||||||
|
|
||||||
|
target_file = bin_file if bin_file.exists() else (uf2_file if uf2_file.exists() else None)
|
||||||
|
|
||||||
|
if target_file:
|
||||||
|
raw_bytes = target_file.read_bytes()
|
||||||
|
binary_b64 = base64.b64encode(raw_bytes).decode('ascii')
|
||||||
|
print(f"[RP2040] Binary file: {target_file.name}, size: {len(raw_bytes)} bytes")
|
||||||
|
print("=== RP2040 Compilation successful ===\n")
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"hex_content": None,
|
||||||
|
"binary_content": binary_b64,
|
||||||
|
"binary_type": "bin" if target_file == bin_file else "uf2",
|
||||||
|
"stdout": result.stdout,
|
||||||
|
"stderr": result.stderr
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print(f"[RP2040] Binary file not found. Files: {list(build_dir.iterdir())}")
|
||||||
|
print("=== RP2040 Compilation failed: binary not found ===\n")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "RP2040 binary (.bin/.uf2) not found after compilation",
|
||||||
|
"stdout": result.stdout,
|
||||||
|
"stderr": result.stderr
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# AVR outputs a .hex file (Intel HEX format)
|
||||||
hex_file = build_dir / "sketch.ino.hex"
|
hex_file = build_dir / "sketch.ino.hex"
|
||||||
print(f"Looking for hex file at: {hex_file}")
|
print(f"Looking for hex file at: {hex_file}")
|
||||||
print(f"Hex file exists: {hex_file.exists()}")
|
print(f"Hex file exists: {hex_file.exists()}")
|
||||||
|
|
@ -93,15 +130,15 @@ class ArduinoCLIService:
|
||||||
if hex_file.exists():
|
if hex_file.exists():
|
||||||
hex_content = hex_file.read_text()
|
hex_content = hex_file.read_text()
|
||||||
print(f"Hex file size: {len(hex_content)} bytes")
|
print(f"Hex file size: {len(hex_content)} bytes")
|
||||||
print("=== Compilation successful ===\n")
|
print("=== AVR Compilation successful ===\n")
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"hex_content": hex_content,
|
"hex_content": hex_content,
|
||||||
|
"binary_content": None,
|
||||||
"stdout": result.stdout,
|
"stdout": result.stdout,
|
||||||
"stderr": result.stderr
|
"stderr": result.stderr
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# List files in build directory
|
|
||||||
print(f"Files in build dir: {list(build_dir.iterdir())}")
|
print(f"Files in build dir: {list(build_dir.iterdir())}")
|
||||||
print("=== Compilation failed: hex file not found ===\n")
|
print("=== Compilation failed: hex file not found ===\n")
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8001:8001"
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8001/health')"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: frontend/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
VITE_API_BASE=/api
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
# ---- Stage 1: Build wokwi-libs and frontend ----
|
||||||
|
FROM node:20-slim AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy root package.json (needed for metadata generation via tsx)
|
||||||
|
COPY package.json .
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy metadata generation script
|
||||||
|
COPY scripts/ scripts/
|
||||||
|
|
||||||
|
# Copy and build avr8js
|
||||||
|
COPY wokwi-libs/avr8js/ wokwi-libs/avr8js/
|
||||||
|
WORKDIR /app/wokwi-libs/avr8js
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
# Copy and build wokwi-elements
|
||||||
|
COPY wokwi-libs/wokwi-elements/ wokwi-libs/wokwi-elements/
|
||||||
|
WORKDIR /app/wokwi-libs/wokwi-elements
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
# Copy frontend source and install dependencies
|
||||||
|
WORKDIR /app
|
||||||
|
COPY frontend/ frontend/
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Generate component metadata and build frontend for production
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ---- Stage 2: Serve with nginx ----
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy custom nginx config
|
||||||
|
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Copy built frontend assets
|
||||||
|
COPY --from=builder /app/frontend/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Frontend SPA — serve index.html for all non-file routes
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy API requests to backend
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend:8001/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import '@wokwi/elements';
|
||||||
|
import { useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface NanoRP2040Props {
|
||||||
|
id?: string;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
ledBuiltIn?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NanoRP2040 = ({
|
||||||
|
id = 'nano-rp2040',
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
ledBuiltIn = false,
|
||||||
|
}: NanoRP2040Props) => {
|
||||||
|
const ref = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
(ref.current as any).ledBuiltIn = ledBuiltIn;
|
||||||
|
}
|
||||||
|
}, [ledBuiltIn]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<wokwi-nano-rp2040-connect
|
||||||
|
id={id}
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${x}px`,
|
||||||
|
top: `${y}px`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useEditorStore } from '../../store/useEditorStore';
|
import { useEditorStore } from '../../store/useEditorStore';
|
||||||
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
import { useSimulatorStore, BOARD_FQBN } from '../../store/useSimulatorStore';
|
||||||
import { compileCode } from '../../services/compilation';
|
import { compileCode } from '../../services/compilation';
|
||||||
import './EditorToolbar.css';
|
import './EditorToolbar.css';
|
||||||
|
|
||||||
export const EditorToolbar = () => {
|
export const EditorToolbar = () => {
|
||||||
const { code } = useEditorStore();
|
const { code } = useEditorStore();
|
||||||
const { setCompiledHex, startSimulation, stopSimulation, resetSimulation, running, compiledHex } = useSimulatorStore();
|
const {
|
||||||
|
boardType,
|
||||||
|
setCompiledHex,
|
||||||
|
setCompiledBinary,
|
||||||
|
startSimulation,
|
||||||
|
stopSimulation,
|
||||||
|
resetSimulation,
|
||||||
|
running,
|
||||||
|
compiledHex,
|
||||||
|
} = useSimulatorStore();
|
||||||
const [compiling, setCompiling] = useState(false);
|
const [compiling, setCompiling] = useState(false);
|
||||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
|
|
||||||
|
|
@ -15,21 +24,27 @@ export const EditorToolbar = () => {
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Starting compilation...');
|
const fqbn = BOARD_FQBN[boardType];
|
||||||
const result = await compileCode(code);
|
console.log('Starting compilation for board:', fqbn);
|
||||||
|
const result = await compileCode(code, fqbn);
|
||||||
console.log('Compilation result:', result);
|
console.log('Compilation result:', result);
|
||||||
|
|
||||||
if (result.success && result.hex_content) {
|
if (result.success) {
|
||||||
|
if (result.hex_content) {
|
||||||
|
// AVR path
|
||||||
setCompiledHex(result.hex_content);
|
setCompiledHex(result.hex_content);
|
||||||
setMessage({ type: 'success', text: 'Compilation successful! Ready to run.' });
|
setMessage({ type: 'success', text: 'Compilation successful! Ready to run.' });
|
||||||
|
} else if (result.binary_content) {
|
||||||
|
// RP2040 path
|
||||||
|
setCompiledBinary(result.binary_content);
|
||||||
|
setMessage({ type: 'success', text: 'Compilation successful! Ready to run.' });
|
||||||
|
} else {
|
||||||
|
setMessage({ type: 'error', text: 'Compilation produced no output' });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = result.error || result.stderr || 'Compilation failed';
|
const errorMsg = result.error || result.stderr || 'Compilation failed';
|
||||||
console.error('Compilation error:', errorMsg);
|
console.error('Compilation error:', errorMsg);
|
||||||
console.error('Full result:', JSON.stringify(result, null, 2));
|
setMessage({ type: 'error', text: errorMsg });
|
||||||
setMessage({
|
|
||||||
type: 'error',
|
|
||||||
text: errorMsg,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Compilation exception:', err);
|
console.error('Compilation exception:', err);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { useSimulatorStore, ARDUINO_POSITION } from '../../store/useSimulatorStore';
|
import { useSimulatorStore, ARDUINO_POSITION, BOARD_LABELS } from '../../store/useSimulatorStore';
|
||||||
|
import type { BoardType } from '../../store/useSimulatorStore';
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { ArduinoUno } from '../components-wokwi/ArduinoUno';
|
import { ArduinoUno } from '../components-wokwi/ArduinoUno';
|
||||||
|
import { NanoRP2040 } from '../components-wokwi/NanoRP2040';
|
||||||
import { ComponentPickerModal } from '../ComponentPickerModal';
|
import { ComponentPickerModal } from '../ComponentPickerModal';
|
||||||
import { ComponentPropertyDialog } from './ComponentPropertyDialog';
|
import { ComponentPropertyDialog } from './ComponentPropertyDialog';
|
||||||
import { DynamicComponent, createComponentFromMetadata } from '../DynamicComponent';
|
import { DynamicComponent, createComponentFromMetadata } from '../DynamicComponent';
|
||||||
|
|
@ -14,6 +16,8 @@ import './SimulatorCanvas.css';
|
||||||
|
|
||||||
export const SimulatorCanvas = () => {
|
export const SimulatorCanvas = () => {
|
||||||
const {
|
const {
|
||||||
|
boardType,
|
||||||
|
setBoardType,
|
||||||
components,
|
components,
|
||||||
running,
|
running,
|
||||||
pinManager,
|
pinManager,
|
||||||
|
|
@ -371,8 +375,21 @@ export const SimulatorCanvas = () => {
|
||||||
{/* Main Canvas */}
|
{/* Main Canvas */}
|
||||||
<div className="simulator-canvas">
|
<div className="simulator-canvas">
|
||||||
<div className="canvas-header">
|
<div className="canvas-header">
|
||||||
<h3>Arduino Simulator</h3>
|
<h3>Simulator</h3>
|
||||||
<div className="canvas-header-info">
|
<div className="canvas-header-info">
|
||||||
|
{/* Board selector */}
|
||||||
|
<select
|
||||||
|
className="board-selector"
|
||||||
|
value={boardType}
|
||||||
|
onChange={(e) => setBoardType(e.target.value as BoardType)}
|
||||||
|
disabled={running}
|
||||||
|
title="Select board"
|
||||||
|
>
|
||||||
|
{(Object.entries(BOARD_LABELS) as [BoardType, string][]).map(([type, label]) => (
|
||||||
|
<option key={type} value={type}>{label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="add-component-btn"
|
className="add-component-btn"
|
||||||
onClick={() => setShowComponentPicker(true)}
|
onClick={() => setShowComponentPicker(true)}
|
||||||
|
|
@ -397,16 +414,24 @@ export const SimulatorCanvas = () => {
|
||||||
{/* Wire Layer - Renders below all components */}
|
{/* Wire Layer - Renders below all components */}
|
||||||
<WireLayer />
|
<WireLayer />
|
||||||
|
|
||||||
{/* Arduino Uno Board using wokwi-elements */}
|
{/* Board visual — switches based on selected board type */}
|
||||||
|
{boardType === 'arduino-uno' ? (
|
||||||
<ArduinoUno
|
<ArduinoUno
|
||||||
x={ARDUINO_POSITION.x}
|
x={ARDUINO_POSITION.x}
|
||||||
y={ARDUINO_POSITION.y}
|
y={ARDUINO_POSITION.y}
|
||||||
led13={Boolean(components.find((c) => c.id === 'led-builtin')?.properties.state)}
|
led13={Boolean(components.find((c) => c.id === 'led-builtin')?.properties.state)}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<NanoRP2040
|
||||||
|
x={ARDUINO_POSITION.x}
|
||||||
|
y={ARDUINO_POSITION.y}
|
||||||
|
ledBuiltIn={Boolean(components.find((c) => c.id === 'led-builtin')?.properties.state)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Arduino pin overlay */}
|
{/* Board pin overlay */}
|
||||||
<PinOverlay
|
<PinOverlay
|
||||||
componentId="arduino-uno"
|
componentId={boardType === 'arduino-uno' ? 'arduino-uno' : 'nano-rp2040'}
|
||||||
componentX={ARDUINO_POSITION.x}
|
componentX={ARDUINO_POSITION.x}
|
||||||
componentY={ARDUINO_POSITION.y}
|
componentY={ARDUINO_POSITION.y}
|
||||||
onPinClick={handlePinClick}
|
onPinClick={handlePinClick}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const API_BASE = 'http://localhost:8001/api';
|
const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8001/api';
|
||||||
|
|
||||||
export interface CompileResult {
|
export interface CompileResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
hex_content?: string;
|
hex_content?: string;
|
||||||
|
binary_content?: string; // base64-encoded .bin for RP2040
|
||||||
|
binary_type?: 'bin' | 'uf2';
|
||||||
stdout: string;
|
stdout: string;
|
||||||
stderr: string;
|
stderr: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,20 @@ export class PinManager {
|
||||||
return this.pinStates.get(arduinoPin) || false;
|
return this.pinStates.get(arduinoPin) || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directly fire pin change callbacks for a specific pin.
|
||||||
|
* Used by RP2040Simulator which has individual GPIO listeners instead of PORT registers.
|
||||||
|
*/
|
||||||
|
triggerPinChange(pin: number, state: boolean): void {
|
||||||
|
const current = this.pinStates.get(pin);
|
||||||
|
if (current === state) return; // no change
|
||||||
|
this.pinStates.set(pin, state);
|
||||||
|
const callbacks = this.listeners.get(pin);
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.forEach(cb => cb(pin, state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── PWM duty cycle API ───────────────────────────────────────────────────
|
// ── PWM duty cycle API ───────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
import { RP2040, GPIOPinState } from 'rp2040js';
|
||||||
|
import { PinManager } from './PinManager';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RP2040Simulator — Emulates Raspberry Pi Pico (RP2040) using rp2040js
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - ARM Cortex-M0+ CPU emulation at 125 MHz
|
||||||
|
* - 30 GPIO pins (GPIO0-GPIO29)
|
||||||
|
* - ADC on GPIO26-GPIO29 (A0-A3)
|
||||||
|
* - LED_BUILTIN on GPIO25
|
||||||
|
*
|
||||||
|
* Arduino-pico pin mapping (Earle Philhower's core):
|
||||||
|
* D0 = GPIO0 … D29 = GPIO29
|
||||||
|
* A0 = GPIO26 … A3 = GPIO29
|
||||||
|
* LED_BUILTIN = GPIO25
|
||||||
|
*/
|
||||||
|
|
||||||
|
const F_CPU = 125_000_000; // 125 MHz
|
||||||
|
const CYCLE_NANOS = 1e9 / F_CPU; // nanoseconds per cycle (~8 ns)
|
||||||
|
const FPS = 60;
|
||||||
|
const CYCLES_PER_FRAME = Math.floor(F_CPU / FPS); // ~2 083 333
|
||||||
|
|
||||||
|
export class RP2040Simulator {
|
||||||
|
private rp2040: RP2040 | null = null;
|
||||||
|
private running = false;
|
||||||
|
private animationFrame: number | null = null;
|
||||||
|
public pinManager: PinManager;
|
||||||
|
private speed = 1.0;
|
||||||
|
private gpioUnsubscribers: Array<() => void> = [];
|
||||||
|
|
||||||
|
constructor(pinManager: PinManager) {
|
||||||
|
this.pinManager = pinManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a compiled binary into the RP2040 flash memory.
|
||||||
|
* Accepts a base64-encoded string of the raw .bin file output by arduino-cli.
|
||||||
|
*/
|
||||||
|
loadBinary(base64: string): void {
|
||||||
|
console.log('[RP2040] Loading binary...');
|
||||||
|
|
||||||
|
const binaryStr = atob(base64);
|
||||||
|
const bytes = new Uint8Array(binaryStr.length);
|
||||||
|
for (let i = 0; i < binaryStr.length; i++) {
|
||||||
|
bytes[i] = binaryStr.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[RP2040] Binary size: ${bytes.length} bytes`);
|
||||||
|
|
||||||
|
this.rp2040 = new RP2040();
|
||||||
|
|
||||||
|
// Load binary into flash starting at offset 0 (maps to 0x10000000)
|
||||||
|
this.rp2040.flash.set(bytes, 0);
|
||||||
|
|
||||||
|
// Set up GPIO listeners
|
||||||
|
this.setupGpioListeners();
|
||||||
|
|
||||||
|
console.log('[RP2040] CPU initialized, GPIO listeners attached');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Same interface as AVRSimulator for store compatibility */
|
||||||
|
loadHex(_hexContent: string): void {
|
||||||
|
console.warn('[RP2040] loadHex() called on RP2040Simulator — use loadBinary() instead');
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
getADC(): any {
|
||||||
|
return this.rp2040?.adc ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupGpioListeners(): void {
|
||||||
|
this.gpioUnsubscribers.forEach(fn => fn());
|
||||||
|
this.gpioUnsubscribers = [];
|
||||||
|
|
||||||
|
if (!this.rp2040) return;
|
||||||
|
|
||||||
|
for (let gpioIdx = 0; gpioIdx < 30; gpioIdx++) {
|
||||||
|
const pin = gpioIdx;
|
||||||
|
const gpio = this.rp2040.gpio[gpioIdx];
|
||||||
|
if (!gpio) continue;
|
||||||
|
|
||||||
|
const unsub = gpio.addListener((state: GPIOPinState, _oldState: GPIOPinState) => {
|
||||||
|
const isHigh = state === GPIOPinState.High;
|
||||||
|
this.pinManager.triggerPinChange(pin, isHigh);
|
||||||
|
});
|
||||||
|
this.gpioUnsubscribers.push(unsub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): void {
|
||||||
|
if (this.running || !this.rp2040) {
|
||||||
|
console.warn('[RP2040] Already running or not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.running = true;
|
||||||
|
console.log('[RP2040] Starting simulation at 125 MHz...');
|
||||||
|
|
||||||
|
let frameCount = 0;
|
||||||
|
const execute = (_timestamp: number) => {
|
||||||
|
if (!this.running || !this.rp2040) return;
|
||||||
|
|
||||||
|
const cyclesTarget = Math.floor(CYCLES_PER_FRAME * this.speed);
|
||||||
|
const { core } = this.rp2040;
|
||||||
|
// Access the internal clock — rp2040js attaches it to the RP2040 instance
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const clock = (this.rp2040 as any).clock;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let cyclesDone = 0;
|
||||||
|
while (cyclesDone < cyclesTarget) {
|
||||||
|
if (core.waiting) {
|
||||||
|
if (clock) {
|
||||||
|
const jump: number = clock.nanosToNextAlarm;
|
||||||
|
clock.tick(jump);
|
||||||
|
cyclesDone += Math.ceil(jump / CYCLE_NANOS);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const cycles: number = core.executeInstruction();
|
||||||
|
if (clock) clock.tick(cycles * CYCLE_NANOS);
|
||||||
|
cyclesDone += cycles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frameCount++;
|
||||||
|
if (frameCount % 60 === 0) {
|
||||||
|
console.log(`[RP2040] Frame ${frameCount}, PC: 0x${core.PC.toString(16)}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RP2040] Simulation error:', error);
|
||||||
|
this.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.animationFrame = requestAnimationFrame(execute);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.animationFrame = requestAnimationFrame(execute);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
if (!this.running) return;
|
||||||
|
this.running = false;
|
||||||
|
if (this.animationFrame !== null) {
|
||||||
|
cancelAnimationFrame(this.animationFrame);
|
||||||
|
this.animationFrame = null;
|
||||||
|
}
|
||||||
|
console.log('[RP2040] Simulation stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.stop();
|
||||||
|
if (this.rp2040) {
|
||||||
|
const flashCopy = new Uint8Array(this.rp2040.flash);
|
||||||
|
this.rp2040 = new RP2040();
|
||||||
|
this.rp2040.flash.set(flashCopy, 0);
|
||||||
|
this.setupGpioListeners();
|
||||||
|
console.log('[RP2040] CPU reset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning(): boolean {
|
||||||
|
return this.running;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSpeed(speed: number): void {
|
||||||
|
this.speed = Math.max(0.1, Math.min(10.0, speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drive a GPIO pin externally (e.g. from a button or slider).
|
||||||
|
* GPIO n = Arduino D(n) for Raspberry Pi Pico.
|
||||||
|
*/
|
||||||
|
setPinState(arduinoPin: number, state: boolean): void {
|
||||||
|
if (!this.rp2040) return;
|
||||||
|
const gpio = this.rp2040.gpio[arduinoPin];
|
||||||
|
if (gpio) {
|
||||||
|
gpio.setInputValue(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { AVRSimulator } from '../AVRSimulator';
|
import { AVRSimulator } from '../AVRSimulator';
|
||||||
|
import { RP2040Simulator } from '../RP2040Simulator';
|
||||||
|
|
||||||
|
export type AnySimulator = AVRSimulator | RP2040Simulator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for simulation logic mapped to a specific wokwi-element
|
* Interface for simulation logic mapped to a specific wokwi-element
|
||||||
|
|
@ -25,7 +28,7 @@ export interface PartSimulationLogic {
|
||||||
*/
|
*/
|
||||||
attachEvents?: (
|
attachEvents?: (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
avrSimulator: AVRSimulator,
|
simulator: AnySimulator,
|
||||||
getArduinoPinHelper: (componentPinName: string) => number | null
|
getArduinoPinHelper: (componentPinName: string) => number | null
|
||||||
) => () => void;
|
) => () => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,22 @@
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { AVRSimulator } from '../simulation/AVRSimulator';
|
import { AVRSimulator } from '../simulation/AVRSimulator';
|
||||||
|
import { RP2040Simulator } from '../simulation/RP2040Simulator';
|
||||||
import { PinManager } from '../simulation/PinManager';
|
import { PinManager } from '../simulation/PinManager';
|
||||||
import type { Wire, WireInProgress, WireEndpoint } from '../types/wire';
|
import type { Wire, WireInProgress, WireEndpoint } from '../types/wire';
|
||||||
import { calculatePinPosition } from '../utils/pinPositionCalculator';
|
import { calculatePinPosition } from '../utils/pinPositionCalculator';
|
||||||
|
|
||||||
|
export type BoardType = 'arduino-uno' | 'raspberry-pi-pico';
|
||||||
|
|
||||||
|
export const BOARD_FQBN: Record<BoardType, string> = {
|
||||||
|
'arduino-uno': 'arduino:avr:uno',
|
||||||
|
'raspberry-pi-pico': 'rp2040:rp2040:rpipico',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BOARD_LABELS: Record<BoardType, string> = {
|
||||||
|
'arduino-uno': 'Arduino Uno',
|
||||||
|
'raspberry-pi-pico': 'Raspberry Pi Pico',
|
||||||
|
};
|
||||||
|
|
||||||
// Fixed position for the Arduino board (not in components array)
|
// Fixed position for the Arduino board (not in components array)
|
||||||
export const ARDUINO_POSITION = { x: 50, y: 50 };
|
export const ARDUINO_POSITION = { x: 50, y: 50 };
|
||||||
|
|
||||||
|
|
@ -16,8 +29,12 @@ interface Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SimulatorState {
|
interface SimulatorState {
|
||||||
|
// Board selection
|
||||||
|
boardType: BoardType;
|
||||||
|
setBoardType: (type: BoardType) => void;
|
||||||
|
|
||||||
// Simulation state
|
// Simulation state
|
||||||
simulator: AVRSimulator | null;
|
simulator: AVRSimulator | RP2040Simulator | null;
|
||||||
pinManager: PinManager;
|
pinManager: PinManager;
|
||||||
running: boolean;
|
running: boolean;
|
||||||
compiledHex: string | null;
|
compiledHex: string | null;
|
||||||
|
|
@ -33,10 +50,12 @@ interface SimulatorState {
|
||||||
// Actions
|
// Actions
|
||||||
initSimulator: () => void;
|
initSimulator: () => void;
|
||||||
loadHex: (hex: string) => void;
|
loadHex: (hex: string) => void;
|
||||||
|
loadBinary: (base64: string) => void;
|
||||||
startSimulation: () => void;
|
startSimulation: () => void;
|
||||||
stopSimulation: () => void;
|
stopSimulation: () => void;
|
||||||
resetSimulation: () => void;
|
resetSimulation: () => void;
|
||||||
setCompiledHex: (hex: string) => void;
|
setCompiledHex: (hex: string) => void;
|
||||||
|
setCompiledBinary: (base64: string) => void;
|
||||||
setRunning: (running: boolean) => void;
|
setRunning: (running: boolean) => void;
|
||||||
|
|
||||||
// Component management
|
// Component management
|
||||||
|
|
@ -70,6 +89,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
const pinManager = new PinManager();
|
const pinManager = new PinManager();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
boardType: 'arduino-uno' as BoardType,
|
||||||
simulator: null,
|
simulator: null,
|
||||||
pinManager,
|
pinManager,
|
||||||
running: false,
|
running: false,
|
||||||
|
|
@ -133,15 +153,30 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
selectedWireId: null,
|
selectedWireId: null,
|
||||||
wireInProgress: null,
|
wireInProgress: null,
|
||||||
|
|
||||||
|
setBoardType: (type: BoardType) => {
|
||||||
|
const { running } = get();
|
||||||
|
if (running) {
|
||||||
|
get().stopSimulation();
|
||||||
|
}
|
||||||
|
const simulator = type === 'arduino-uno'
|
||||||
|
? new AVRSimulator(pinManager)
|
||||||
|
: new RP2040Simulator(pinManager);
|
||||||
|
set({ boardType: type, simulator, compiledHex: null });
|
||||||
|
console.log(`Board switched to: ${type}`);
|
||||||
|
},
|
||||||
|
|
||||||
initSimulator: () => {
|
initSimulator: () => {
|
||||||
const simulator = new AVRSimulator(pinManager);
|
const { boardType } = get();
|
||||||
|
const simulator = boardType === 'arduino-uno'
|
||||||
|
? new AVRSimulator(pinManager)
|
||||||
|
: new RP2040Simulator(pinManager);
|
||||||
set({ simulator });
|
set({ simulator });
|
||||||
console.log('Simulator initialized');
|
console.log(`Simulator initialized: ${boardType}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
loadHex: (hex: string) => {
|
loadHex: (hex: string) => {
|
||||||
const { simulator } = get();
|
const { simulator } = get();
|
||||||
if (simulator) {
|
if (simulator && simulator instanceof AVRSimulator) {
|
||||||
try {
|
try {
|
||||||
simulator.loadHex(hex);
|
simulator.loadHex(hex);
|
||||||
set({ compiledHex: hex });
|
set({ compiledHex: hex });
|
||||||
|
|
@ -150,7 +185,22 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
console.error('Failed to load HEX:', error);
|
console.error('Failed to load HEX:', error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Simulator not initialized');
|
console.warn('loadHex: simulator not initialized or wrong board type');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadBinary: (base64: string) => {
|
||||||
|
const { simulator } = get();
|
||||||
|
if (simulator && simulator instanceof RP2040Simulator) {
|
||||||
|
try {
|
||||||
|
simulator.loadBinary(base64);
|
||||||
|
set({ compiledHex: base64 }); // reuse compiledHex as "program loaded" flag
|
||||||
|
console.log('Binary loaded into RP2040 successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load binary:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('loadBinary: simulator not initialized or wrong board type');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -180,10 +230,14 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
|
|
||||||
setCompiledHex: (hex: string) => {
|
setCompiledHex: (hex: string) => {
|
||||||
set({ compiledHex: hex });
|
set({ compiledHex: hex });
|
||||||
// Auto-load hex when set
|
|
||||||
get().loadHex(hex);
|
get().loadHex(hex);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setCompiledBinary: (base64: string) => {
|
||||||
|
set({ compiledHex: base64 }); // use compiledHex as "program ready" flag
|
||||||
|
get().loadBinary(base64);
|
||||||
|
},
|
||||||
|
|
||||||
setRunning: (running: boolean) => set({ running }),
|
setRunning: (running: boolean) => set({ running }),
|
||||||
|
|
||||||
addComponent: (component) => {
|
addComponent: (component) => {
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'avr8js': path.resolve(__dirname, '../wokwi-libs/avr8js/dist/esm'),
|
'avr8js': path.resolve(__dirname, '../wokwi-libs/avr8js/dist/esm'),
|
||||||
|
'rp2040js': path.resolve(__dirname, '../wokwi-libs/rp2040js/dist/esm'),
|
||||||
'@wokwi/elements': path.resolve(__dirname, '../wokwi-libs/wokwi-elements/dist/esm'),
|
'@wokwi/elements': path.resolve(__dirname, '../wokwi-libs/wokwi-elements/dist/esm'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['avr8js', '@wokwi/elements'],
|
include: ['avr8js', 'rp2040js', '@wokwi/elements'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue