feat: Add Arduino Nano support to simulator and update related components

pull/10/head
David Montero Crespo 2026-03-07 23:14:33 -03:00
parent c7f9d6d029
commit 41dfd20583
6 changed files with 73 additions and 16 deletions

View File

@ -0,0 +1,36 @@
import '@wokwi/elements';
import { useRef, useEffect } from 'react';
interface ArduinoNanoProps {
id?: string;
x?: number;
y?: number;
led13?: boolean;
}
export const ArduinoNano = ({
id = 'arduino-nano',
x = 0,
y = 0,
led13 = false,
}: ArduinoNanoProps) => {
const nanoRef = useRef<HTMLElement>(null);
useEffect(() => {
if (nanoRef.current) {
(nanoRef.current as any).led13 = led13;
}
}, [led13]);
return (
<wokwi-arduino-nano
id={id}
ref={nanoRef}
style={{
position: 'absolute',
left: `${x}px`,
top: `${y}px`,
}}
/>
);
};

View File

@ -2,6 +2,7 @@ import { useSimulatorStore, BOARD_LABELS } from '../../store/useSimulatorStore';
import type { BoardType } from '../../store/useSimulatorStore';
import React, { useEffect, useState, useRef, useCallback } from 'react';
import { ArduinoUno } from '../components-wokwi/ArduinoUno';
import { ArduinoNano } from '../components-wokwi/ArduinoNano';
import { NanoRP2040 } from '../components-wokwi/NanoRP2040';
import { ComponentPickerModal } from '../ComponentPickerModal';
import { ComponentPropertyDialog } from './ComponentPropertyDialog';
@ -587,6 +588,12 @@ export const SimulatorCanvas = () => {
y={boardPosition.y}
led13={Boolean(components.find((c) => c.id === 'led-builtin')?.properties.state)}
/>
) : boardType === 'arduino-nano' ? (
<ArduinoNano
x={boardPosition.x}
y={boardPosition.y}
led13={Boolean(components.find((c) => c.id === 'led-builtin')?.properties.state)}
/>
) : (
<NanoRP2040
x={boardPosition.x}
@ -602,8 +609,8 @@ export const SimulatorCanvas = () => {
position: 'absolute',
left: boardPosition.x,
top: boardPosition.y,
width: boardType === 'arduino-uno' ? 360 : 280,
height: boardType === 'arduino-uno' ? 250 : 180,
width: boardType === 'arduino-uno' ? 360 : boardType === 'arduino-nano' ? 175 : 280,
height: boardType === 'arduino-uno' ? 250 : boardType === 'arduino-nano' ? 70 : 180,
cursor: 'move',
zIndex: 1,
}}
@ -621,7 +628,7 @@ export const SimulatorCanvas = () => {
{/* Board pin overlay */}
<PinOverlay
componentId={boardType === 'arduino-uno' ? 'arduino-uno' : 'nano-rp2040'}
componentId={boardType === 'arduino-uno' ? 'arduino-uno' : boardType === 'arduino-nano' ? 'arduino-nano' : 'nano-rp2040'}
componentX={boardPosition.x}
componentY={boardPosition.y}
onPinClick={handlePinClick}

View File

@ -11,7 +11,7 @@ export interface ExampleProject {
category: 'basics' | 'sensors' | 'displays' | 'communication' | 'games' | 'robotics';
difficulty: 'beginner' | 'intermediate' | 'advanced';
/** Target board — defaults to 'arduino-uno' if omitted */
boardType?: 'arduino-uno' | 'raspberry-pi-pico';
boardType?: 'arduino-uno' | 'arduino-nano' | 'raspberry-pi-pico';
code: string;
components: Array<{
type: string;

View File

@ -7,15 +7,17 @@ import type { RP2040I2CDevice } from '../simulation/RP2040Simulator';
import type { Wire, WireInProgress, WireEndpoint } from '../types/wire';
import { calculatePinPosition } from '../utils/pinPositionCalculator';
export type BoardType = 'arduino-uno' | 'raspberry-pi-pico';
export type BoardType = 'arduino-uno' | 'arduino-nano' | 'raspberry-pi-pico';
export const BOARD_FQBN: Record<BoardType, string> = {
'arduino-uno': 'arduino:avr:uno',
'arduino-nano': 'arduino:avr:nano:cpu=atmega328',
'raspberry-pi-pico': 'rp2040:rp2040:rpipico',
};
export const BOARD_LABELS: Record<BoardType, string> = {
'arduino-uno': 'Arduino Uno',
'arduino-nano': 'Arduino Nano',
'raspberry-pi-pico': 'Raspberry Pi Pico',
};
@ -184,7 +186,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
if (running) {
get().stopSimulation();
}
const simulator = type === 'arduino-uno'
const simulator = (type === 'arduino-uno' || type === 'arduino-nano')
? new AVRSimulator(pinManager)
: new RP2040Simulator(pinManager);
// Wire serial output callback for both simulator types
@ -200,7 +202,7 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
initSimulator: () => {
const { boardType } = get();
const simulator = boardType === 'arduino-uno'
const simulator = (boardType === 'arduino-uno' || boardType === 'arduino-nano')
? new AVRSimulator(pinManager)
: new RP2040Simulator(pinManager);
// Wire serial output callback for both simulator types

View File

@ -51,7 +51,7 @@ const ARDUINO_UNO_ANALOG_MAP: Record<string, number> = {
};
/** All known board component IDs in the simulator */
export const BOARD_COMPONENT_IDS = ['arduino-uno', 'nano-rp2040'];
export const BOARD_COMPONENT_IDS = ['arduino-uno', 'arduino-nano', 'nano-rp2040'];
/**
* Check whether a componentId represents a board (not an external component).
@ -69,7 +69,7 @@ export function isBoardComponent(componentId: string): boolean {
* @returns Numeric pin/GPIO number, or null if unmapped
*/
export function boardPinToNumber(boardId: string, pinName: string): number | null {
if (boardId === 'arduino-uno') {
if (boardId === 'arduino-uno' || boardId === 'arduino-nano') {
// Try numeric (covers '0' through '13', also legacy examples using just numbers)
const num = parseInt(pinName, 10);
if (!isNaN(num) && num >= 0 && num <= 21) return num;

View File

@ -43,7 +43,7 @@ export interface VelxioComponent {
}
export interface ImportResult {
boardType: 'arduino-uno' | 'raspberry-pi-pico';
boardType: 'arduino-uno' | 'arduino-nano' | 'raspberry-pi-pico';
boardPosition: { x: number; y: number };
components: VelxioComponent[];
wires: Wire[];
@ -53,9 +53,9 @@ export interface ImportResult {
// ── Board mappings ────────────────────────────────────────────────────────────
// Wokwi board type → Velxio boardType
const WOKWI_TYPE_TO_BOARD: Record<string, 'arduino-uno' | 'raspberry-pi-pico'> = {
const WOKWI_TYPE_TO_BOARD: Record<string, 'arduino-uno' | 'arduino-nano' | 'raspberry-pi-pico'> = {
'wokwi-arduino-uno': 'arduino-uno',
'wokwi-arduino-nano': 'arduino-uno',
'wokwi-arduino-nano': 'arduino-nano',
'wokwi-arduino-mega': 'arduino-uno',
'wokwi-raspberry-pi-pico': 'raspberry-pi-pico',
};
@ -63,12 +63,14 @@ const WOKWI_TYPE_TO_BOARD: Record<string, 'arduino-uno' | 'raspberry-pi-pico'> =
// Velxio boardType → Wokwi type
const BOARD_TO_WOKWI_TYPE: Record<string, string> = {
'arduino-uno': 'wokwi-arduino-uno',
'arduino-nano': 'wokwi-arduino-nano',
'raspberry-pi-pico': 'wokwi-raspberry-pi-pico',
};
// Velxio boardType → default Wokwi part id
const BOARD_TO_WOKWI_ID: Record<string, string> = {
'arduino-uno': 'uno',
'arduino-nano': 'nano',
'raspberry-pi-pico': 'pico',
};
@ -140,8 +142,10 @@ export async function exportToWokwiZip(
// Build connections
const connections: [string, string, string, string[]][] = wires.map((w) => {
const startId = w.start.componentId === 'arduino-uno' ? boardId : w.start.componentId;
const endId = w.end.componentId === 'arduino-uno' ? boardId : w.end.componentId;
const isBoardStart = w.start.componentId === 'arduino-uno' || w.start.componentId === 'arduino-nano' || w.start.componentId === 'nano-rp2040';
const isBoardEnd = w.end.componentId === 'arduino-uno' || w.end.componentId === 'arduino-nano' || w.end.componentId === 'nano-rp2040';
const startId = isBoardStart ? boardId : w.start.componentId;
const endId = isBoardEnd ? boardId : w.end.componentId;
return [
`${startId}:${w.start.pinName}`,
`${endId}:${w.end.pinName}`,
@ -193,6 +197,14 @@ export async function importFromWokwiZip(file: File): Promise<ImportResult> {
const boardType = boardPart ? WOKWI_TYPE_TO_BOARD[boardPart.type] : 'arduino-uno';
const boardId = boardPart?.id ?? 'uno';
// Velxio internal component ID for the board element (must match DOM element id)
const VELXIO_BOARD_ID: Record<string, string> = {
'arduino-uno': 'arduino-uno',
'arduino-nano': 'arduino-nano',
'raspberry-pi-pico': 'nano-rp2040',
};
const velxioBoardId = VELXIO_BOARD_ID[boardType] ?? 'arduino-uno';
// Board position from diagram (use directly as Velxio board position)
const boardPosition = {
x: boardPart?.left ?? 50,
@ -221,8 +233,8 @@ export async function importFromWokwiZip(file: File): Promise<ImportResult> {
const endPin = colonB >= 0 ? endStr.slice(colonB + 1) : '';
// Remap board part id → Velxio internal board id
const startId = startCompRaw === boardId ? 'arduino-uno' : startCompRaw;
const endId = endCompRaw === boardId ? 'arduino-uno' : endCompRaw;
const startId = startCompRaw === boardId ? velxioBoardId : startCompRaw;
const endId = endCompRaw === boardId ? velxioBoardId : endCompRaw;
return {
id: `wire-${i}-${Date.now()}`,