Merge pull request #54 from davidmonterocrespo24/website
feat: enhance ESP32 integration tests and update landing page content…pull/74/head
commit
f721ce107e
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 & Raspberry Pi.<br />
|
ESP32 & 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,
|
||||||
|
|
|
||||||
|
|
@ -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?.();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue