diff --git a/frontend/src/components/components-wokwi/ArduinoNano.tsx b/frontend/src/components/components-wokwi/ArduinoNano.tsx new file mode 100644 index 00000000..da211f33 --- /dev/null +++ b/frontend/src/components/components-wokwi/ArduinoNano.tsx @@ -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(null); + + useEffect(() => { + if (nanoRef.current) { + (nanoRef.current as any).led13 = led13; + } + }, [led13]); + + return ( + + ); +}; diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx index fb64d6ee..c3eb5d4d 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.tsx +++ b/frontend/src/components/simulator/SimulatorCanvas.tsx @@ -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' ? ( + c.id === 'led-builtin')?.properties.state)} + /> ) : ( { 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 */} = { 'arduino-uno': 'arduino:avr:uno', + 'arduino-nano': 'arduino:avr:nano:cpu=atmega328', 'raspberry-pi-pico': 'rp2040:rp2040:rpipico', }; export const BOARD_LABELS: Record = { 'arduino-uno': 'Arduino Uno', + 'arduino-nano': 'Arduino Nano', 'raspberry-pi-pico': 'Raspberry Pi Pico', }; @@ -184,7 +186,7 @@ export const useSimulatorStore = create((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((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 diff --git a/frontend/src/utils/boardPinMapping.ts b/frontend/src/utils/boardPinMapping.ts index 19c2c027..d1dd9e17 100644 --- a/frontend/src/utils/boardPinMapping.ts +++ b/frontend/src/utils/boardPinMapping.ts @@ -51,7 +51,7 @@ const ARDUINO_UNO_ANALOG_MAP: Record = { }; /** 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; diff --git a/frontend/src/utils/wokwiZip.ts b/frontend/src/utils/wokwiZip.ts index 1ab5c4ed..44913a6a 100644 --- a/frontend/src/utils/wokwiZip.ts +++ b/frontend/src/utils/wokwiZip.ts @@ -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 = { +const WOKWI_TYPE_TO_BOARD: Record = { '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 = // Velxio boardType → Wokwi type const BOARD_TO_WOKWI_TYPE: Record = { '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 = { '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 { 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 = { + '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 { 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()}`,