Merge pull request #54 from davidmonterocrespo24/website

feat: enhance ESP32 integration tests and update landing page content…
pull/74/head
David Montero Crespo 2026-03-23 14:11:23 -03:00 committed by GitHub
commit f721ce107e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 30 additions and 22 deletions

View File

@ -314,10 +314,11 @@ describe('useSimulatorStore — ESP32 boards', () => {
); );
}); });
it('addBoard("esp32") creates an Esp32Bridge, not a simulator', () => { it('addBoard("esp32") creates an Esp32Bridge + a shim in simulatorMap', () => {
const { addBoard } = useSimulatorStore.getState(); const { addBoard } = useSimulatorStore.getState();
const id = addBoard('esp32', 300, 100); const id = addBoard('esp32', 300, 100);
expect(getBoardSimulator(id)).toBeUndefined(); // Esp32BridgeShim is stored in simulatorMap so PartSimulationRegistry components work
expect(getBoardSimulator(id)).toBeDefined();
expect(getEsp32Bridge(id)).toBeDefined(); expect(getEsp32Bridge(id)).toBeDefined();
expect(getEsp32Bridge(id)?.boardKind).toBe('esp32'); expect(getEsp32Bridge(id)?.boardKind).toBe('esp32');
}); });
@ -328,11 +329,13 @@ describe('useSimulatorStore — ESP32 boards', () => {
expect(getEsp32Bridge(id)?.boardKind).toBe('esp32-s3'); expect(getEsp32Bridge(id)?.boardKind).toBe('esp32-s3');
}); });
it('addBoard("esp32-c3") creates an Esp32C3Simulator, not an Esp32Bridge', () => { it('addBoard("esp32-c3") uses QEMU Esp32Bridge (full ESP-IDF support)', () => {
const { addBoard } = useSimulatorStore.getState(); const { addBoard } = useSimulatorStore.getState();
const id = addBoard('esp32-c3', 300, 100); const id = addBoard('esp32-c3', 300, 100);
// ESP32-C3 uses the browser-side RV32IMC emulator — no QEMU bridge // ESP32-C3 uses QEMU backend via Esp32Bridge for full ESP-IDF ROM compatibility
expect(getEsp32Bridge(id)).toBeUndefined(); expect(getEsp32Bridge(id)).toBeDefined();
expect(getEsp32Bridge(id)?.boardKind).toBe('esp32-c3');
// Esp32BridgeShim is also present in simulatorMap for component compatibility
expect(getBoardSimulator(id)).toBeDefined(); expect(getBoardSimulator(id)).toBeDefined();
}); });

View File

