2026-03-03 10:20:49 +07:00
|
|
|
|
import { create } from 'zustand';
|
|
|
|
|
|
import { AVRSimulator } from '../simulation/AVRSimulator';
|
2026-03-05 05:28:33 +07:00
|
|
|
|
import { RP2040Simulator } from '../simulation/RP2040Simulator';
|
2026-03-16 01:39:26 +07:00
|
|
|
|
import { RiscVSimulator } from '../simulation/RiscVSimulator';
|
2026-03-16 04:40:08 +07:00
|
|
|
|
import { Esp32C3Simulator } from '../simulation/Esp32C3Simulator';
|
2026-03-03 10:20:49 +07:00
|
|
|
|
import { PinManager } from '../simulation/PinManager';
|
2026-03-05 16:56:14 +07:00
|
|
|
|
import { VirtualDS1307, VirtualTempSensor, I2CMemoryDevice } from '../simulation/I2CBusManager';
|
2026-03-06 07:07:03 +07:00
|
|
|
|
import type { RP2040I2CDevice } from '../simulation/RP2040Simulator';
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
import type { Wire, WireInProgress, WireEndpoint } from '../types/wire';
|
2026-03-30 07:27:41 +07:00
|
|
|
|
import type { BoardKind, BoardInstance, LanguageMode } from '../types/board';
|
|
|
|
|
|
import { BOARD_SUPPORTS_MICROPYTHON } from '../types/board';
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
import { calculatePinPosition } from '../utils/pinPositionCalculator';
|
2026-03-11 22:09:24 +07:00
|
|
|
|
import { useOscilloscopeStore } from './useOscilloscopeStore';
|
2026-03-13 09:39:04 +07:00
|
|
|
|
import { RaspberryPi3Bridge } from '../simulation/RaspberryPi3Bridge';
|
2026-03-14 06:35:48 +07:00
|
|
|
|
import { Esp32Bridge } from '../simulation/Esp32Bridge';
|
|
|
|
|
|
import { useEditorStore } from './useEditorStore';
|
2026-03-16 00:04:01 +07:00
|
|
|
|
import { useVfsStore } from './useVfsStore';
|
2026-03-23 04:03:17 +07:00
|
|
|
|
import { boardPinToNumber, isBoardComponent } from '../utils/boardPinMapping';
|
|
|
|
|
|
|
|
|
|
|
|
// ── Sensor pre-registration ──────────────────────────────────────────────────
|
|
|
|
|
|
// Maps component metadataId → { sensorType, dataPinName, propertyKeys }
|
|
|
|
|
|
// Used to pre-register sensors in the start_esp32 payload so the QEMU worker
|
|
|
|
|
|
// has them ready before the firmware starts executing (prevents race conditions).
|
|
|
|
|
|
const SENSOR_COMPONENT_MAP: Record<string, {
|
|
|
|
|
|
sensorType: string;
|
|
|
|
|
|
dataPinName: string;
|
|
|
|
|
|
propertyKeys: string[];
|
2026-03-23 04:39:09 +07:00
|
|
|
|
extraPins?: Record<string, string>; // extra pin mappings: prop name → component pin name
|
2026-03-23 04:03:17 +07:00
|
|
|
|
}> = {
|
|
|
|
|
|
'dht22': { sensorType: 'dht22', dataPinName: 'SDA', propertyKeys: ['temperature', 'humidity'] },
|
2026-03-23 04:39:09 +07:00
|
|
|
|
'hc-sr04': { sensorType: 'hc-sr04', dataPinName: 'TRIG', propertyKeys: ['distance'], extraPins: { echo_pin: 'ECHO' } },
|
2026-03-23 04:03:17 +07:00
|
|
|
|
};
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-04-08 10:02:43 +07:00
|
|
|
|
// ── I2C sensor pre-registration ───────────────────────────────────────────────
|
|
|
|
|
|
// I2C sensors use virtual pins (200 + i2c_addr) instead of real GPIO pins.
|
|
|
|
|
|
// They are identified by I2C address and do not need wire-resolution.
|
|
|
|
|
|
// `addrProp` is the component property that overrides the default address.
|
|
|
|
|
|
const I2C_SENSOR_MAP: Record<string, {
|
|
|
|
|
|
sensorType: string;
|
|
|
|
|
|
defaultAddr: number;
|
|
|
|
|
|
addrProp?: string; // property key that holds the I2C address (e.g. 'address')
|
2026-04-08 10:02:54 +07:00
|
|
|
|
addrIsBool?: boolean; // true when addrProp is a boolean flag (e.g. AD0 → 0x68/0x69)
|
|
|
|
|
|
addrBoolHigh?: number; // address when the boolean flag is truthy
|
2026-04-08 10:02:43 +07:00
|
|
|
|
propertyKeys?: string[]; // additional sensor values to forward (e.g. temperature, pressure)
|
|
|
|
|
|
}> = {
|
2026-04-08 10:02:54 +07:00
|
|
|
|
'mpu6050': { sensorType: 'mpu6050', defaultAddr: 0x68, addrProp: 'ad0', addrIsBool: true, addrBoolHigh: 0x69 },
|
2026-04-08 10:02:43 +07:00
|
|
|
|
'bmp280': { sensorType: 'bmp280', defaultAddr: 0x76, addrProp: 'address', propertyKeys: ['temperature', 'pressure'] },
|
|
|
|
|
|
'ds1307': { sensorType: 'ds1307', defaultAddr: 0x68 },
|
|
|
|
|
|
'ds3231': { sensorType: 'ds3231', defaultAddr: 0x68, propertyKeys: ['temperature'] },
|
|
|
|
|
|
'ssd1306': { sensorType: 'ssd1306', defaultAddr: 0x3C },
|
|
|
|
|
|
'pcf8574': { sensorType: 'pcf8574', defaultAddr: 0x27, addrProp: 'i2cAddress' },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Legacy type aliases (keep external consumers working) ──────────────────
|
2026-03-09 20:08:14 +07:00
|
|
|
|
export type BoardType = 'arduino-uno' | 'arduino-nano' | 'arduino-mega' | 'raspberry-pi-pico';
|
2026-03-05 05:28:33 +07:00
|
|
|
|
|
|
|
|
|
|
export const BOARD_FQBN: Record<BoardType, string> = {
|
|
|
|
|
|
'arduino-uno': 'arduino:avr:uno',
|
2026-03-08 09:14:33 +07:00
|
|
|
|
'arduino-nano': 'arduino:avr:nano:cpu=atmega328',
|
2026-03-09 20:08:14 +07:00
|
|
|
|
'arduino-mega': 'arduino:avr:mega',
|
2026-03-05 05:28:33 +07:00
|
|
|
|
'raspberry-pi-pico': 'rp2040:rp2040:rpipico',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const BOARD_LABELS: Record<BoardType, string> = {
|
|
|
|
|
|
'arduino-uno': 'Arduino Uno',
|
2026-03-08 09:14:33 +07:00
|
|
|
|
'arduino-nano': 'Arduino Nano',
|
2026-03-09 20:08:14 +07:00
|
|
|
|
'arduino-mega': 'Arduino Mega 2560',
|
2026-03-05 05:28:33 +07:00
|
|
|
|
'raspberry-pi-pico': 'Raspberry Pi Pico',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-08 05:29:23 +07:00
|
|
|
|
export const DEFAULT_BOARD_POSITION = { x: 50, y: 50 };
|
|
|
|
|
|
export const ARDUINO_POSITION = DEFAULT_BOARD_POSITION;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
|
2026-03-22 12:25:58 +07:00
|
|
|
|
// ── Lightweight shim wrapping Esp32Bridge so component simulations (DHT22, etc.)
|
|
|
|
|
|
// can call setPinState / pinManager just like they would on a local simulator. ──
|
|
|
|
|
|
class Esp32BridgeShim {
|
|
|
|
|
|
pinManager: PinManager;
|
|
|
|
|
|
onSerialData: ((ch: string) => void) | null = null;
|
|
|
|
|
|
onPinChangeWithTime: ((pin: number, state: boolean, timeMs: number) => void) | null = null;
|
|
|
|
|
|
onBaudRateChange: ((baud: number) => void) | null = null;
|
|
|
|
|
|
private bridge: Esp32Bridge;
|
|
|
|
|
|
|
|
|
|
|
|
constructor(bridge: Esp32Bridge, pm: PinManager) {
|
|
|
|
|
|
this.bridge = bridge;
|
|
|
|
|
|
this.pinManager = pm;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setPinState(pin: number, state: boolean): void { this.bridge.sendPinEvent(pin, state); }
|
|
|
|
|
|
getCurrentCycles(): number { return -1; }
|
|
|
|
|
|
getClockHz(): number { return 240_000_000; }
|
|
|
|
|
|
isRunning(): boolean { return this.bridge.connected; }
|
|
|
|
|
|
serialWrite(text: string): void {
|
|
|
|
|
|
this.bridge.sendSerialBytes(Array.from(new TextEncoder().encode(text)));
|
|
|
|
|
|
}
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
|
getADC(): any { return null; }
|
2026-03-23 20:37:06 +07:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Set ADC value for an ESP32 GPIO pin.
|
|
|
|
|
|
* ESP32 ADC1: GPIO 36-39 → CH0-3, GPIO 32-35 → CH4-7
|
|
|
|
|
|
* Returns true if the pin is a valid ADC pin.
|
|
|
|
|
|
*/
|
|
|
|
|
|
setAdcVoltage(pin: number, voltage: number): boolean {
|
|
|
|
|
|
let channel = -1;
|
|
|
|
|
|
if (pin >= 36 && pin <= 39) channel = pin - 36; // GPIO 36→CH0, 37→CH1, 38→CH2, 39→CH3
|
|
|
|
|
|
else if (pin >= 32 && pin <= 35) channel = pin - 28; // GPIO 32→CH4, 33→CH5, 34→CH6, 35→CH7
|
|
|
|
|
|
if (channel < 0) return false;
|
|
|
|
|
|
const millivolts = Math.round(voltage * 1000);
|
|
|
|
|
|
this.bridge.setAdc(channel, millivolts);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-03-22 12:25:58 +07:00
|
|
|
|
getMCU(): null { return null; }
|
|
|
|
|
|
start(): void { /* managed by bridge */ }
|
|
|
|
|
|
stop(): void { /* managed by bridge */ }
|
|
|
|
|
|
reset(): void { /* managed by bridge */ }
|
|
|
|
|
|
setSpeed(_s: number): void { /* no-op */ }
|
|
|
|
|
|
getSpeed(): number { return 1; }
|
|
|
|
|
|
loadHex(_hex: string): void { /* no-op */ }
|
|
|
|
|
|
loadBinary(_b64: string): void { /* no-op */ }
|
2026-03-23 04:03:17 +07:00
|
|
|
|
|
|
|
|
|
|
// ── Generic sensor registration (board-agnostic API) ──────────────────────
|
|
|
|
|
|
// ESP32 delegates sensor protocols to the backend QEMU.
|
|
|
|
|
|
|
|
|
|
|
|
registerSensor(type: string, pin: number, properties: Record<string, unknown>): boolean {
|
|
|
|
|
|
this.bridge.sendSensorAttach(type, pin, properties);
|
|
|
|
|
|
return true; // backend handles the protocol
|
|
|
|
|
|
}
|
|
|
|
|
|
updateSensor(pin: number, properties: Record<string, unknown>): void {
|
|
|
|
|
|
this.bridge.sendSensorUpdate(pin, properties);
|
|
|
|
|
|
}
|
|
|
|
|
|
unregisterSensor(pin: number): void {
|
|
|
|
|
|
this.bridge.sendSensorDetach(pin);
|
|
|
|
|
|
}
|
2026-04-08 01:43:09 +07:00
|
|
|
|
|
|
|
|
|
|
// ── I2C write-only device relay (SSD1306, PCF8574) ───────────────────────
|
|
|
|
|
|
private _i2cTransactionListeners = new Map<number, (data: number[]) => void>();
|
|
|
|
|
|
|
|
|
|
|
|
addI2CTransactionListener(addr: number, fn: (data: number[]) => void): void {
|
|
|
|
|
|
this._i2cTransactionListeners.set(addr, fn);
|
|
|
|
|
|
this.bridge.onI2cTransaction = (a: number, data: number[]) => {
|
|
|
|
|
|
this._i2cTransactionListeners.get(a)?.(data);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
removeI2CTransactionListener(addr: number): void {
|
|
|
|
|
|
this._i2cTransactionListeners.delete(addr);
|
|
|
|
|
|
if (this._i2cTransactionListeners.size === 0) {
|
|
|
|
|
|
this.bridge.onI2cTransaction = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-22 12:25:58 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 23:54:44 +07:00
|
|
|
|
// ── Shared LEDC update handler (used by addBoard, setBoardType, initSimulator) ─
|
|
|
|
|
|
function makeLedcUpdateHandler(boardId: string) {
|
|
|
|
|
|
return (update: { channel: number; duty_pct: number; gpio?: number }) => {
|
|
|
|
|
|
const boardPm = pinManagerMap.get(boardId);
|
|
|
|
|
|
if (!boardPm) return;
|
|
|
|
|
|
const dutyCycle = update.duty_pct / 100;
|
|
|
|
|
|
if (update.gpio !== undefined && update.gpio >= 0) {
|
|
|
|
|
|
boardPm.updatePwm(update.gpio, dutyCycle);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// gpio unknown (QEMU doesn't expose gpio_out_sel for LEDC):
|
|
|
|
|
|
// broadcast to ALL PWM listeners. Components filter by duty range
|
|
|
|
|
|
// (servo accepts 0.01–0.20, LEDs use 0–1.0).
|
|
|
|
|
|
boardPm.broadcastPwm(dutyCycle);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Runtime Maps (outside Zustand — not serialisable) ─────────────────────
|
2026-03-22 12:25:58 +07:00
|
|
|
|
const simulatorMap = new Map<string, AVRSimulator | RP2040Simulator | RiscVSimulator | Esp32C3Simulator | Esp32BridgeShim>();
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const pinManagerMap = new Map<string, PinManager>();
|
|
|
|
|
|
const bridgeMap = new Map<string, RaspberryPi3Bridge>();
|
2026-03-14 06:35:48 +07:00
|
|
|
|
const esp32BridgeMap = new Map<string, Esp32Bridge>();
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
|
|
|
|
|
export const getBoardSimulator = (id: string) => simulatorMap.get(id);
|
|
|
|
|
|
export const getBoardPinManager = (id: string) => pinManagerMap.get(id);
|
|
|
|
|
|
export const getBoardBridge = (id: string) => bridgeMap.get(id);
|
2026-03-14 06:35:48 +07:00
|
|
|
|
export const getEsp32Bridge = (id: string) => esp32BridgeMap.get(id);
|
|
|
|
|
|
|
2026-03-19 09:30:45 +07:00
|
|
|
|
// Xtensa-based ESP32 boards — use QEMU bridge (backend)
|
2026-03-16 01:39:26 +07:00
|
|
|
|
const ESP32_KINDS = new Set<BoardKind>([
|
|
|
|
|
|
'esp32', 'esp32-devkit-c-v4', 'esp32-cam', 'wemos-lolin32-lite',
|
|
|
|
|
|
'esp32-s3', 'xiao-esp32-s3', 'arduino-nano-esp32',
|
2026-03-16 04:40:08 +07:00
|
|
|
|
]);
|
|
|
|
|
|
|
2026-03-19 09:30:45 +07:00
|
|
|
|
// RISC-V ESP32 boards — also use QEMU bridge (qemu-system-riscv32 -M esp32c3)
|
|
|
|
|
|
// The browser-side Esp32C3Simulator cannot handle the 150+ ROM functions ESP-IDF needs.
|
2026-03-16 04:40:08 +07:00
|
|
|
|
const ESP32_RISCV_KINDS = new Set<BoardKind>([
|
2026-03-16 01:39:26 +07:00
|
|
|
|
'esp32-c3', 'xiao-esp32-c3', 'aitewinrobot-esp32c3-supermini',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
function isEsp32Kind(kind: BoardKind): boolean {
|
2026-03-19 09:30:45 +07:00
|
|
|
|
return ESP32_KINDS.has(kind) || ESP32_RISCV_KINDS.has(kind);
|
2026-03-14 06:35:48 +07:00
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
2026-03-16 04:40:08 +07:00
|
|
|
|
function isRiscVEsp32Kind(kind: BoardKind): boolean {
|
|
|
|
|
|
return ESP32_RISCV_KINDS.has(kind);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Component type ────────────────────────────────────────────────────────
|
2026-03-03 10:20:49 +07:00
|
|
|
|
interface Component {
|
|
|
|
|
|
id: string;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
metadataId: string;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
x: number;
|
|
|
|
|
|
y: number;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
properties: Record<string, unknown>;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Store interface ───────────────────────────────────────────────────────
|
2026-03-03 10:20:49 +07:00
|
|
|
|
interface SimulatorState {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Multi-board state ───────────────────────────────────────────────────
|
|
|
|
|
|
boards: BoardInstance[];
|
|
|
|
|
|
activeBoardId: string | null;
|
|
|
|
|
|
|
|
|
|
|
|
addBoard: (boardKind: BoardKind, x: number, y: number) => string;
|
|
|
|
|
|
removeBoard: (boardId: string) => void;
|
|
|
|
|
|
updateBoard: (boardId: string, updates: Partial<BoardInstance>) => void;
|
|
|
|
|
|
setBoardPosition: (pos: { x: number; y: number }, boardId?: string) => void;
|
|
|
|
|
|
setActiveBoardId: (boardId: string) => void;
|
|
|
|
|
|
compileBoardProgram: (boardId: string, program: string) => void;
|
2026-03-30 07:27:41 +07:00
|
|
|
|
loadMicroPythonProgram: (boardId: string, files: Array<{ name: string; content: string }>) => Promise<void>;
|
|
|
|
|
|
setBoardLanguageMode: (boardId: string, mode: LanguageMode) => void;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
startBoard: (boardId: string) => void;
|
|
|
|
|
|
stopBoard: (boardId: string) => void;
|
|
|
|
|
|
resetBoard: (boardId: string) => void;
|
|
|
|
|
|
|
|
|
|
|
|
// ── Legacy single-board API (reads/writes activeBoardId board) ───────────
|
|
|
|
|
|
/** @deprecated use boards[]/activeBoardId directly */
|
2026-03-05 05:28:33 +07:00
|
|
|
|
boardType: BoardType;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
/** @deprecated use boards[x].x/y */
|
2026-03-08 05:29:23 +07:00
|
|
|
|
boardPosition: { x: number; y: number };
|
2026-03-13 09:39:04 +07:00
|
|
|
|
/** @deprecated use getBoardSimulator(activeBoardId) */
|
2026-03-22 12:25:58 +07:00
|
|
|
|
simulator: AVRSimulator | RP2040Simulator | RiscVSimulator | Esp32C3Simulator | Esp32BridgeShim | null;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
/** @deprecated use getBoardPinManager(activeBoardId) */
|
2026-03-03 10:20:49 +07:00
|
|
|
|
pinManager: PinManager;
|
|
|
|
|
|
running: boolean;
|
|
|
|
|
|
compiledHex: string | null;
|
2026-03-09 12:31:04 +07:00
|
|
|
|
hexEpoch: number;
|
2026-03-05 16:56:14 +07:00
|
|
|
|
serialOutput: string;
|
2026-03-06 07:07:10 +07:00
|
|
|
|
serialBaudRate: number;
|
2026-03-05 16:56:14 +07:00
|
|
|
|
serialMonitorOpen: boolean;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
/** @deprecated use getBoardBridge(activeBoardId) */
|
2026-03-12 18:17:29 +07:00
|
|
|
|
remoteConnected: boolean;
|
|
|
|
|
|
remoteSocket: WebSocket | null;
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
setBoardType: (type: BoardType) => void;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
initSimulator: () => void;
|
|
|
|
|
|
loadHex: (hex: string) => void;
|
2026-03-05 05:28:33 +07:00
|
|
|
|
loadBinary: (base64: string) => void;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
startSimulation: () => void;
|
|
|
|
|
|
stopSimulation: () => void;
|
|
|
|
|
|
resetSimulation: () => void;
|
|
|
|
|
|
setCompiledHex: (hex: string) => void;
|
2026-03-05 05:28:33 +07:00
|
|
|
|
setCompiledBinary: (base64: string) => void;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
setRunning: (running: boolean) => void;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
connectRemoteSimulator: (clientId: string) => void;
|
|
|
|
|
|
disconnectRemoteSimulator: () => void;
|
|
|
|
|
|
sendRemotePinEvent: (pin: string, state: number) => void;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
|
2026-03-14 22:05:35 +07:00
|
|
|
|
// ── ESP32 crash notification ─────────────────────────────────────────────
|
|
|
|
|
|
esp32CrashBoardId: string | null;
|
|
|
|
|
|
dismissEsp32Crash: () => void;
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Components ──────────────────────────────────────────────────────────
|
|
|
|
|
|
components: Component[];
|
2026-03-03 10:20:49 +07:00
|
|
|
|
addComponent: (component: Component) => void;
|
|
|
|
|
|
removeComponent: (id: string) => void;
|
|
|
|
|
|
updateComponent: (id: string, updates: Partial<Component>) => void;
|
|
|
|
|
|
updateComponentState: (id: string, state: boolean) => void;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
handleComponentEvent: (componentId: string, eventName: string, data?: unknown) => void;
|
2026-03-04 08:25:52 +07:00
|
|
|
|
setComponents: (components: Component[]) => void;
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Wires ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
wires: Wire[];
|
|
|
|
|
|
selectedWireId: string | null;
|
|
|
|
|
|
wireInProgress: WireInProgress | null;
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
addWire: (wire: Wire) => void;
|
|
|
|
|
|
removeWire: (wireId: string) => void;
|
|
|
|
|
|
updateWire: (wireId: string, updates: Partial<Wire>) => void;
|
|
|
|
|
|
setSelectedWire: (wireId: string | null) => void;
|
2026-03-04 08:25:52 +07:00
|
|
|
|
setWires: (wires: Wire[]) => void;
|
2026-03-15 08:55:13 +07:00
|
|
|
|
startWireCreation: (endpoint: WireEndpoint, color: string) => void;
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
updateWireInProgress: (x: number, y: number) => void;
|
2026-03-15 08:55:13 +07:00
|
|
|
|
addWireWaypoint: (x: number, y: number) => void;
|
|
|
|
|
|
setWireInProgressColor: (color: string) => void;
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
finishWireCreation: (endpoint: WireEndpoint) => void;
|
|
|
|
|
|
cancelWireCreation: () => void;
|
|
|
|
|
|
updateWirePositions: (componentId: string) => void;
|
|
|
|
|
|
recalculateAllWirePositions: () => void;
|
2026-03-05 16:56:14 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Serial monitor ──────────────────────────────────────────────────────
|
2026-03-05 16:56:14 +07:00
|
|
|
|
toggleSerialMonitor: () => void;
|
|
|
|
|
|
serialWrite: (text: string) => void;
|
2026-03-16 00:04:01 +07:00
|
|
|
|
serialWriteToBoard: (boardId: string, text: string) => void;
|
2026-03-05 16:56:14 +07:00
|
|
|
|
clearSerialOutput: () => void;
|
2026-03-16 00:04:01 +07:00
|
|
|
|
clearBoardSerialOutput: (boardId: string) => void;
|
2026-03-03 10:20:49 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Helper: create a simulator for a given board kind ─────────────────────
|
|
|
|
|
|
function createSimulator(
|
|
|
|
|
|
boardKind: BoardKind,
|
|
|
|
|
|
pm: PinManager,
|
|
|
|
|
|
onSerial: (ch: string) => void,
|
|
|
|
|
|
onBaud: (baud: number) => void,
|
|
|
|
|
|
onPinTime: (pin: number, state: boolean, t: number) => void,
|
2026-03-16 04:40:08 +07:00
|
|
|
|
): AVRSimulator | RP2040Simulator | RiscVSimulator | Esp32C3Simulator {
|
|
|
|
|
|
let sim: AVRSimulator | RP2040Simulator | RiscVSimulator | Esp32C3Simulator;
|
2026-03-13 09:39:04 +07:00
|
|
|
|
if (boardKind === 'arduino-mega') {
|
|
|
|
|
|
sim = new AVRSimulator(pm, 'mega');
|
2026-03-16 01:39:26 +07:00
|
|
|
|
} else if (boardKind === 'attiny85') {
|
|
|
|
|
|
sim = new AVRSimulator(pm, 'tiny85');
|
|
|
|
|
|
} else if (boardKind === 'raspberry-pi-pico' || boardKind === 'pi-pico-w') {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
sim = new RP2040Simulator(pm);
|
2026-03-16 04:40:08 +07:00
|
|
|
|
} else if (isRiscVEsp32Kind(boardKind)) {
|
|
|
|
|
|
// ESP32-C3 / XIAO-C3 / C3 SuperMini — browser-side RV32IMC emulator
|
|
|
|
|
|
sim = new Esp32C3Simulator(pm);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
} else {
|
|
|
|
|
|
// arduino-uno, arduino-nano
|
|
|
|
|
|
sim = new AVRSimulator(pm, 'uno');
|
|
|
|
|
|
}
|
|
|
|
|
|
sim.onSerialData = onSerial;
|
|
|
|
|
|
if (sim instanceof AVRSimulator) sim.onBaudRateChange = onBaud;
|
|
|
|
|
|
sim.onPinChangeWithTime = onPinTime;
|
|
|
|
|
|
return sim;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Default initial board (Arduino Uno — same as old behaviour) ───────────
|
|
|
|
|
|
const INITIAL_BOARD_ID = 'arduino-uno';
|
|
|
|
|
|
const INITIAL_BOARD: BoardInstance = {
|
|
|
|
|
|
id: INITIAL_BOARD_ID,
|
|
|
|
|
|
boardKind: 'arduino-uno',
|
|
|
|
|
|
x: DEFAULT_BOARD_POSITION.x,
|
|
|
|
|
|
y: DEFAULT_BOARD_POSITION.y,
|
|
|
|
|
|
running: false,
|
|
|
|
|
|
compiledProgram: null,
|
|
|
|
|
|
serialOutput: '',
|
|
|
|
|
|
serialBaudRate: 0,
|
|
|
|
|
|
serialMonitorOpen: false,
|
|
|
|
|
|
activeFileGroupId: `group-${INITIAL_BOARD_ID}`,
|
2026-03-30 07:27:41 +07:00
|
|
|
|
languageMode: 'arduino' as LanguageMode,
|
2026-03-13 09:39:04 +07:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ── Store ─────────────────────────────────────────────────────────────────
|
2026-03-03 10:20:49 +07:00
|
|
|
|
export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// Initialise runtime objects for the default board
|
|
|
|
|
|
const initialPm = new PinManager();
|
|
|
|
|
|
pinManagerMap.set(INITIAL_BOARD_ID, initialPm);
|
2026-03-03 10:20:49 +07:00
|
|
|
|
|
2026-03-16 20:31:02 +07:00
|
|
|
|
function getOscilloscopeCallback(boardId: string) {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
return (pin: number, state: boolean, timeMs: number) => {
|
2026-03-11 22:09:24 +07:00
|
|
|
|
const { channels, pushSample } = useOscilloscopeStore.getState();
|
|
|
|
|
|
for (const ch of channels) {
|
2026-03-16 20:31:02 +07:00
|
|
|
|
if (ch.boardId === boardId && ch.pin === pin) pushSample(ch.id, timeMs, state);
|
2026-03-11 22:09:24 +07:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-03-12 18:17:29 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const initialSim = createSimulator(
|
|
|
|
|
|
'arduino-uno',
|
|
|
|
|
|
initialPm,
|
|
|
|
|
|
(ch) => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === INITIAL_BOARD_ID ? { ...b, serialOutput: b.serialOutput + ch } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === INITIAL_BOARD_ID;
|
|
|
|
|
|
return { boards, ...(isActive ? { serialOutput: s.serialOutput + ch } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
(baud) => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === INITIAL_BOARD_ID ? { ...b, serialBaudRate: baud } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === INITIAL_BOARD_ID;
|
|
|
|
|
|
return { boards, ...(isActive ? { serialBaudRate: baud } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2026-03-16 20:31:02 +07:00
|
|
|
|
getOscilloscopeCallback(INITIAL_BOARD_ID),
|
2026-03-13 09:39:04 +07:00
|
|
|
|
);
|
2026-03-16 00:04:01 +07:00
|
|
|
|
// Cross-board serial bridge for the initial board: AVR TX → Pi bridges RX
|
|
|
|
|
|
const initialOrigSerial = initialSim.onSerialData;
|
|
|
|
|
|
initialSim.onSerialData = (ch: string) => {
|
|
|
|
|
|
initialOrigSerial?.(ch);
|
|
|
|
|
|
get().boards.forEach((b) => {
|
|
|
|
|
|
const bridge = bridgeMap.get(b.id);
|
|
|
|
|
|
if (bridge) bridge.sendSerialBytes([ch.charCodeAt(0)]);
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-03-13 09:39:04 +07:00
|
|
|
|
simulatorMap.set(INITIAL_BOARD_ID, initialSim);
|
2026-03-12 18:17:29 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Legacy single-board PinManager (references initial board's pm) ───────
|
|
|
|
|
|
const legacyPinManager = initialPm;
|
2026-03-12 18:26:56 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
return {
|
|
|
|
|
|
// ── Multi-board state ─────────────────────────────────────────────────
|
|
|
|
|
|
boards: [INITIAL_BOARD],
|
|
|
|
|
|
activeBoardId: INITIAL_BOARD_ID,
|
|
|
|
|
|
|
|
|
|
|
|
addBoard: (boardKind: BoardKind, x: number, y: number) => {
|
|
|
|
|
|
const existing = get().boards.filter((b) => b.boardKind === boardKind);
|
|
|
|
|
|
const id = existing.length === 0
|
|
|
|
|
|
? boardKind
|
|
|
|
|
|
: `${boardKind}-${existing.length + 1}`;
|
|
|
|
|
|
|
|
|
|
|
|
const pm = new PinManager();
|
|
|
|
|
|
pinManagerMap.set(id, pm);
|
|
|
|
|
|
|
2026-03-14 06:35:48 +07:00
|
|
|
|
const serialCallback = (ch: string) => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === id ? { ...b, serialOutput: b.serialOutput + ch } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === id;
|
|
|
|
|
|
return { boards, ...(isActive ? { serialOutput: s.serialOutput + ch } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (boardKind === 'raspberry-pi-3') {
|
|
|
|
|
|
const bridge = new RaspberryPi3Bridge(id);
|
2026-03-16 00:04:01 +07:00
|
|
|
|
bridge.onSerialData = (ch: string) => {
|
|
|
|
|
|
serialCallback(ch);
|
|
|
|
|
|
// Cross-board serial bridge: Pi TX → all AVR simulators RX
|
|
|
|
|
|
get().boards.forEach((b) => {
|
|
|
|
|
|
const sim = simulatorMap.get(b.id);
|
2026-03-16 01:39:26 +07:00
|
|
|
|
if (sim instanceof AVRSimulator || sim instanceof RiscVSimulator) sim.serialWrite(ch);
|
2026-03-16 00:04:01 +07:00
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-03-14 06:35:48 +07:00
|
|
|
|
bridge.onPinChange = (_gpioPin, _state) => {
|
|
|
|
|
|
// Cross-board routing handled in SimulatorCanvas
|
|
|
|
|
|
};
|
|
|
|
|
|
bridgeMap.set(id, bridge);
|
|
|
|
|
|
} else if (isEsp32Kind(boardKind)) {
|
|
|
|
|
|
const bridge = new Esp32Bridge(id, boardKind);
|
|
|
|
|
|
bridge.onSerialData = serialCallback;
|
2026-03-14 22:05:35 +07:00
|
|
|
|
bridge.onPinChange = (gpioPin, state) => {
|
|
|
|
|
|
const boardPm = pinManagerMap.get(id);
|
|
|
|
|
|
if (boardPm) boardPm.triggerPinChange(gpioPin, state);
|
|
|
|
|
|
};
|
|
|
|
|
|
bridge.onCrash = () => {
|
|
|
|
|
|
set({ esp32CrashBoardId: id });
|
|
|
|
|
|
};
|
2026-03-17 12:28:08 +07:00
|
|
|
|
bridge.onDisconnected = () => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) => b.id === id ? { ...b, running: false } : b);
|
|
|
|
|
|
const isActive = s.activeBoardId === id;
|
|
|
|
|
|
return { boards, ...(isActive ? { running: false } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-03-24 23:54:44 +07:00
|
|
|
|
bridge.onLedcUpdate = makeLedcUpdateHandler(id);
|
2026-03-14 22:05:35 +07:00
|
|
|
|
bridge.onWs2812Update = (channel, pixels) => {
|
|
|
|
|
|
// Forward WS2812 pixel data to any DOM element with id=`ws2812-{id}-{channel}`
|
|
|
|
|
|
// (set by NeoPixel components rendered in SimulatorCanvas).
|
|
|
|
|
|
// We fire a custom event that NeoPixel components can listen to.
|
|
|
|
|
|
const eventTarget = document.getElementById(`ws2812-${id}-${channel}`);
|
|
|
|
|
|
if (eventTarget) {
|
|
|
|
|
|
eventTarget.dispatchEvent(
|
|
|
|
|
|
new CustomEvent('ws2812-pixels', { detail: { pixels } })
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-03-14 06:35:48 +07:00
|
|
|
|
};
|
2026-04-01 06:53:56 +07:00
|
|
|
|
bridge.onWifiStatus = (ws) => {
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
boards: s.boards.map((b) => b.id === id ? { ...b, wifiStatus: ws } : b),
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
|
|
|
|
|
bridge.onBleStatus = (bs) => {
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
boards: s.boards.map((b) => b.id === id ? { ...b, bleStatus: bs } : b),
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
2026-03-14 06:35:48 +07:00
|
|
|
|
esp32BridgeMap.set(id, bridge);
|
2026-03-22 12:25:58 +07:00
|
|
|
|
// Provide a shim so PartSimulationRegistry components (DHT22, etc.)
|
|
|
|
|
|
// can call setPinState / access pinManager on ESP32 boards.
|
|
|
|
|
|
const shim = new Esp32BridgeShim(bridge, pm);
|
|
|
|
|
|
shim.onSerialData = serialCallback;
|
|
|
|
|
|
simulatorMap.set(id, shim);
|
2026-03-14 06:35:48 +07:00
|
|
|
|
} else {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const sim = createSimulator(
|
|
|
|
|
|
boardKind,
|
|
|
|
|
|
pm,
|
2026-03-14 06:35:48 +07:00
|
|
|
|
serialCallback,
|
2026-03-13 09:39:04 +07:00
|
|
|
|
(baud) => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === id ? { ...b, serialBaudRate: baud } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === id;
|
|
|
|
|
|
return { boards, ...(isActive ? { serialBaudRate: baud } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2026-03-16 20:31:02 +07:00
|
|
|
|
getOscilloscopeCallback(id),
|
2026-03-13 09:39:04 +07:00
|
|
|
|
);
|
2026-03-16 00:04:01 +07:00
|
|
|
|
// Cross-board serial bridge: AVR TX → all Pi bridges RX
|
|
|
|
|
|
const origSerial = sim.onSerialData;
|
|
|
|
|
|
sim.onSerialData = (ch: string) => {
|
|
|
|
|
|
origSerial?.(ch);
|
|
|
|
|
|
get().boards.forEach((b) => {
|
|
|
|
|
|
const bridge = bridgeMap.get(b.id);
|
|
|
|
|
|
if (bridge) bridge.sendSerialBytes([ch.charCodeAt(0)]);
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-03-13 09:39:04 +07:00
|
|
|
|
simulatorMap.set(id, sim);
|
|
|
|
|
|
}
|
2026-03-12 18:26:56 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const newBoard: BoardInstance = {
|
|
|
|
|
|
id, boardKind, x, y,
|
|
|
|
|
|
running: false, compiledProgram: null,
|
|
|
|
|
|
serialOutput: '', serialBaudRate: 0,
|
|
|
|
|
|
serialMonitorOpen: false,
|
|
|
|
|
|
activeFileGroupId: `group-${id}`,
|
2026-03-30 07:27:41 +07:00
|
|
|
|
languageMode: 'arduino',
|
2026-03-12 18:17:29 +07:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
set((s) => ({ boards: [...s.boards, newBoard] }));
|
2026-03-14 06:35:48 +07:00
|
|
|
|
// Create the editor file group for this board
|
|
|
|
|
|
useEditorStore.getState().createFileGroup(`group-${id}`);
|
2026-03-16 00:04:01 +07:00
|
|
|
|
// Init VFS for Raspberry Pi 3 boards
|
|
|
|
|
|
if (boardKind === 'raspberry-pi-3') {
|
|
|
|
|
|
useVfsStore.getState().initBoardVfs(id);
|
|
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
return id;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
removeBoard: (boardId: string) => {
|
2026-03-29 11:55:10 +07:00
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
getBoardSimulator(boardId)?.stop();
|
|
|
|
|
|
simulatorMap.delete(boardId);
|
|
|
|
|
|
pinManagerMap.delete(boardId);
|
|
|
|
|
|
const bridge = getBoardBridge(boardId);
|
|
|
|
|
|
if (bridge) { bridge.disconnect(); bridgeMap.delete(boardId); }
|
2026-03-14 06:35:48 +07:00
|
|
|
|
const esp32Bridge = getEsp32Bridge(boardId);
|
|
|
|
|
|
if (esp32Bridge) { esp32Bridge.disconnect(); esp32BridgeMap.delete(boardId); }
|
2026-03-13 09:39:04 +07:00
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.filter((b) => b.id !== boardId);
|
|
|
|
|
|
const activeBoardId = s.activeBoardId === boardId
|
|
|
|
|
|
? (boards[0]?.id ?? null)
|
|
|
|
|
|
: s.activeBoardId;
|
2026-03-29 11:55:10 +07:00
|
|
|
|
// Remove wires connected to this board
|
|
|
|
|
|
const wires = s.wires.filter((w) =>
|
|
|
|
|
|
w.start.componentId !== boardId && w.end.componentId !== boardId
|
|
|
|
|
|
);
|
|
|
|
|
|
return { boards, activeBoardId, wires };
|
2026-03-13 09:39:04 +07:00
|
|
|
|
});
|
2026-03-29 11:55:10 +07:00
|
|
|
|
// Clean up file group in editor store
|
|
|
|
|
|
if (board) {
|
|
|
|
|
|
useEditorStore.getState().deleteFileGroup(board.activeFileGroupId);
|
|
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
},
|
2026-03-12 18:26:56 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
updateBoard: (boardId: string, updates: Partial<BoardInstance>) => {
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
boards: s.boards.map((b) => b.id === boardId ? { ...b, ...updates } : b),
|
|
|
|
|
|
}));
|
|
|
|
|
|
},
|
2026-03-12 18:26:56 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
setBoardPosition: (pos: { x: number; y: number }, boardId?: string) => {
|
|
|
|
|
|
const id = boardId ?? get().activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
boardPosition: s.activeBoardId === id ? pos : s.boardPosition,
|
|
|
|
|
|
boards: s.boards.map((b) => b.id === id ? { ...b, x: pos.x, y: pos.y } : b),
|
|
|
|
|
|
}));
|
|
|
|
|
|
},
|
2026-03-12 18:26:56 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
setActiveBoardId: (boardId: string) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
set({
|
|
|
|
|
|
activeBoardId: boardId,
|
|
|
|
|
|
// Sync legacy flat fields to this board's values
|
|
|
|
|
|
boardType: (board.boardKind === 'raspberry-pi-3' ? 'arduino-uno' : board.boardKind) as BoardType,
|
|
|
|
|
|
boardPosition: { x: board.x, y: board.y },
|
|
|
|
|
|
simulator: simulatorMap.get(boardId) ?? null,
|
|
|
|
|
|
pinManager: pinManagerMap.get(boardId) ?? legacyPinManager,
|
|
|
|
|
|
running: board.running,
|
|
|
|
|
|
compiledHex: board.compiledProgram,
|
|
|
|
|
|
serialOutput: board.serialOutput,
|
|
|
|
|
|
serialBaudRate: board.serialBaudRate,
|
|
|
|
|
|
serialMonitorOpen: board.serialMonitorOpen,
|
2026-03-14 06:35:48 +07:00
|
|
|
|
remoteConnected: (bridgeMap.get(boardId)?.connected ?? esp32BridgeMap.get(boardId)?.connected) ?? false,
|
2026-03-13 09:39:04 +07:00
|
|
|
|
remoteSocket: null,
|
|
|
|
|
|
});
|
2026-03-14 06:35:48 +07:00
|
|
|
|
// Switch the editor to this board's file group
|
|
|
|
|
|
useEditorStore.getState().setActiveGroup(board.activeFileGroupId);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
},
|
2026-03-12 18:26:56 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
compileBoardProgram: (boardId: string, program: string) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
|
2026-03-14 06:35:48 +07:00
|
|
|
|
if (isEsp32Kind(board.boardKind)) {
|
2026-04-01 06:53:56 +07:00
|
|
|
|
// All ESP32 boards (Xtensa + RISC-V C3): send firmware to QEMU via bridge.
|
|
|
|
|
|
// Note: isEsp32Kind() includes C3 boards, so they route through Esp32Bridge
|
|
|
|
|
|
// for full WiFi/BLE emulation via qemu-system-riscv32.
|
2026-03-14 06:35:48 +07:00
|
|
|
|
const esp32Bridge = getEsp32Bridge(boardId);
|
|
|
|
|
|
if (esp32Bridge) esp32Bridge.loadFirmware(program);
|
2026-03-16 04:40:08 +07:00
|
|
|
|
} else if (isRiscVEsp32Kind(board.boardKind)) {
|
2026-04-01 06:53:56 +07:00
|
|
|
|
// Fallback: browser-only RV32IMC emulation (no WiFi/BLE support).
|
|
|
|
|
|
// Currently unreachable because isEsp32Kind() above includes C3 boards.
|
2026-03-16 04:40:08 +07:00
|
|
|
|
const sim = getBoardSimulator(boardId);
|
|
|
|
|
|
if (sim instanceof Esp32C3Simulator) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
sim.loadFlashImage(program);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(`[Esp32C3Simulator] loadFlashImage failed for ${boardId}:`, err);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-14 06:35:48 +07:00
|
|
|
|
} else {
|
|
|
|
|
|
const sim = getBoardSimulator(boardId);
|
|
|
|
|
|
if (sim && board.boardKind !== 'raspberry-pi-3') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (sim instanceof AVRSimulator) {
|
|
|
|
|
|
sim.loadHex(program);
|
|
|
|
|
|
sim.addI2CDevice(new VirtualDS1307());
|
|
|
|
|
|
sim.addI2CDevice(new VirtualTempSensor());
|
|
|
|
|
|
sim.addI2CDevice(new I2CMemoryDevice(0x50));
|
|
|
|
|
|
} else if (sim instanceof RP2040Simulator) {
|
|
|
|
|
|
sim.loadBinary(program);
|
|
|
|
|
|
sim.addI2CDevice(new VirtualDS1307() as RP2040I2CDevice);
|
|
|
|
|
|
sim.addI2CDevice(new VirtualTempSensor() as RP2040I2CDevice);
|
|
|
|
|
|
sim.addI2CDevice(new I2CMemoryDevice(0x50) as RP2040I2CDevice);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(`compileBoardProgram(${boardId}):`, err);
|
|
|
|
|
|
return;
|
2026-03-12 18:17:29 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
}
|
2026-03-12 18:17:29 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, compiledProgram: program } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return {
|
|
|
|
|
|
boards,
|
|
|
|
|
|
...(isActive ? { compiledHex: program, hexEpoch: s.hexEpoch + 1 } : {}),
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
2026-03-12 18:17:29 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-30 07:27:41 +07:00
|
|
|
|
loadMicroPythonProgram: async (boardId: string, files: Array<{ name: string; content: string }>) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
if (!BOARD_SUPPORTS_MICROPYTHON.has(board.boardKind)) return;
|
|
|
|
|
|
|
2026-03-30 09:12:24 +07:00
|
|
|
|
if (isEsp32Kind(board.boardKind)) {
|
|
|
|
|
|
// ESP32 path: load MicroPython firmware via QEMU bridge, inject code via raw-paste REPL
|
2026-04-13 09:55:11 +07:00
|
|
|
|
const { getEsp32Firmware, padToFlashSize, uint8ArrayToBase64 } = await import('../simulation/Esp32MicroPythonLoader');
|
2026-03-30 09:12:24 +07:00
|
|
|
|
const esp32Bridge = getEsp32Bridge(boardId);
|
|
|
|
|
|
if (!esp32Bridge) return;
|
2026-03-30 07:27:41 +07:00
|
|
|
|
|
2026-03-30 09:12:24 +07:00
|
|
|
|
const firmware = await getEsp32Firmware(board.boardKind);
|
2026-04-13 09:55:11 +07:00
|
|
|
|
const b64 = uint8ArrayToBase64(padToFlashSize(firmware, board.boardKind));
|
2026-03-30 09:12:24 +07:00
|
|
|
|
esp32Bridge.loadFirmware(b64);
|
|
|
|
|
|
|
|
|
|
|
|
// Queue code injection for after REPL boots
|
|
|
|
|
|
const mainFile = files.find(f => f.name === 'main.py') ?? files[0];
|
|
|
|
|
|
if (mainFile) {
|
|
|
|
|
|
esp32Bridge.setPendingMicroPythonCode(mainFile.content);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// RP2040 path: load firmware + filesystem in browser
|
|
|
|
|
|
const sim = getBoardSimulator(boardId);
|
|
|
|
|
|
if (!(sim instanceof RP2040Simulator)) return;
|
|
|
|
|
|
await sim.loadMicroPython(files);
|
|
|
|
|
|
}
|
2026-03-30 07:27:41 +07:00
|
|
|
|
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, compiledProgram: 'micropython-loaded' } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return {
|
|
|
|
|
|
boards,
|
|
|
|
|
|
...(isActive ? { compiledHex: 'micropython-loaded', hexEpoch: s.hexEpoch + 1 } : {}),
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
setBoardLanguageMode: (boardId: string, mode: LanguageMode) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Only allow MicroPython for supported boards
|
|
|
|
|
|
if (mode === 'micropython' && !BOARD_SUPPORTS_MICROPYTHON.has(board.boardKind)) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Stop any running simulation
|
|
|
|
|
|
if (board.running) get().stopBoard(boardId);
|
|
|
|
|
|
|
|
|
|
|
|
// Clear compiled program since language changed
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
boards: s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, languageMode: mode, compiledProgram: null } : b
|
|
|
|
|
|
),
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2026-04-13 21:16:43 +07:00
|
|
|
|
// Replace file group with appropriate default files and activate it
|
2026-03-30 07:27:41 +07:00
|
|
|
|
const editorStore = useEditorStore.getState();
|
|
|
|
|
|
editorStore.deleteFileGroup(board.activeFileGroupId);
|
|
|
|
|
|
editorStore.createFileGroup(board.activeFileGroupId, mode);
|
2026-04-13 21:16:43 +07:00
|
|
|
|
editorStore.setActiveGroup(board.activeFileGroupId);
|
2026-03-30 07:27:41 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
startBoard: (boardId: string) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (board.boardKind === 'raspberry-pi-3') {
|
2026-03-14 06:35:48 +07:00
|
|
|
|
getBoardBridge(boardId)?.connect();
|
|
|
|
|
|
} else if (isEsp32Kind(board.boardKind)) {
|
2026-03-23 04:03:17 +07:00
|
|
|
|
// Pre-register sensors connected to this board so the QEMU worker
|
|
|
|
|
|
// has them ready before the firmware starts executing.
|
|
|
|
|
|
const esp32Bridge = getEsp32Bridge(boardId);
|
|
|
|
|
|
if (esp32Bridge) {
|
|
|
|
|
|
const { components, wires } = get();
|
|
|
|
|
|
const sensors: Array<Record<string, unknown>> = [];
|
|
|
|
|
|
for (const comp of components) {
|
|
|
|
|
|
const sensorDef = SENSOR_COMPONENT_MAP[comp.metadataId];
|
|
|
|
|
|
if (!sensorDef) continue;
|
|
|
|
|
|
// Find the wire connecting this component's data pin to the board
|
|
|
|
|
|
for (const w of wires) {
|
|
|
|
|
|
const compEndpoint = (w.start.componentId === comp.id && w.start.pinName === sensorDef.dataPinName)
|
|
|
|
|
|
? w.start : (w.end.componentId === comp.id && w.end.pinName === sensorDef.dataPinName)
|
|
|
|
|
|
? w.end : null;
|
|
|
|
|
|
if (!compEndpoint) continue;
|
|
|
|
|
|
const boardEndpoint = compEndpoint === w.start ? w.end : w.start;
|
|
|
|
|
|
if (!isBoardComponent(boardEndpoint.componentId)) continue;
|
|
|
|
|
|
// Resolve GPIO pin number
|
|
|
|
|
|
const gpioPin = boardPinToNumber(board.boardKind, boardEndpoint.pinName);
|
|
|
|
|
|
if (gpioPin === null || gpioPin < 0) continue;
|
|
|
|
|
|
// Collect sensor properties from the component
|
|
|
|
|
|
const props: Record<string, unknown> = {
|
|
|
|
|
|
sensor_type: sensorDef.sensorType,
|
|
|
|
|
|
pin: gpioPin,
|
|
|
|
|
|
};
|
|
|
|
|
|
for (const key of sensorDef.propertyKeys) {
|
|
|
|
|
|
const val = comp.properties[key];
|
|
|
|
|
|
if (val !== undefined) props[key] = typeof val === 'string' ? parseFloat(val) : val;
|
|
|
|
|
|
}
|
2026-03-23 04:39:09 +07:00
|
|
|
|
// Resolve extra pins (e.g. echo_pin for HC-SR04) from wires
|
|
|
|
|
|
if (sensorDef.extraPins) {
|
|
|
|
|
|
for (const [propName, compPinName] of Object.entries(sensorDef.extraPins)) {
|
|
|
|
|
|
for (const ew of wires) {
|
|
|
|
|
|
const epComp = (ew.start.componentId === comp.id && ew.start.pinName === compPinName)
|
|
|
|
|
|
? ew.start : (ew.end.componentId === comp.id && ew.end.pinName === compPinName)
|
|
|
|
|
|
? ew.end : null;
|
|
|
|
|
|
if (!epComp) continue;
|
|
|
|
|
|
const epBoard = epComp === ew.start ? ew.end : ew.start;
|
|
|
|
|
|
if (!isBoardComponent(epBoard.componentId)) continue;
|
|
|
|
|
|
const extraGpio = boardPinToNumber(board.boardKind, epBoard.pinName);
|
|
|
|
|
|
if (extraGpio !== null && extraGpio >= 0) {
|
|
|
|
|
|
props[propName] = extraGpio;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-23 04:03:17 +07:00
|
|
|
|
sensors.push(props);
|
|
|
|
|
|
break; // only one data pin per sensor
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-08 10:02:43 +07:00
|
|
|
|
|
|
|
|
|
|
// Pre-register I2C sensors (virtual pin = 200 + i2c_addr, no wire resolution needed)
|
|
|
|
|
|
for (const comp of components) {
|
|
|
|
|
|
const i2cDef = I2C_SENSOR_MAP[comp.metadataId];
|
|
|
|
|
|
if (!i2cDef) continue;
|
|
|
|
|
|
// Resolve I2C address from component property or use default
|
|
|
|
|
|
let addr = i2cDef.defaultAddr;
|
|
|
|
|
|
if (i2cDef.addrProp) {
|
|
|
|
|
|
const rawAddr = comp.properties[i2cDef.addrProp];
|
|
|
|
|
|
if (rawAddr !== undefined) {
|
2026-04-08 10:02:54 +07:00
|
|
|
|
if (i2cDef.addrIsBool) {
|
|
|
|
|
|
// Boolean flag (e.g. AD0 on MPU-6050): truthy → high address
|
|
|
|
|
|
if (rawAddr === true || rawAddr === 'true' || rawAddr === '1') {
|
|
|
|
|
|
addr = i2cDef.addrBoolHigh ?? i2cDef.defaultAddr;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const parsed = typeof rawAddr === 'string'
|
|
|
|
|
|
? (rawAddr.startsWith('0x') ? parseInt(rawAddr, 16) : parseInt(rawAddr, 10))
|
|
|
|
|
|
: Number(rawAddr);
|
|
|
|
|
|
if (!isNaN(parsed)) addr = parsed;
|
|
|
|
|
|
}
|
2026-04-08 10:02:43 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const virtualPin = 200 + addr;
|
|
|
|
|
|
const props: Record<string, unknown> = { sensor_type: i2cDef.sensorType, pin: virtualPin, addr };
|
|
|
|
|
|
for (const key of (i2cDef.propertyKeys ?? [])) {
|
|
|
|
|
|
const val = comp.properties[key];
|
|
|
|
|
|
if (val !== undefined) props[key] = typeof val === 'string' ? parseFloat(val) : val;
|
|
|
|
|
|
}
|
|
|
|
|
|
sensors.push(props);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 04:03:17 +07:00
|
|
|
|
esp32Bridge.setSensors(sensors);
|
2026-04-01 06:53:56 +07:00
|
|
|
|
|
2026-04-06 03:14:40 +07:00
|
|
|
|
// Use WiFi flag set by the compiler (most reliable — avoids stale file group issues).
|
|
|
|
|
|
// Fall back to scanning the active file group if the flag hasn't been set yet.
|
|
|
|
|
|
let hasWifi = board.hasWifi;
|
|
|
|
|
|
if (hasWifi === undefined) {
|
|
|
|
|
|
const editorState = useEditorStore.getState();
|
|
|
|
|
|
const rawFiles = editorState.fileGroups[board.activeFileGroupId];
|
|
|
|
|
|
const boardFiles = (rawFiles && rawFiles.length > 0) ? rawFiles : editorState.files;
|
|
|
|
|
|
hasWifi = boardFiles.some(f =>
|
|
|
|
|
|
f.content.includes('#include <WiFi.h>') ||
|
|
|
|
|
|
f.content.includes('#include <esp_wifi.h>') ||
|
|
|
|
|
|
f.content.includes('#include "WiFi.h"') ||
|
|
|
|
|
|
f.content.includes('WiFi.begin(')
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-04-01 06:53:56 +07:00
|
|
|
|
esp32Bridge.wifiEnabled = hasWifi;
|
|
|
|
|
|
|
2026-04-01 23:47:15 +07:00
|
|
|
|
// Ensure firmware is loaded into the bridge (handles page-refresh case
|
|
|
|
|
|
// where _pendingFirmware is lost but compiledProgram is still in store).
|
|
|
|
|
|
if (!esp32Bridge.hasFirmware() && board.compiledProgram) {
|
|
|
|
|
|
esp32Bridge.loadFirmware(board.compiledProgram);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 04:03:17 +07:00
|
|
|
|
esp32Bridge.connect();
|
|
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
} else {
|
2026-03-14 06:35:48 +07:00
|
|
|
|
getBoardSimulator(boardId)?.start();
|
2026-03-12 18:17:29 +07:00
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
2026-03-17 08:15:52 +07:00
|
|
|
|
b.id === boardId ? { ...b, running: true, serialMonitorOpen: true } : b
|
2026-03-13 09:39:04 +07:00
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
2026-03-17 08:15:52 +07:00
|
|
|
|
return { boards, ...(isActive ? { running: true, serialMonitorOpen: true } : {}) };
|
2026-03-13 09:39:04 +07:00
|
|
|
|
});
|
2026-03-12 18:17:29 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
stopBoard: (boardId: string) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (board.boardKind === 'raspberry-pi-3') {
|
|
|
|
|
|
getBoardBridge(boardId)?.disconnect();
|
2026-03-14 06:35:48 +07:00
|
|
|
|
} else if (isEsp32Kind(board.boardKind)) {
|
|
|
|
|
|
getEsp32Bridge(boardId)?.disconnect();
|
2026-03-13 09:39:04 +07:00
|
|
|
|
} else {
|
|
|
|
|
|
getBoardSimulator(boardId)?.stop();
|
2026-03-12 18:17:29 +07:00
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, running: false } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return { boards, ...(isActive ? { running: false } : {}) };
|
|
|
|
|
|
});
|
2026-03-12 18:17:29 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
resetBoard: (boardId: string) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
|
2026-03-14 08:55:37 +07:00
|
|
|
|
if (isEsp32Kind(board.boardKind)) {
|
|
|
|
|
|
// Reset ESP32: disconnect then reconnect the QEMU bridge
|
|
|
|
|
|
const esp32Bridge = getEsp32Bridge(boardId);
|
|
|
|
|
|
if (esp32Bridge?.connected) {
|
|
|
|
|
|
esp32Bridge.disconnect();
|
|
|
|
|
|
setTimeout(() => esp32Bridge.connect(), 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (board.boardKind !== 'raspberry-pi-3') {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const sim = getBoardSimulator(boardId);
|
|
|
|
|
|
if (sim) {
|
|
|
|
|
|
sim.reset();
|
|
|
|
|
|
// Re-wire serial callback after reset
|
|
|
|
|
|
sim.onSerialData = (ch) => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, serialOutput: b.serialOutput + ch } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return { boards, ...(isActive ? { serialOutput: s.serialOutput + ch } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
if (sim instanceof AVRSimulator) {
|
|
|
|
|
|
sim.onBaudRateChange = (baud) => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, serialBaudRate: baud } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return { boards, ...(isActive ? { serialBaudRate: baud } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, running: false, serialOutput: '', serialBaudRate: 0 } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return { boards, ...(isActive ? { running: false, serialOutput: '', serialBaudRate: 0 } : {}) };
|
|
|
|
|
|
});
|
2026-03-08 05:29:23 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Legacy single-board API ───────────────────────────────────────────
|
|
|
|
|
|
boardType: 'arduino-uno',
|
|
|
|
|
|
boardPosition: { ...DEFAULT_BOARD_POSITION },
|
|
|
|
|
|
simulator: initialSim,
|
|
|
|
|
|
pinManager: legacyPinManager,
|
|
|
|
|
|
running: false,
|
|
|
|
|
|
compiledHex: null,
|
|
|
|
|
|
hexEpoch: 0,
|
|
|
|
|
|
serialOutput: '',
|
|
|
|
|
|
serialBaudRate: 0,
|
|
|
|
|
|
serialMonitorOpen: false,
|
|
|
|
|
|
remoteConnected: false,
|
|
|
|
|
|
remoteSocket: null,
|
|
|
|
|
|
|
2026-03-14 22:05:35 +07:00
|
|
|
|
esp32CrashBoardId: null,
|
|
|
|
|
|
dismissEsp32Crash: () => set({ esp32CrashBoardId: null }),
|
|
|
|
|
|
|
2026-03-05 05:28:33 +07:00
|
|
|
|
setBoardType: (type: BoardType) => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId, running, stopSimulation } = get();
|
|
|
|
|
|
if (running) stopSimulation();
|
|
|
|
|
|
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
const pm = getBoardPinManager(boardId) ?? legacyPinManager;
|
|
|
|
|
|
|
2026-03-15 02:57:22 +07:00
|
|
|
|
// Stop and remove old simulator / bridge
|
2026-03-13 09:39:04 +07:00
|
|
|
|
getBoardSimulator(boardId)?.stop();
|
|
|
|
|
|
simulatorMap.delete(boardId);
|
2026-03-15 02:57:22 +07:00
|
|
|
|
getEsp32Bridge(boardId)?.disconnect();
|
|
|
|
|
|
esp32BridgeMap.delete(boardId);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
2026-03-15 02:57:22 +07:00
|
|
|
|
const serialCallback = (ch: string) => set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, serialOutput: b.serialOutput + ch } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
return { boards, serialOutput: s.serialOutput + ch };
|
|
|
|
|
|
});
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
2026-03-15 02:57:22 +07:00
|
|
|
|
if (isEsp32Kind(type as BoardKind)) {
|
|
|
|
|
|
// ESP32: use bridge, not AVR simulator
|
|
|
|
|
|
const bridge = new Esp32Bridge(boardId, type as BoardKind);
|
|
|
|
|
|
bridge.onSerialData = serialCallback;
|
|
|
|
|
|
bridge.onPinChange = (gpioPin, state) => {
|
|
|
|
|
|
const boardPm = pinManagerMap.get(boardId);
|
|
|
|
|
|
if (boardPm) boardPm.triggerPinChange(gpioPin, state);
|
|
|
|
|
|
};
|
|
|
|
|
|
bridge.onCrash = () => { set({ esp32CrashBoardId: boardId }); };
|
2026-03-17 12:28:08 +07:00
|
|
|
|
bridge.onDisconnected = () => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) => b.id === boardId ? { ...b, running: false } : b);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return { boards, ...(isActive ? { running: false } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-03-24 23:54:44 +07:00
|
|
|
|
bridge.onLedcUpdate = makeLedcUpdateHandler(boardId);
|
2026-03-15 02:57:22 +07:00
|
|
|
|
bridge.onWs2812Update = (channel, pixels) => {
|
|
|
|
|
|
const eventTarget = document.getElementById(`ws2812-${boardId}-${channel}`);
|
|
|
|
|
|
if (eventTarget) {
|
|
|
|
|
|
eventTarget.dispatchEvent(new CustomEvent('ws2812-pixels', { detail: { pixels } }));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
esp32BridgeMap.set(boardId, bridge);
|
2026-03-22 12:25:58 +07:00
|
|
|
|
const shim = new Esp32BridgeShim(bridge, pm);
|
|
|
|
|
|
shim.onSerialData = serialCallback;
|
|
|
|
|
|
simulatorMap.set(boardId, shim);
|
2026-03-15 02:57:22 +07:00
|
|
|
|
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
boardType: type,
|
2026-03-22 12:25:58 +07:00
|
|
|
|
simulator: shim as any,
|
2026-03-15 02:57:22 +07:00
|
|
|
|
compiledHex: null,
|
|
|
|
|
|
serialOutput: '',
|
|
|
|
|
|
serialBaudRate: 0,
|
|
|
|
|
|
boards: s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId
|
|
|
|
|
|
? { ...b, boardKind: type as BoardKind, compiledProgram: null, serialOutput: '', serialBaudRate: 0 }
|
|
|
|
|
|
: b
|
|
|
|
|
|
),
|
|
|
|
|
|
}));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const sim = createSimulator(
|
|
|
|
|
|
type as BoardKind,
|
|
|
|
|
|
pm,
|
|
|
|
|
|
serialCallback,
|
|
|
|
|
|
(baud) => set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, serialBaudRate: baud } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
return { boards, serialBaudRate: baud };
|
|
|
|
|
|
}),
|
|
|
|
|
|
getOscilloscopeCallback(),
|
|
|
|
|
|
);
|
|
|
|
|
|
simulatorMap.set(boardId, sim);
|
|
|
|
|
|
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
boardType: type,
|
|
|
|
|
|
simulator: sim,
|
|
|
|
|
|
compiledHex: null,
|
|
|
|
|
|
serialOutput: '',
|
|
|
|
|
|
serialBaudRate: 0,
|
|
|
|
|
|
boards: s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId
|
|
|
|
|
|
? { ...b, boardKind: type as BoardKind, compiledProgram: null, serialOutput: '', serialBaudRate: 0 }
|
|
|
|
|
|
: b
|
|
|
|
|
|
),
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
2026-03-05 05:28:33 +07:00
|
|
|
|
console.log(`Board switched to: ${type}`);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-03 10:20:49 +07:00
|
|
|
|
initSimulator: () => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { boardType, activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
const pm = getBoardPinManager(boardId) ?? legacyPinManager;
|
|
|
|
|
|
|
|
|
|
|
|
getBoardSimulator(boardId)?.stop();
|
|
|
|
|
|
simulatorMap.delete(boardId);
|
2026-03-23 01:17:44 +07:00
|
|
|
|
getEsp32Bridge(boardId)?.disconnect();
|
|
|
|
|
|
esp32BridgeMap.delete(boardId);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
2026-03-23 01:17:44 +07:00
|
|
|
|
const serialCallback = (ch: string) => set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, serialOutput: b.serialOutput + ch } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
return { boards, serialOutput: s.serialOutput + ch };
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (isEsp32Kind(boardType as BoardKind)) {
|
|
|
|
|
|
// ESP32: create bridge + shim (same as setBoardType)
|
|
|
|
|
|
const bridge = new Esp32Bridge(boardId, boardType as BoardKind);
|
|
|
|
|
|
bridge.onSerialData = serialCallback;
|
|
|
|
|
|
bridge.onPinChange = (gpioPin, state) => {
|
|
|
|
|
|
const boardPm = pinManagerMap.get(boardId);
|
|
|
|
|
|
if (boardPm) boardPm.triggerPinChange(gpioPin, state);
|
|
|
|
|
|
};
|
|
|
|
|
|
bridge.onCrash = () => { set({ esp32CrashBoardId: boardId }); };
|
|
|
|
|
|
bridge.onDisconnected = () => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) => b.id === boardId ? { ...b, running: false } : b);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return { boards, ...(isActive ? { running: false } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-03-24 23:54:44 +07:00
|
|
|
|
bridge.onLedcUpdate = makeLedcUpdateHandler(boardId);
|
2026-03-25 00:38:08 +07:00
|
|
|
|
bridge.onWs2812Update = (channel, pixels) => {
|
|
|
|
|
|
const eventTarget = document.getElementById(`ws2812-${boardId}-${channel}`);
|
|
|
|
|
|
if (eventTarget) {
|
|
|
|
|
|
eventTarget.dispatchEvent(new CustomEvent('ws2812-pixels', { detail: { pixels } }));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-03-23 01:17:44 +07:00
|
|
|
|
esp32BridgeMap.set(boardId, bridge);
|
|
|
|
|
|
const shim = new Esp32BridgeShim(bridge, pm);
|
|
|
|
|
|
shim.onSerialData = serialCallback;
|
|
|
|
|
|
simulatorMap.set(boardId, shim);
|
|
|
|
|
|
set({ simulator: shim as any, serialOutput: '', serialBaudRate: 0 });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const sim = createSimulator(
|
|
|
|
|
|
boardType as BoardKind,
|
|
|
|
|
|
pm,
|
|
|
|
|
|
serialCallback,
|
|
|
|
|
|
(baud) => set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, serialBaudRate: baud } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
return { boards, serialBaudRate: baud };
|
|
|
|
|
|
}),
|
|
|
|
|
|
getOscilloscopeCallback(),
|
|
|
|
|
|
);
|
|
|
|
|
|
simulatorMap.set(boardId, sim);
|
|
|
|
|
|
set({ simulator: sim, serialOutput: '', serialBaudRate: 0 });
|
|
|
|
|
|
}
|
2026-03-05 05:28:33 +07:00
|
|
|
|
console.log(`Simulator initialized: ${boardType}`);
|
2026-03-03 10:20:49 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
loadHex: (hex: string) => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
const sim = getBoardSimulator(boardId);
|
|
|
|
|
|
if (sim && sim instanceof AVRSimulator) {
|
2026-03-03 10:20:49 +07:00
|
|
|
|
try {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
sim.loadHex(hex);
|
|
|
|
|
|
sim.addI2CDevice(new VirtualDS1307());
|
|
|
|
|
|
sim.addI2CDevice(new VirtualTempSensor());
|
|
|
|
|
|
sim.addI2CDevice(new I2CMemoryDevice(0x50));
|
2026-03-09 12:31:04 +07:00
|
|
|
|
set((s) => ({ compiledHex: hex, hexEpoch: s.hexEpoch + 1 }));
|
2026-03-03 10:20:49 +07:00
|
|
|
|
console.log('HEX file loaded successfully');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to load HEX:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-03-05 05:28:33 +07:00
|
|
|
|
console.warn('loadHex: simulator not initialized or wrong board type');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
loadBinary: (base64: string) => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
const sim = getBoardSimulator(boardId);
|
|
|
|
|
|
if (sim && sim instanceof RP2040Simulator) {
|
2026-03-05 05:28:33 +07:00
|
|
|
|
try {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
sim.loadBinary(base64);
|
|
|
|
|
|
sim.addI2CDevice(new VirtualDS1307() as RP2040I2CDevice);
|
|
|
|
|
|
sim.addI2CDevice(new VirtualTempSensor() as RP2040I2CDevice);
|
|
|
|
|
|
sim.addI2CDevice(new I2CMemoryDevice(0x50) as RP2040I2CDevice);
|
2026-03-09 12:31:04 +07:00
|
|
|
|
set((s) => ({ compiledHex: base64, hexEpoch: s.hexEpoch + 1 }));
|
2026-03-05 05:28:33 +07:00
|
|
|
|
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');
|
2026-03-03 10:20:49 +07:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
startSimulation: () => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
get().startBoard(boardId);
|
2026-03-03 10:20:49 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
stopSimulation: () => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
get().stopBoard(boardId);
|
2026-03-03 10:20:49 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
resetSimulation: () => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
get().resetBoard(boardId);
|
2026-03-03 10:20:49 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
setCompiledHex: (hex: string) => {
|
|
|
|
|
|
set({ compiledHex: hex });
|
|
|
|
|
|
get().loadHex(hex);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-05 05:28:33 +07:00
|
|
|
|
setCompiledBinary: (base64: string) => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
set({ compiledHex: base64 });
|
2026-03-05 05:28:33 +07:00
|
|
|
|
get().loadBinary(base64);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-03 10:20:49 +07:00
|
|
|
|
setRunning: (running: boolean) => set({ running }),
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
connectRemoteSimulator: (clientId: string) => {
|
|
|
|
|
|
// Legacy: connect a Pi bridge for the given clientId
|
|
|
|
|
|
const boardId = clientId;
|
|
|
|
|
|
let bridge = getBoardBridge(boardId);
|
|
|
|
|
|
if (!bridge) {
|
|
|
|
|
|
bridge = new RaspberryPi3Bridge(boardId);
|
|
|
|
|
|
bridge.onSerialData = (ch) => {
|
|
|
|
|
|
set((s) => {
|
|
|
|
|
|
const boards = s.boards.map((b) =>
|
|
|
|
|
|
b.id === boardId ? { ...b, serialOutput: b.serialOutput + ch } : b
|
|
|
|
|
|
);
|
|
|
|
|
|
const isActive = s.activeBoardId === boardId;
|
|
|
|
|
|
return { boards, ...(isActive ? { serialOutput: s.serialOutput + ch } : {}) };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
bridge.onPinChange = (gpioPin, state) => {
|
|
|
|
|
|
const { wires } = get();
|
|
|
|
|
|
const sim = getBoardSimulator(get().activeBoardId ?? INITIAL_BOARD_ID);
|
|
|
|
|
|
if (!sim) return;
|
|
|
|
|
|
const wire = wires.find(w =>
|
|
|
|
|
|
(w.start.componentId.includes('raspberry-pi') && w.start.pinName === String(gpioPin)) ||
|
|
|
|
|
|
(w.end.componentId.includes('raspberry-pi') && w.end.pinName === String(gpioPin))
|
|
|
|
|
|
);
|
|
|
|
|
|
if (wire) {
|
|
|
|
|
|
const isArduinoStart = !wire.start.componentId.includes('raspberry-pi');
|
|
|
|
|
|
const targetEndpoint = isArduinoStart ? wire.start : wire.end;
|
|
|
|
|
|
const pinNum = parseInt(targetEndpoint.pinName, 10);
|
|
|
|
|
|
if (!isNaN(pinNum)) sim.setPinState(pinNum, state);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
bridgeMap.set(boardId, bridge);
|
|
|
|
|
|
}
|
|
|
|
|
|
bridge.connect();
|
|
|
|
|
|
set({ remoteConnected: true });
|
2026-03-03 10:20:49 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
disconnectRemoteSimulator: () => {
|
|
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
getBoardBridge(boardId)?.disconnect();
|
|
|
|
|
|
set({ remoteConnected: false, remoteSocket: null });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
sendRemotePinEvent: (pin: string, state: number) => {
|
|
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
getBoardBridge(boardId)?.sendPinEvent(parseInt(pin, 10), state === 1);
|
2026-03-03 10:20:49 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// ── Components ────────────────────────────────────────────────────────
|
|
|
|
|
|
components: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'led-builtin',
|
|
|
|
|
|
metadataId: 'led',
|
|
|
|
|
|
x: 350,
|
|
|
|
|
|
y: 100,
|
2026-04-07 23:27:54 +07:00
|
|
|
|
properties: { color: 'red' },
|
2026-03-13 09:39:04 +07:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
|
|
wires: [
|
|
|
|
|
|
{
|
2026-04-07 23:27:54 +07:00
|
|
|
|
id: 'wire-builtin-anode',
|
|
|
|
|
|
start: { componentId: 'arduino-uno', pinName: '13', x: 0, y: 0 },
|
2026-03-13 09:39:04 +07:00
|
|
|
|
end: { componentId: 'led-builtin', pinName: 'A', x: 0, y: 0 },
|
2026-03-15 08:55:13 +07:00
|
|
|
|
waypoints: [],
|
2026-04-07 23:27:54 +07:00
|
|
|
|
color: '#22c55e',
|
2026-03-13 09:39:04 +07:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-04-07 23:27:54 +07:00
|
|
|
|
id: 'wire-builtin-cathode',
|
|
|
|
|
|
start: { componentId: 'arduino-uno', pinName: 'GND.1', x: 0, y: 0 },
|
2026-03-13 09:39:04 +07:00
|
|
|
|
end: { componentId: 'led-builtin', pinName: 'C', x: 0, y: 0 },
|
2026-03-15 08:55:13 +07:00
|
|
|
|
waypoints: [],
|
2026-04-07 23:27:54 +07:00
|
|
|
|
color: '#000000',
|
2026-03-13 09:39:04 +07:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
selectedWireId: null,
|
|
|
|
|
|
wireInProgress: null,
|
|
|
|
|
|
|
|
|
|
|
|
addComponent: (component) => set((state) => ({ components: [...state.components, component] })),
|
|
|
|
|
|
|
|
|
|
|
|
removeComponent: (id) => set((state) => ({
|
|
|
|
|
|
components: state.components.filter((c) => c.id !== id),
|
|
|
|
|
|
wires: state.wires.filter((w) => w.start.componentId !== id && w.end.componentId !== id),
|
|
|
|
|
|
})),
|
|
|
|
|
|
|
2026-03-03 10:20:49 +07:00
|
|
|
|
updateComponent: (id, updates) => {
|
|
|
|
|
|
set((state) => ({
|
2026-03-13 09:39:04 +07:00
|
|
|
|
components: state.components.map((c) => c.id === id ? { ...c, ...updates } : c),
|
2026-03-03 10:20:49 +07:00
|
|
|
|
}));
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
if (updates.x !== undefined || updates.y !== undefined) {
|
|
|
|
|
|
get().updateWirePositions(id);
|
|
|
|
|
|
}
|
2026-03-03 10:20:49 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
updateComponentState: (id, state) => {
|
|
|
|
|
|
set((prevState) => ({
|
|
|
|
|
|
components: prevState.components.map((c) =>
|
2026-03-04 23:36:33 +07:00
|
|
|
|
c.id === id ? { ...c, properties: { ...c.properties, state, value: state } } : c
|
2026-03-03 10:20:49 +07:00
|
|
|
|
),
|
|
|
|
|
|
}));
|
|
|
|
|
|
},
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
handleComponentEvent: (_componentId, _eventName, _data) => {},
|
2026-03-04 23:36:33 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
setComponents: (components) => set({ components }),
|
2026-03-04 08:25:52 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
addWire: (wire) => set((state) => ({ wires: [...state.wires, wire] })),
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
removeWire: (wireId) => set((state) => ({
|
|
|
|
|
|
wires: state.wires.filter((w) => w.id !== wireId),
|
|
|
|
|
|
selectedWireId: state.selectedWireId === wireId ? null : state.selectedWireId,
|
|
|
|
|
|
})),
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
updateWire: (wireId, updates) => set((state) => ({
|
|
|
|
|
|
wires: state.wires.map((w) => w.id === wireId ? { ...w, ...updates } : w),
|
|
|
|
|
|
})),
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
setSelectedWire: (wireId) => set({ selectedWireId: wireId }),
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-15 08:55:13 +07:00
|
|
|
|
setWires: (wires) => set({
|
|
|
|
|
|
// Ensure every wire has waypoints (backwards-compatible with saved projects)
|
|
|
|
|
|
wires: wires.map((w) => ({ waypoints: [], ...w })),
|
|
|
|
|
|
}),
|
2026-03-04 08:25:52 +07:00
|
|
|
|
|
2026-03-15 08:55:13 +07:00
|
|
|
|
startWireCreation: (endpoint, color) => set({
|
|
|
|
|
|
wireInProgress: {
|
|
|
|
|
|
startEndpoint: endpoint,
|
|
|
|
|
|
waypoints: [],
|
|
|
|
|
|
color,
|
|
|
|
|
|
currentX: endpoint.x,
|
|
|
|
|
|
currentY: endpoint.y,
|
|
|
|
|
|
},
|
2026-03-13 09:39:04 +07:00
|
|
|
|
}),
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
updateWireInProgress: (x, y) => set((state) => {
|
|
|
|
|
|
if (!state.wireInProgress) return state;
|
|
|
|
|
|
return { wireInProgress: { ...state.wireInProgress, currentX: x, currentY: y } };
|
|
|
|
|
|
}),
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
2026-03-15 08:55:13 +07:00
|
|
|
|
addWireWaypoint: (x, y) => set((state) => {
|
|
|
|
|
|
if (!state.wireInProgress) return state;
|
|
|
|
|
|
return {
|
|
|
|
|
|
wireInProgress: {
|
|
|
|
|
|
...state.wireInProgress,
|
|
|
|
|
|
waypoints: [...state.wireInProgress.waypoints, { x, y }],
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
|
|
setWireInProgressColor: (color) => set((state) => {
|
|
|
|
|
|
if (!state.wireInProgress) return state;
|
|
|
|
|
|
return { wireInProgress: { ...state.wireInProgress, color } };
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
finishWireCreation: (endpoint) => {
|
|
|
|
|
|
const state = get();
|
|
|
|
|
|
if (!state.wireInProgress) return;
|
2026-03-15 08:55:13 +07:00
|
|
|
|
const { startEndpoint, waypoints, color } = state.wireInProgress;
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
const newWire: Wire = {
|
|
|
|
|
|
id: `wire-${Date.now()}`,
|
|
|
|
|
|
start: startEndpoint,
|
|
|
|
|
|
end: endpoint,
|
2026-03-15 08:55:13 +07:00
|
|
|
|
waypoints,
|
|
|
|
|
|
color,
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
};
|
2026-03-13 09:39:04 +07:00
|
|
|
|
set((state) => ({ wires: [...state.wires, newWire], wireInProgress: null }));
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
cancelWireCreation: () => set({ wireInProgress: null }),
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
|
|
|
|
|
updateWirePositions: (componentId) => {
|
|
|
|
|
|
set((state) => {
|
|
|
|
|
|
const component = state.components.find((c) => c.id === componentId);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
// Check if this componentId matches a board id
|
|
|
|
|
|
const board = state.boards.find((b) => b.id === componentId);
|
2026-03-16 20:31:02 +07:00
|
|
|
|
// Components have a DynamicComponent wrapper with border:2px + padding:4px → offset (4,6)
|
|
|
|
|
|
// Boards are rendered directly without a wrapper, so no offset.
|
|
|
|
|
|
const compX = component ? component.x + 4 : (board ? board.x : state.boardPosition.x);
|
|
|
|
|
|
const compY = component ? component.y + 6 : (board ? board.y : state.boardPosition.y);
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
|
|
|
|
|
|
const updatedWires = state.wires.map((wire) => {
|
|
|
|
|
|
const updated = { ...wire };
|
|
|
|
|
|
if (wire.start.componentId === componentId) {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const pos = calculatePinPosition(componentId, wire.start.pinName, compX, compY);
|
|
|
|
|
|
if (pos) updated.start = { ...wire.start, x: pos.x, y: pos.y };
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
}
|
|
|
|
|
|
if (wire.end.componentId === componentId) {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const pos = calculatePinPosition(componentId, wire.end.pinName, compX, compY);
|
|
|
|
|
|
if (pos) updated.end = { ...wire.end, x: pos.x, y: pos.y };
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
}
|
|
|
|
|
|
return updated;
|
|
|
|
|
|
});
|
|
|
|
|
|
return { wires: updatedWires };
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
recalculateAllWirePositions: () => {
|
|
|
|
|
|
const state = get();
|
|
|
|
|
|
const updatedWires = state.wires.map((wire) => {
|
|
|
|
|
|
const updated = { ...wire };
|
|
|
|
|
|
|
2026-03-16 20:31:02 +07:00
|
|
|
|
// Resolve start — components have wrapper offset (4,6), boards do not
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const startComp = state.components.find((c) => c.id === wire.start.componentId);
|
|
|
|
|
|
const startBoard = state.boards.find((b) => b.id === wire.start.componentId);
|
2026-03-16 20:31:02 +07:00
|
|
|
|
const startX = startComp ? startComp.x + 4 : (startBoard ? startBoard.x : state.boardPosition.x);
|
|
|
|
|
|
const startY = startComp ? startComp.y + 6 : (startBoard ? startBoard.y : state.boardPosition.y);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const startPos = calculatePinPosition(wire.start.componentId, wire.start.pinName, startX, startY);
|
|
|
|
|
|
updated.start = startPos
|
|
|
|
|
|
? { ...wire.start, x: startPos.x, y: startPos.y }
|
|
|
|
|
|
: { ...wire.start, x: startX, y: startY };
|
|
|
|
|
|
|
2026-03-16 20:31:02 +07:00
|
|
|
|
// Resolve end — components have wrapper offset (4,6), boards do not
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
const endComp = state.components.find((c) => c.id === wire.end.componentId);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const endBoard = state.boards.find((b) => b.id === wire.end.componentId);
|
2026-03-16 20:31:02 +07:00
|
|
|
|
const endX = endComp ? endComp.x + 4 : (endBoard ? endBoard.x : state.boardPosition.x);
|
|
|
|
|
|
const endY = endComp ? endComp.y + 6 : (endBoard ? endBoard.y : state.boardPosition.y);
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const endPos = calculatePinPosition(wire.end.componentId, wire.end.pinName, endX, endY);
|
|
|
|
|
|
updated.end = endPos
|
|
|
|
|
|
? { ...wire.end, x: endPos.x, y: endPos.y }
|
|
|
|
|
|
: { ...wire.end, x: endX, y: endY };
|
|
|
|
|
|
|
Add backend and frontend test suites for Arduino compilation and simulation
- Implemented a comprehensive backend test suite in `test_compilation.py` to validate the Arduino CLI installation, AVR core presence, compilation service, and API endpoint functionality.
- Created a frontend test suite in `simulation.test.ts` to test the `PinManager` and `AVRSimulator` components, ensuring proper functionality and integration.
- Introduced new components for wire management in the simulator, including `PinOverlay`, `WireInProgressRenderer`, `WireLayer`, and `WireRenderer`, enhancing the visual wiring system.
- Developed utility functions for pin position calculations and wire color management, ensuring accurate connections and visual representation.
- Added types for wire management in `wire.ts`, defining structures for wire endpoints, control points, and signal types.
2026-03-04 02:44:07 +07:00
|
|
|
|
return updated;
|
|
|
|
|
|
});
|
|
|
|
|
|
set({ wires: updatedWires });
|
|
|
|
|
|
},
|
2026-03-05 16:56:14 +07:00
|
|
|
|
|
2026-03-13 09:39:04 +07:00
|
|
|
|
toggleSerialMonitor: () => set((s) => ({ serialMonitorOpen: !s.serialMonitorOpen })),
|
2026-03-05 16:56:14 +07:00
|
|
|
|
|
|
|
|
|
|
serialWrite: (text: string) => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (board.boardKind === 'raspberry-pi-3') {
|
|
|
|
|
|
const bridge = getBoardBridge(boardId);
|
|
|
|
|
|
if (bridge) {
|
|
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
|
|
|
|
bridge.sendSerialByte(text.charCodeAt(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-14 08:55:37 +07:00
|
|
|
|
} else if (isEsp32Kind(board.boardKind)) {
|
|
|
|
|
|
const esp32Bridge = getEsp32Bridge(boardId);
|
|
|
|
|
|
if (esp32Bridge) {
|
|
|
|
|
|
esp32Bridge.sendSerialBytes(Array.from(new TextEncoder().encode(text)));
|
|
|
|
|
|
}
|
2026-03-13 09:39:04 +07:00
|
|
|
|
} else {
|
|
|
|
|
|
getBoardSimulator(boardId)?.serialWrite(text);
|
2026-03-05 16:56:14 +07:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
clearSerialOutput: () => {
|
2026-03-13 09:39:04 +07:00
|
|
|
|
const { activeBoardId } = get();
|
|
|
|
|
|
const boardId = activeBoardId ?? INITIAL_BOARD_ID;
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
serialOutput: '',
|
|
|
|
|
|
boards: s.boards.map((b) => b.id === boardId ? { ...b, serialOutput: '' } : b),
|
|
|
|
|
|
}));
|
2026-03-05 16:56:14 +07:00
|
|
|
|
},
|
2026-03-16 00:04:01 +07:00
|
|
|
|
|
|
|
|
|
|
serialWriteToBoard: (boardId: string, text: string) => {
|
|
|
|
|
|
const board = get().boards.find((b) => b.id === boardId);
|
|
|
|
|
|
if (!board) return;
|
|
|
|
|
|
if (board.boardKind === 'raspberry-pi-3') {
|
|
|
|
|
|
const bridge = getBoardBridge(boardId);
|
|
|
|
|
|
if (bridge) {
|
|
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
|
|
|
|
bridge.sendSerialByte(text.charCodeAt(i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (isEsp32Kind(board.boardKind)) {
|
|
|
|
|
|
const esp32Bridge = getEsp32Bridge(boardId);
|
|
|
|
|
|
if (esp32Bridge) {
|
|
|
|
|
|
esp32Bridge.sendSerialBytes(Array.from(new TextEncoder().encode(text)));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
getBoardSimulator(boardId)?.serialWrite(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
clearBoardSerialOutput: (boardId: string) => {
|
|
|
|
|
|
const isActive = get().activeBoardId === boardId;
|
|
|
|
|
|
set((s) => ({
|
|
|
|
|
|
...(isActive ? { serialOutput: '' } : {}),
|
|
|
|
|
|
boards: s.boards.map((b) => b.id === boardId ? { ...b, serialOutput: '' } : b),
|
|
|
|
|
|
}));
|
|
|
|
|
|
},
|
2026-03-03 10:20:49 +07:00
|
|
|
|
};
|
|
|
|
|
|
});
|
2026-03-13 09:39:04 +07:00
|
|
|
|
|
|
|
|
|
|
// ── Helper: get the active board instance (convenience for consumers) ─────
|
|
|
|
|
|
export function getActiveBoard(): BoardInstance | null {
|
|
|
|
|
|
const { boards, activeBoardId } = useSimulatorStore.getState();
|
|
|
|
|
|
return boards.find((b) => b.id === activeBoardId) ?? null;
|
|
|
|
|
|
}
|