550 lines
21 KiB
TypeScript
550 lines
21 KiB
TypeScript
/**
|
|
* Two Arduino Unos — every protocol over the same shared bench
|
|
* ============================================================
|
|
*
|
|
* Comprehensive matrix of inter-board communication for two Arduino
|
|
* Unos. Each describe block covers one protocol so a regression on
|
|
* any single signaling path is localized.
|
|
*
|
|
* Pin map (Uno):
|
|
* USART0: D0(RX) / D1(TX)
|
|
* I2C: A4(SDA, AVR pin 18) / A5(SCL, AVR pin 19)
|
|
* SPI: D10(SS) / D11(MOSI) / D12(MISO) / D13(SCK)
|
|
* PWM-cap: D3, D5, D6, D9, D10, D11
|
|
* GPIO: D2..D13 (D0/D1 reserved for hardware Serial)
|
|
*
|
|
* Test convention: trigger the source PinManager directly (which is
|
|
* exactly what AVRSimulator does internally on PORT writes) and assert
|
|
* the destination simulator's setPinState / serialWrite / feedUart was
|
|
* called along the registered route.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
|
vi.mock('../simulation/AVRSimulator', () => ({
|
|
AVRSimulator: vi.fn(function (this: any) {
|
|
this.onSerialData = null;
|
|
this.onBaudRateChange = null;
|
|
this.onPinChangeWithTime = null;
|
|
this.start = vi.fn();
|
|
this.stop = vi.fn();
|
|
this.reset = vi.fn();
|
|
this.loadHex = vi.fn();
|
|
this.serialWrite = vi.fn();
|
|
this.feedUart = vi.fn();
|
|
this.addI2CDevice = vi.fn();
|
|
this.setPinState = vi.fn();
|
|
}),
|
|
}));
|
|
vi.mock('../simulation/RP2040Simulator', () => ({
|
|
RP2040Simulator: vi.fn(function (this: any) {
|
|
this.onSerialData = null;
|
|
this.onUartByte = null;
|
|
this.onPinChangeWithTime = null;
|
|
this.start = vi.fn();
|
|
this.stop = vi.fn();
|
|
this.reset = vi.fn();
|
|
this.loadBinary = vi.fn();
|
|
this.serialWrite = vi.fn();
|
|
this.feedUart = vi.fn();
|
|
this.addI2CDevice = vi.fn();
|
|
this.setPinState = vi.fn();
|
|
this.attachCyw43 = vi.fn();
|
|
this.spi = { onByte: null, completeTransfer: vi.fn() };
|
|
}),
|
|
}));
|
|
vi.mock('../simulation/RiscVSimulator', () => ({
|
|
RiscVSimulator: vi.fn(function (this: any) {
|
|
this.onSerialData = null;
|
|
this.serialWrite = vi.fn();
|
|
this.feedUart = vi.fn();
|
|
this.start = vi.fn();
|
|
this.stop = vi.fn();
|
|
this.reset = vi.fn();
|
|
this.setPinState = vi.fn();
|
|
}),
|
|
}));
|
|
vi.mock('../simulation/Esp32C3Simulator', () => ({
|
|
Esp32C3Simulator: vi.fn(function (this: any) {
|
|
this.onSerialData = null;
|
|
this.serialWrite = vi.fn();
|
|
this.feedUart = vi.fn();
|
|
this.start = vi.fn();
|
|
this.stop = vi.fn();
|
|
this.reset = vi.fn();
|
|
this.setPinState = vi.fn();
|
|
}),
|
|
}));
|
|
vi.mock('../simulation/Esp32Bridge', () => ({
|
|
Esp32Bridge: vi.fn(function (this: any, _id: string, _kind: string) {
|
|
this.onSerialData = null;
|
|
this.onPinChange = null;
|
|
this.connect = vi.fn();
|
|
this.disconnect = vi.fn();
|
|
this.connected = true;
|
|
this.sendSerialBytes = vi.fn();
|
|
this.sendPinEvent = vi.fn();
|
|
}),
|
|
Esp32BridgeShim: vi.fn(function (this: any) {
|
|
this.onSerialData = null;
|
|
this.serialWrite = vi.fn();
|
|
this.feedUart = vi.fn();
|
|
this.setPinState = vi.fn();
|
|
}),
|
|
}));
|
|
vi.mock('../simulation/RaspberryPi3Bridge', () => ({
|
|
RaspberryPi3Bridge: vi.fn(function (this: any, _id: string) {
|
|
this.onSerialData = null;
|
|
this.onPinChange = null;
|
|
this.connect = vi.fn();
|
|
this.disconnect = vi.fn();
|
|
this.connected = true;
|
|
this.sendSerialBytes = vi.fn();
|
|
this.sendPinEvent = vi.fn();
|
|
}),
|
|
}));
|
|
vi.mock('../simulation/I2CBusManager', async () => {
|
|
const actual = await vi.importActual<typeof import('../simulation/I2CBusManager')>(
|
|
'../simulation/I2CBusManager',
|
|
);
|
|
return actual;
|
|
});
|
|
vi.mock('../store/useOscilloscopeStore', () => ({
|
|
useOscilloscopeStore: {
|
|
getState: vi.fn().mockReturnValue({ channels: [], pushSample: vi.fn() }),
|
|
},
|
|
}));
|
|
vi.stubGlobal('requestAnimationFrame', (_cb: FrameRequestCallback) => 1);
|
|
vi.stubGlobal('cancelAnimationFrame', vi.fn());
|
|
|
|
import { setWires, resetStore, clearAllPinManagerState } from './helpers/multiBoardSetup';
|
|
import { resetInterconnect } from '../simulation/Interconnect';
|
|
import {
|
|
useSimulatorStore,
|
|
getBoardSimulator,
|
|
getBoardPinManager,
|
|
} from '../store/useSimulatorStore';
|
|
|
|
function fullReset() {
|
|
clearAllPinManagerState(useSimulatorStore, getBoardPinManager);
|
|
resetInterconnect();
|
|
resetStore(useSimulatorStore);
|
|
}
|
|
|
|
// Helper that mirrors flexible UART assertion from other tests.
|
|
function uartFedWith(sim: any, expected: string, uart = 0): boolean {
|
|
return (
|
|
(sim.feedUart as any).mock.calls.some(
|
|
(c: any[]) => c[0] === uart && c[1] === expected,
|
|
) || (sim.serialWrite as any).mock.calls.some((c: any[]) => c[0] === expected)
|
|
);
|
|
}
|
|
|
|
function setupTwoUnos() {
|
|
const store = useSimulatorStore.getState();
|
|
const idA = 'arduino-uno';
|
|
const idB = store.addBoard('arduino-uno', 400, 100);
|
|
return { idA, idB };
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 1. Hardware UART (USART0 on D0/D1)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('Dual Arduino Uno — hardware UART (USART0)', () => {
|
|
beforeEach(() => fullReset());
|
|
|
|
it('A.D1(TX) → B.D0(RX) routes byte', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D1', toBoard: idB, toPin: 'D0' },
|
|
{ fromBoard: idB, fromPin: 'D1', toBoard: idA, toPin: 'D0' },
|
|
]);
|
|
const simA = getBoardSimulator(idA) as any;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
simA.onSerialData('X');
|
|
expect(uartFedWith(simB, 'X')).toBe(true);
|
|
});
|
|
|
|
it('full duplex — both A→B and B→A simultaneously', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D1', toBoard: idB, toPin: 'D0' },
|
|
{ fromBoard: idB, fromPin: 'D1', toBoard: idA, toPin: 'D0' },
|
|
]);
|
|
const simA = getBoardSimulator(idA) as any;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
simA.onSerialData('A');
|
|
simB.onSerialData('B');
|
|
expect(uartFedWith(simB, 'A')).toBe(true);
|
|
expect(uartFedWith(simA, 'B')).toBe(true);
|
|
});
|
|
|
|
it('long burst (printf-style line) preserves byte stream', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D1', toBoard: idB, toPin: 'D0' },
|
|
]);
|
|
const simA = getBoardSimulator(idA) as any;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
const line = 'Sensor=42.7\n';
|
|
for (const ch of line) simA.onSerialData(ch);
|
|
const fed = [
|
|
...(simB.feedUart as any).mock.calls.filter((c: any[]) => c[0] === 0).map((c: any[]) => c[1]),
|
|
...(simB.serialWrite as any).mock.calls.map((c: any[]) => c[0]),
|
|
].join('');
|
|
expect(fed).toContain('Sensor=42.7');
|
|
});
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 2. I2C (Wire library, A4=SDA, A5=SCL)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('Dual Arduino Uno — I2C (A4/A5)', () => {
|
|
beforeEach(() => fullReset());
|
|
|
|
function setupI2C() {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
// A4 (SDA) ↔ A4
|
|
{ fromBoard: idA, fromPin: 'A4', toBoard: idB, toPin: 'A4' },
|
|
// A5 (SCL) ↔ A5
|
|
{ fromBoard: idA, fromPin: 'A5', toBoard: idB, toPin: 'A5' },
|
|
{ fromBoard: idA, fromPin: 'GND', toBoard: idB, toPin: 'GND' },
|
|
]);
|
|
return { idA, idB };
|
|
}
|
|
|
|
it('SDA pulse from master propagates to slave (Wire.beginTransmission)', () => {
|
|
const { idA, idB } = setupI2C();
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
// Start condition: SDA goes LOW while SCL is HIGH
|
|
pmA.triggerPinChange(18, false); // A4 = AVR pin 18 (SDA)
|
|
expect(simB.setPinState).toHaveBeenCalledWith(18, false);
|
|
});
|
|
|
|
it('SCL clock pulses propagate to slave', () => {
|
|
const { idA, idB } = setupI2C();
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
// 8 clock pulses for one byte
|
|
for (let i = 0; i < 8; i++) {
|
|
pmA.triggerPinChange(19, true); // A5 = AVR pin 19 (SCL)
|
|
pmA.triggerPinChange(19, false);
|
|
}
|
|
const sclCalls = (simB.setPinState as any).mock.calls.filter(
|
|
(c: any[]) => c[0] === 19,
|
|
);
|
|
expect(sclCalls.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('slave ACK (slave pulls SDA low) reaches master', () => {
|
|
const { idA, idB } = setupI2C();
|
|
const pmB = getBoardPinManager(idB)!;
|
|
const simA = getBoardSimulator(idA) as any;
|
|
pmB.triggerPinChange(18, false); // slave acks
|
|
expect(simA.setPinState).toHaveBeenCalledWith(18, false);
|
|
});
|
|
|
|
it('I2C and UART can coexist on the same board pair', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'A4', toBoard: idB, toPin: 'A4' },
|
|
{ fromBoard: idA, fromPin: 'A5', toBoard: idB, toPin: 'A5' },
|
|
{ fromBoard: idA, fromPin: 'D1', toBoard: idB, toPin: 'D0' },
|
|
]);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simA = getBoardSimulator(idA) as any;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
|
|
// I2C
|
|
pmA.triggerPinChange(18, false);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(18, false);
|
|
|
|
// UART byte (independent of I2C activity)
|
|
simA.onSerialData('U');
|
|
expect(uartFedWith(simB, 'U')).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 3. SPI (master D10..D13)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('Dual Arduino Uno — SPI (D10..D13)', () => {
|
|
beforeEach(() => fullReset());
|
|
|
|
function setupSpi() {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D13', toBoard: idB, toPin: 'D13' }, // SCK
|
|
{ fromBoard: idA, fromPin: 'D11', toBoard: idB, toPin: 'D11' }, // MOSI
|
|
{ fromBoard: idB, fromPin: 'D12', toBoard: idA, toPin: 'D12' }, // MISO
|
|
{ fromBoard: idA, fromPin: 'D10', toBoard: idB, toPin: 'D10' }, // SS / CS
|
|
{ fromBoard: idA, fromPin: 'GND', toBoard: idB, toPin: 'GND' },
|
|
]);
|
|
return { idA, idB };
|
|
}
|
|
|
|
it('SS/CS goes LOW from master (D10) reaches slave', () => {
|
|
const { idA, idB } = setupSpi();
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
pmA.triggerPinChange(10, false);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(10, false);
|
|
});
|
|
|
|
it('SCK toggles propagate (clock signal)', () => {
|
|
const { idA, idB } = setupSpi();
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
pmA.triggerPinChange(13, true);
|
|
pmA.triggerPinChange(13, false);
|
|
pmA.triggerPinChange(13, true);
|
|
const sckCalls = (simB.setPinState as any).mock.calls.filter(
|
|
(c: any[]) => c[0] === 13,
|
|
);
|
|
expect(sckCalls.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('MOSI bit from master reaches slave', () => {
|
|
const { idA, idB } = setupSpi();
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
pmA.triggerPinChange(11, true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(11, true);
|
|
});
|
|
|
|
it('MISO bit from slave reaches master (reverse direction)', () => {
|
|
const { idA, idB } = setupSpi();
|
|
const pmB = getBoardPinManager(idB)!;
|
|
const simA = getBoardSimulator(idA) as any;
|
|
pmB.triggerPinChange(12, true);
|
|
expect(simA.setPinState).toHaveBeenCalledWith(12, true);
|
|
});
|
|
|
|
it('full SPI byte sequence (MOSI + 8 SCK pulses) reaches slave', () => {
|
|
const { idA, idB } = setupSpi();
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
|
|
// CS LOW
|
|
pmA.triggerPinChange(10, false);
|
|
// 8 bits of MOSI=0xA5 = 0b10100101, MSB-first
|
|
const bits = [true, false, true, false, false, true, false, true];
|
|
for (const bit of bits) {
|
|
pmA.triggerPinChange(11, bit);
|
|
pmA.triggerPinChange(13, true); // SCK rising edge — sample
|
|
pmA.triggerPinChange(13, false);
|
|
}
|
|
pmA.triggerPinChange(10, true); // CS HIGH
|
|
|
|
// Slave should have seen pin 10 (CS), 11 (MOSI), 13 (SCK) transitions
|
|
const csCount = (simB.setPinState as any).mock.calls.filter((c: any[]) => c[0] === 10).length;
|
|
const mosiCount = (simB.setPinState as any).mock.calls.filter((c: any[]) => c[0] === 11).length;
|
|
const sckCount = (simB.setPinState as any).mock.calls.filter((c: any[]) => c[0] === 13).length;
|
|
expect(csCount).toBeGreaterThanOrEqual(2); // LOW + HIGH
|
|
expect(mosiCount).toBeGreaterThanOrEqual(1);
|
|
expect(sckCount).toBeGreaterThanOrEqual(2);
|
|
});
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 4. SoftwareSerial (bit-bang on any digital pair)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('Dual Arduino Uno — SoftwareSerial', () => {
|
|
beforeEach(() => fullReset());
|
|
|
|
it('SoftwareSerial on D2/D3 propagates pin transitions both ways', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D3', toBoard: idB, toPin: 'D2' }, // A SS-TX → B SS-RX
|
|
{ fromBoard: idB, fromPin: 'D3', toBoard: idA, toPin: 'D2' }, // B SS-TX → A SS-RX
|
|
]);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const pmB = getBoardPinManager(idB)!;
|
|
const simA = getBoardSimulator(idA) as any;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
|
|
pmA.triggerPinChange(3, false); // A drives start bit
|
|
expect(simB.setPinState).toHaveBeenCalledWith(2, false);
|
|
|
|
pmB.triggerPinChange(3, false); // B drives start bit
|
|
expect(simA.setPinState).toHaveBeenCalledWith(2, false);
|
|
});
|
|
|
|
it('SoftwareSerial on alternate pair D8/D9 also works', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D9', toBoard: idB, toPin: 'D8' },
|
|
]);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
pmA.triggerPinChange(9, true);
|
|
pmA.triggerPinChange(9, false);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(8, true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(8, false);
|
|
});
|
|
|
|
it('two SoftwareSerial pairs on the same board pair are independent', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D2', toBoard: idB, toPin: 'D3' },
|
|
{ fromBoard: idA, fromPin: 'D4', toBoard: idB, toPin: 'D5' },
|
|
]);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
|
|
pmA.triggerPinChange(2, false);
|
|
pmA.triggerPinChange(4, true);
|
|
|
|
expect(simB.setPinState).toHaveBeenCalledWith(3, false);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(5, true);
|
|
// No cross-talk
|
|
const pin5False = (simB.setPinState as any).mock.calls.find(
|
|
(c: any[]) => c[0] === 5 && c[1] === false,
|
|
);
|
|
expect(pin5False).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 5. Raw digital pins (digitalWrite/digitalRead)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('Dual Arduino Uno — raw digital pins', () => {
|
|
beforeEach(() => fullReset());
|
|
|
|
it('every digital pin D2..D12 propagates independently', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
const pinsToWire = [2, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
|
setWires(
|
|
useSimulatorStore,
|
|
pinsToWire.map((p) => ({
|
|
fromBoard: idA,
|
|
fromPin: `D${p}`,
|
|
toBoard: idB,
|
|
toPin: `D${p}`,
|
|
})),
|
|
);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
|
|
for (const p of pinsToWire) pmA.triggerPinChange(p, true);
|
|
|
|
for (const p of pinsToWire) {
|
|
expect(simB.setPinState).toHaveBeenCalledWith(p, true);
|
|
}
|
|
});
|
|
|
|
it('analog-as-digital pin (A0 = D14) propagates', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'A0', toBoard: idB, toPin: 'A0' },
|
|
]);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
pmA.triggerPinChange(14, true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(14, true);
|
|
});
|
|
|
|
it('cross-pin wiring (A.D7 ↔ B.D11) carries the digital signal', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D7', toBoard: idB, toPin: 'D11' },
|
|
]);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
pmA.triggerPinChange(7, true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(11, true);
|
|
});
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 6. Concurrent multi-protocol activity on the same wire bench
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('Dual Arduino Uno — concurrent multi-protocol activity', () => {
|
|
beforeEach(() => fullReset());
|
|
|
|
it('UART + I2C + SPI + raw digital all work simultaneously', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
// UART
|
|
{ fromBoard: idA, fromPin: 'D1', toBoard: idB, toPin: 'D0' },
|
|
{ fromBoard: idB, fromPin: 'D1', toBoard: idA, toPin: 'D0' },
|
|
// I2C
|
|
{ fromBoard: idA, fromPin: 'A4', toBoard: idB, toPin: 'A4' },
|
|
{ fromBoard: idA, fromPin: 'A5', toBoard: idB, toPin: 'A5' },
|
|
// SPI
|
|
{ fromBoard: idA, fromPin: 'D13', toBoard: idB, toPin: 'D13' },
|
|
{ fromBoard: idA, fromPin: 'D11', toBoard: idB, toPin: 'D11' },
|
|
{ fromBoard: idB, fromPin: 'D12', toBoard: idA, toPin: 'D12' },
|
|
{ fromBoard: idA, fromPin: 'D10', toBoard: idB, toPin: 'D10' },
|
|
// Random digital
|
|
{ fromBoard: idA, fromPin: 'D7', toBoard: idB, toPin: 'D7' },
|
|
// Common ground
|
|
{ fromBoard: idA, fromPin: 'GND', toBoard: idB, toPin: 'GND' },
|
|
]);
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simA = getBoardSimulator(idA) as any;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
|
|
// UART
|
|
simA.onSerialData('Q');
|
|
// I2C
|
|
pmA.triggerPinChange(18, false);
|
|
pmA.triggerPinChange(19, true);
|
|
// SPI
|
|
pmA.triggerPinChange(10, false);
|
|
pmA.triggerPinChange(11, true);
|
|
pmA.triggerPinChange(13, true);
|
|
// Raw digital
|
|
pmA.triggerPinChange(7, true);
|
|
|
|
// All must reach B
|
|
expect(uartFedWith(simB, 'Q')).toBe(true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(18, false);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(19, true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(10, false);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(11, true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(13, true);
|
|
expect(simB.setPinState).toHaveBeenCalledWith(7, true);
|
|
});
|
|
|
|
it('removing an I2C wire stops just the I2C path, leaving UART intact', () => {
|
|
const { idA, idB } = setupTwoUnos();
|
|
setWires(useSimulatorStore, [
|
|
{ fromBoard: idA, fromPin: 'D1', toBoard: idB, toPin: 'D0' },
|
|
{ fromBoard: idA, fromPin: 'A4', toBoard: idB, toPin: 'A4' },
|
|
{ fromBoard: idA, fromPin: 'A5', toBoard: idB, toPin: 'A5' },
|
|
]);
|
|
|
|
// Drop I2C wires (keep UART)
|
|
useSimulatorStore.setState((s) => ({
|
|
wires: s.wires.filter(
|
|
(w) => !(w.start.pinName === 'A4' || w.start.pinName === 'A5'),
|
|
),
|
|
}));
|
|
|
|
const pmA = getBoardPinManager(idA)!;
|
|
const simA = getBoardSimulator(idA) as any;
|
|
const simB = getBoardSimulator(idB) as any;
|
|
|
|
pmA.triggerPinChange(18, false);
|
|
pmA.triggerPinChange(19, true);
|
|
simA.onSerialData('U');
|
|
|
|
// I2C pins NOT propagated
|
|
const i2cCalls = (simB.setPinState as any).mock.calls.filter(
|
|
(c: any[]) => c[0] === 18 || c[0] === 19,
|
|
);
|
|
expect(i2cCalls.length).toBe(0);
|
|
// UART still works
|
|
expect(uartFedWith(simB, 'U')).toBe(true);
|
|
});
|
|
});
|