velxio/fancy-splashing-shannon.md

390 lines
11 KiB
Markdown

# Plan: Construcción de Emulador Arduino Local (Wokwi Clone)
## Resumen Ejecutivo
Crear una aplicación web local que permita editar, compilar y emular código Arduino con visualización de componentes electrónicos en tiempo real.
**Arquitectura:** Monolito web (React + Vite) con backend FastAPI para compilación
**Prioridades:** Editor de código + compilación, emulación Arduino Uno, componentes básicos (LED, resistencias)
**Persistencia:** SQLite
## Tecnologías Core
### Frontend
- **React + Vite + TypeScript** - Framework principal
- **Monaco Editor** (`@monaco-editor/react`) - Editor de código
- **avr8js** - Emulador AVR8 (ATmega328p = Arduino Uno)
- **@wokwi/elements** - Componentes electrónicos web (LEDs, resistencias)
- **Zustand** - State management
### Backend
- **FastAPI + Python** - API REST
- **arduino-cli** - Compilador de Arduino
- **SQLAlchemy + SQLite** - Base de datos
## Estructura del Proyecto
```
wokwi_clon/
├── frontend/ # React + Vite
│ ├── src/
│ │ ├── components/
│ │ │ ├── editor/
│ │ │ │ ├── CodeEditor.tsx # Monaco Editor wrapper
│ │ │ │ └── EditorToolbar.tsx # Compile/Run/Stop
│ │ │ ├── simulator/
│ │ │ │ ├── SimulatorCanvas.tsx # Canvas principal
│ │ │ │ └── ArduinoBoard.tsx # Visualización Arduino Uno
│ │ │ ├── components-wokwi/
│ │ │ │ ├── LED.tsx # Wrapper para wokwi-led
│ │ │ │ └── Resistor.tsx # Wrapper para wokwi-resistor
│ │ │ └── projects/
│ │ │ ├── ProjectList.tsx # Lista de proyectos
│ │ │ └── ProjectDialog.tsx # Guardar/Cargar
│ │ ├── simulation/
│ │ │ ├── AVRSimulator.ts # Core: avr8js wrapper
│ │ │ ├── PinManager.ts # Gestión de pines
│ │ │ └── ComponentRegistry.ts # Registro de componentes
│ │ ├── store/
│ │ │ ├── useSimulatorStore.ts # Estado de simulación (Zustand)
│ │ │ ├── useEditorStore.ts # Estado del editor
│ │ │ └── useProjectStore.ts # Estado de proyectos
│ │ ├── services/
│ │ │ ├── api.ts # Cliente API
│ │ │ └── compilation.ts # Servicio de compilación
│ │ └── utils/
│ │ └── hexParser.ts # Parser de archivos .hex
│ ├── package.json
│ └── vite.config.ts
├── backend/ # Python + FastAPI
│ ├── app/
│ │ ├── main.py # Entry point
│ │ ├── api/routes/
│ │ │ ├── compile.py # POST /api/compile
│ │ │ └── projects.py # CRUD proyectos
│ │ ├── services/
│ │ │ └── arduino_cli.py # Integración arduino-cli
│ │ ├── models/
│ │ │ └── project.py # Modelo SQLAlchemy
│ │ └── database/
│ │ └── connection.py # Conexión SQLite
│ └── requirements.txt
└── README.md
```
## Flujo de Datos Principal
### 1. Compilación (Editor → Backend → Hex)
```
Usuario escribe código en Monaco Editor
Click "Compile"
POST /api/compile { code: "...", board_fqbn: "arduino:avr:uno" }
Backend: arduino-cli compila código a .hex
Backend retorna { success: true, hex_content: "..." }
Frontend: useSimulatorStore.loadHex(hex)
```
### 2. Emulación (Hex → CPU → Pines → Componentes)
```
AVRSimulator.loadHex(hexString)
Parse hex → Uint16Array (program memory)
Inicializar CPU (ATmega328p)
Click "Run"
Execution loop (requestAnimationFrame)
CPU ejecuta ~267k cycles/frame @ 60fps
CPU escribe en PORTB/PORTC/PORTD
Write hooks → PinManager.updatePort()
PinManager notifica componentes conectados
LED actualiza estado visual (ref.current.value = true/false)
```
## Archivos Críticos para Implementar
### 1. `frontend/src/simulation/AVRSimulator.ts`
**Motor de emulación**
- Integra avr8js (CPU, AVRTimer, AVRUSART)
- Carga archivos .hex a memoria de programa
- Loop de ejecución con requestAnimationFrame
- Write hooks en registros PORT (0x25=PORTB, 0x28=PORTC, 0x2B=PORTD)
```typescript
export class AVRSimulator {
private cpu: CPU | null = null;
private program: Uint16Array | null = null;
loadHex(hexContent: string) {
const bytes = hexToUint8Array(hexContent);
this.program = new Uint16Array(16384); // 32KB
// Load bytes into program memory...
this.cpu = new CPU(this.program);
this.setupPinHooks();
}
start() {
const execute = () => {
for (let i = 0; i < 267000; i++) {
this.cpu.tick();
}
requestAnimationFrame(execute);
};
requestAnimationFrame(execute);
}
}
```
### 2. `frontend/src/components/components-wokwi/LED.tsx`
**Wrapper React para Web Components**
- Importa `@wokwi/elements`
- Usa `useRef` para manipular DOM directamente
- Propiedades via asignación directa (no atributos)
```typescript
import '@wokwi/elements';
export const LED = ({ color, value, x, y }) => {
const ledRef = useRef<HTMLElement>(null);
useEffect(() => {
if (ledRef.current) {
(ledRef.current as any).value = value;
(ledRef.current as any).color = color;
}
}, [value, color]);
return <wokwi-led ref={ledRef} style={{ position: 'absolute', left: x, top: y }} />;
};
```
### 3. `backend/app/services/arduino_cli.py`
**Integración con arduino-cli**
- Compilación asíncrona con asyncio
- Manejo de archivos temporales
- Parse de errores de compilación
```python
async def compile(code: str, board_fqbn: str = "arduino:avr:uno") -> dict:
with tempfile.TemporaryDirectory() as temp_dir:
sketch_dir = Path(temp_dir) / "sketch"
sketch_dir.mkdir()
(sketch_dir / "sketch.ino").write_text(code)
process = await asyncio.create_subprocess_exec(
"arduino-cli", "compile",
"--fqbn", board_fqbn,
"--output-dir", str(sketch_dir / "build"),
str(sketch_dir),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await process.communicate()
if process.returncode == 0:
hex_file = sketch_dir / "build" / "sketch.ino.hex"
return {
"success": True,
"hex_content": hex_file.read_text()
}
else:
return {
"success": False,
"error": stderr.decode()
}
```
### 4. `frontend/src/store/useSimulatorStore.ts`
**Estado global de simulación (Zustand)**
- Simulator instance
- Estado running/stopped
- Lista de componentes
- Actions: loadHex, start, stop, addComponent
```typescript
export const useSimulatorStore = create<SimulatorState>((set, get) => ({
simulator: null,
running: false,
components: [],
loadHex: (hex: string) => {
const { simulator } = get();
simulator?.loadHex(hex);
},
startSimulation: () => {
get().simulator?.start();
set({ running: true });
},
addComponent: (component) => {
set(state => ({
components: [...state.components, component]
}));
}
}));
```
### 5. `backend/app/models/project.py`
**Modelo de base de datos**
```python
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
code = Column(Text, nullable=False)
circuit = Column(JSON) # { components: [...], wires: [...] }
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, onupdate=datetime.utcnow)
```
**Circuit JSON Structure:**
```json
{
"components": [
{ "id": "led1", "type": "led", "x": 250, "y": 150, "properties": { "color": "red" } }
],
"wires": [
{ "from": { "component": "arduino", "pin": 13 }, "to": { "component": "led1", "pin": "A" } }
]
}
```
## Dependencias Clave
### Frontend
```json
{
"@monaco-editor/react": "^4.6.0",
"avr8js": "^0.30.0",
"@wokwi/elements": "^1.9.1",
"zustand": "^4.5.0",
"axios": "^1.7.0",
"react": "^18.3.1",
"vite": "^5.4.0"
}
```
### Backend
```txt
fastapi==0.115.0
uvicorn[standard]==0.32.0
sqlalchemy==2.0.36
aiosqlite==0.20.0
pydantic==2.9.2
```
### Herramientas Externas
```bash
# Instalar arduino-cli
# Windows:
choco install arduino-cli
# Inicializar:
arduino-cli core update-index
arduino-cli core install arduino:avr
```
## Fases de Implementación
### Fase 1: Foundation (Prioridad Alta)
- [frontend] Inicializar proyecto Vite + React + TypeScript
- [frontend] Integrar Monaco Editor con syntax highlighting C++
- [frontend] Layout básico (editor izquierda, canvas derecha)
- [backend] Setup FastAPI + endpoint /api/compile
- [backend] Integración arduino-cli para compilación
- **Entregable:** Compilar código y recibir .hex
### Fase 2: Emulation Core (Prioridad Alta)
- [frontend] Implementar AVRSimulator.ts con avr8js
- [frontend] Parser de archivos .hex
- [frontend] PinManager para tracking de pines
- [frontend] Botones Run/Stop/Reset
- **Entregable:** Ejecutar Blink example y ver cambios de pin
### Fase 3: Visual Components (Prioridad Alta)
- [frontend] Wrappers React para wokwi-led, wokwi-resistor
- [frontend] SimulatorCanvas con drag & drop
- [frontend] Conectar LEDs a PinManager
- **Entregable:** LED se enciende/apaga con digitalWrite()
### Fase 4: Project Persistence (Prioridad Media)
- [backend] Setup SQLite con SQLAlchemy
- [backend] CRUD endpoints para proyectos
- [frontend] UI para guardar/cargar proyectos
- **Entregable:** Persistir código + circuito
### Fase 5: Polish (Prioridad Baja)
- Más componentes (botones, potenciómetros)
- Serial monitor
- Control de velocidad
- Ejemplos pre-cargados
- **Entregable:** App completa y pulida
## Puntos Críticos de Integración
### Monaco Editor en Vite
- Usar `@monaco-editor/react` (no `monaco-editor` directamente)
- No requiere configuración especial de Vite
- Syntax highlighting C++ funciona con `defaultLanguage="cpp"`
### Web Components en React
- Web Components requieren manipulación directa del DOM
- Usar `useRef` + `useEffect` para setear propiedades
- Declarar tipos JSX en `vite-env.d.ts`
### avr8js Performance
- Ejecutar en batches (~267k cycles/frame @ 16MHz/60fps)
- Usar `requestAnimationFrame` para smooth simulation
- Evitar re-renders de React (usar refs)
### arduino-cli
- Sketch name debe coincidir con directory name
- Output: `<sketch_name>.ino.hex`
- Requiere `arduino:avr` core instalado
## Comandos de Desarrollo
```bash
# Backend
cd backend
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
# Frontend
cd frontend
npm install
npm run dev
# Acceso
Frontend: http://localhost:5173
Backend: http://localhost:8000
API Docs: http://localhost:8000/docs
```
## Archivos de Configuración Esenciales
### [frontend/vite.config.ts](frontend/vite.config.ts)
### [frontend/tsconfig.json](frontend/tsconfig.json)
### [frontend/package.json](frontend/package.json)
### [backend/app/main.py](backend/app/main.py)
### [backend/requirements.txt](backend/requirements.txt)