@ -687,30 +687,32 @@ describe('Servo — attachEvents', () => {
}); });
it('calculates 90° when OCR1A = ICR1/2 (servo midpoint)', () => { it('calculates 90° when OCR1A = ICR1/2 (servo midpoint)', () => {
// ICR1 = 20000 (50 Hz at prescaler 8, 16 MHz) // Real Arduino Servo.h: prescaler=8, 16 MHz → 0.5 µs/tick
// OCR1A = 10000 → pulseUs = 1000 + (10000/20000)*1000 = 1500 µs → 90° // ICR1 = 40000 ticks = 20 ms period (50 Hz)
// 90° midpoint: 1472 µs → OCR1A = 1472 / 0.5 = 2944
// pulseUs = (2944/40000)*20000 = 1472 µs → angle = (1472-544)/1856*180 = 90°
const logic = PartSimulationRegistry.get('servo')!; const logic = PartSimulationRegistry.get('servo')!;
const el = makeElement({ angle: -1 }); const el = makeElement({ angle: -1 });
const sim = makeSimulator(); const sim = makeSimulator();
// ICR1L=0x86, ICR1H=0x87; OCR1AL=0x88, OCR1AH=0x89 // ICR1L=0x86, ICR1H=0x87; OCR1AL=0x88, OCR1AH=0x89
sim.cpu.data[0x88] = 10000 & 0xFF; // OCR1AL sim.cpu.data[0x88] = 2944 & 0xFF; // OCR1AL
sim.cpu.data[0x89] = (10000 >> 8) & 0xFF; // OCR1AH sim.cpu.data[0x89] = (2944 >> 8) & 0xFF; // OCR1AH
sim.cpu.data[0x86] = 20000 & 0xFF; // ICR1L sim.cpu.data[0x86] = 40000 & 0xFF; // ICR1L
sim.cpu.data[0x87] = (20000 >> 8) & 0xFF; // ICR1H sim.cpu.data[0x87] = (40000 >> 8) & 0xFF; // ICR1H
logic.attachEvents!(el, sim as any, noPins); logic.attachEvents!(el, sim as any, noPins);
expect((el as any).angle).toBe(90); expect((el as any).angle).toBe(90);
}); });
it('calculates 0° when OCR1A = 0 (minimum pulse)', () => { it('calculates 0° when OCR1A = 0 (minimum pulse)', () => {
// OCR1A=0, ICR1=20000 → pulseUs = 1000 + 0 = 1000 µs → 0° // OCR1A=0 → pulseUs=0 → clamped to MIN_PULSE_US=544 µs → 0°
const logic = PartSimulationRegistry.get('servo')!; const logic = PartSimulationRegistry.get('servo')!;
const el = makeElement({ angle: -1 }); const el = makeElement({ angle: -1 });
const sim = makeSimulator(); const sim = makeSimulator();
sim.cpu.data[0x86] = 20000 & 0xFF; sim.cpu.data[0x86] = 40000 & 0xFF;
sim.cpu.data[0x87] = (20000 >> 8) & 0xFF; sim.cpu.data[0x87] = (40000 >> 8) & 0xFF;
// OCR1A = 0 (default) // OCR1A = 0 (default)
logic.attachEvents!(el, sim as any, noPins); logic.attachEvents!(el, sim as any, noPins);
@ -718,15 +720,16 @@ describe('Servo — attachEvents', () => {
}); });
it('calculates 180° when OCR1A = ICR1 (maximum pulse)', () => { it('calculates 180° when OCR1A = ICR1 (maximum pulse)', () => {
// OCR1A=20000, ICR1=20000 → pulseUs = 1000 + 1000 = 2000 µs → 180° // 180° maximum: 2400 µs → OCR1A = 2400 / 0.5 = 4800 (ICR1=40000, 50 Hz)
// pulseUs = (4800/40000)*20000 = 2400 µs → angle = (2400-544)/1856*180 = 180°
const logic = PartSimulationRegistry.get('servo')!; const logic = PartSimulationRegistry.get('servo')!;
const el = makeElement({ angle: -1 }); const el = makeElement({ angle: -1 });
const sim = makeSimulator(); const sim = makeSimulator();
sim.cpu.data[0x88] = 20000 & 0xFF; sim.cpu.data[0x88] = 4800 & 0xFF;
sim.cpu.data[0x89] = (20000 >> 8) & 0xFF; sim.cpu.data[0x89] = (4800 >> 8) & 0xFF;
sim.cpu.data[0x86] = 20000 & 0xFF; sim.cpu.data[0x86] = 40000 & 0xFF;
sim.cpu.data[0x87] = (20000 >> 8) & 0xFF; sim.cpu.data[0x87] = (40000 >> 8) & 0xFF;
logic.attachEvents!(el, sim as any, noPins); logic.attachEvents!(el, sim as any, noPins);
expect((el as any).angle).toBe(180); expect((el as any).angle).toBe(180);

View File

@ -450,9 +450,9 @@ export const LandingPage: React.FC = () => {
<section className="landing-hero"> <section className="landing-hero">
<div className="hero-left"> <div className="hero-left">
<h1 className="hero-title"> <h1 className="hero-title">
Simulate Arduino,<br /> Emulate Arduino,<br />
ESP32 &amp; Raspberry Pi.<br /> ESP32 &amp; Raspberry Pi.<br />
<span className="hero-accent">And 16 more boards in your browser..</span> <span className="hero-accent">in your browser.</span>
</h1> </h1>
<p className="hero-subtitle"> <p className="hero-subtitle">
Write code, compile, and run on 19 real boards Arduino Uno, ESP32, ESP32-C3, Write code, compile, and run on 19 real boards Arduino Uno, ESP32, ESP32-C3,

View File

@ -47,6 +47,8 @@ const API_BASE = (): string =>
/** Returns a stable UUID for this browser tab (persists across reloads, resets on new tab). */ /** Returns a stable UUID for this browser tab (persists across reloads, resets on new tab). */
function getTabSessionId(): string { function getTabSessionId(): string {
// sessionStorage is not available in Node/test environments
if (typeof sessionStorage === 'undefined') return crypto.randomUUID();
const KEY = 'velxio-tab-id'; const KEY = 'velxio-tab-id';
let id = sessionStorage.getItem(KEY); let id = sessionStorage.getItem(KEY);
if (!id) { if (!id) {
@ -187,7 +189,7 @@ export class Esp32Bridge {
}; };
socket.onclose = (ev) => { socket.onclose = (ev) => {
console.log(`[Esp32Bridge:${this.boardId}] WebSocket closed (code=${ev.code})`); console.log(`[Esp32Bridge:${this.boardId}] WebSocket closed (code=${ev?.code ?? '?'})`);
this._connected = false; this._connected = false;
this.socket = null; this.socket = null;
this.onDisconnected?.(); this.onDisconnected?.();