From fddc03aa60ffe20a42cbf8c527140cd09d6471d1 Mon Sep 17 00:00:00 2001 From: David Montero Date: Fri, 12 Jun 2026 08:29:03 +0200 Subject: [PATCH] fix(interconnect): classify Arduino Mega UART/I2C function-label pins Follow-up audit after the ESP32 fix: classifyPin() was run for every board against the protocol pin labels its element actually exposes. One real gap remained -- Arduino Mega. Its dedicated SDA/SCL pins are only labelled (not numbered), so I2C links drawn on them came back 'digital' and never bridged. Map every Mega function label (TX/RX, TX0-3/RX0-3, SDA/SCL) to its pin number. Audit result for the rest (added as board-protocols-audit.test.ts): - Arduino Uno/Nano, Pico/Pico-W, STM32 Blue Pill: already OK. - ESP32 / ESP32-C3: fixed earlier (esp32-uart-pin-classify). - Raspberry Pi 3/4/5: OK -- the element labels pins by physical number (1..40) which normalize to BCM, so no function-label gap exists there. --- .../__tests__/board-protocols-audit.test.ts | 73 +++++++++++++++++++ frontend/src/utils/boardProtocols.ts | 14 ++++ 2 files changed, 87 insertions(+) create mode 100644 frontend/src/__tests__/board-protocols-audit.test.ts diff --git a/frontend/src/__tests__/board-protocols-audit.test.ts b/frontend/src/__tests__/board-protocols-audit.test.ts new file mode 100644 index 00000000..9b7dbd55 --- /dev/null +++ b/frontend/src/__tests__/board-protocols-audit.test.ts @@ -0,0 +1,73 @@ +/** + * Cross-board audit: a wire drawn on a board's protocol pins must classify as + * UART/I2C/SPI (not raw digital) so the Interconnect installs the byte-level + * bridge. Tests every supported board against the pin labels its element + * actually exposes. + * + * Boards expose pins differently: + * - Arduino Uno/Nano: numbers + TX/RX/SDA/SCL labels. + * - Arduino Mega: numbers for UART, dedicated SDA/SCL labels for I2C. + * - ESP32 / ESP32-C3: TX0/RX0/TX2/RX2 labels + GPIO numbers. + * - Pico/Pico-W: GPn labels. + * - STM32 Blue Pill: PAn/PBn port labels. + * - Raspberry Pi 3/4/5: PHYSICAL pin numbers (1..40) -> BCM. + */ +import { describe, it, expect } from 'vitest'; +import { classifyPin } from '../utils/boardProtocols'; + +const notDigital = (bk: string, pin: string) => { + const r = classifyPin(bk, pin); + expect(r.kind, `${bk} pin ${pin} classified as ${r.kind}`).not.toBe('digital'); + return r; +}; + +describe('board protocol-pin classification (multi-board interconnect)', () => { + it('Arduino Uno/Nano: TX/RX/SDA/SCL', () => { + for (const bk of ['arduino-uno', 'arduino-nano']) { + expect(classifyPin(bk, 'TX')).toEqual({ kind: 'uart-tx', uart: 0 }); + expect(classifyPin(bk, 'RX')).toEqual({ kind: 'uart-rx', uart: 0 }); + expect(classifyPin(bk, 'SDA')).toEqual({ kind: 'i2c-sda', bus: 0 }); + expect(classifyPin(bk, 'SCL')).toEqual({ kind: 'i2c-scl', bus: 0 }); + } + }); + + it('Arduino Mega: 4 UARTs by label + dedicated SDA/SCL', () => { + expect(classifyPin('arduino-mega', 'TX1')).toEqual({ kind: 'uart-tx', uart: 1 }); + expect(classifyPin('arduino-mega', 'RX1')).toEqual({ kind: 'uart-rx', uart: 1 }); + expect(classifyPin('arduino-mega', 'TX2')).toEqual({ kind: 'uart-tx', uart: 2 }); + expect(classifyPin('arduino-mega', 'TX3')).toEqual({ kind: 'uart-tx', uart: 3 }); + expect(classifyPin('arduino-mega', 'SDA')).toEqual({ kind: 'i2c-sda', bus: 0 }); + expect(classifyPin('arduino-mega', 'SCL')).toEqual({ kind: 'i2c-scl', bus: 0 }); + }); + + it('ESP32 variants + C3: UART labels', () => { + for (const bk of ['esp32', 'esp32-devkit-c-v4', 'esp32-cam', 'esp32-s3']) { + notDigital(bk, 'TX0'); + notDigital(bk, 'RX0'); + notDigital(bk, 'TX2'); + notDigital(bk, 'RX2'); + } + expect(classifyPin('esp32-c3', 'TX')).toEqual({ kind: 'uart-tx', uart: 0 }); + expect(classifyPin('esp32-c3', 'SDA')).toEqual({ kind: 'i2c-sda', bus: 0 }); + }); + + it('Pico/Pico-W: GPn labels', () => { + for (const bk of ['raspberry-pi-pico', 'pi-pico-w']) { + notDigital(bk, 'GP0'); + notDigital(bk, 'GP1'); + } + }); + + it('STM32 Blue Pill: PAn USART labels', () => { + expect(classifyPin('stm32-bluepill', 'PA9')).toEqual({ kind: 'uart-tx', uart: 0 }); + expect(classifyPin('stm32-bluepill', 'PA10')).toEqual({ kind: 'uart-rx', uart: 0 }); + }); + + it('Raspberry Pi 3: physical pin numbers map to BCM protocol roles', () => { + // Physical 8/10 = BCM14/15 = UART0; physical 3/5 = BCM2/3 = I2C1. + expect(classifyPin('raspberry-pi-3', '8')).toEqual({ kind: 'uart-tx', uart: 0 }); + expect(classifyPin('raspberry-pi-3', '10')).toEqual({ kind: 'uart-rx', uart: 0 }); + expect(classifyPin('raspberry-pi-3', '3')).toEqual({ kind: 'i2c-sda', bus: 1 }); + expect(classifyPin('raspberry-pi-3', '5')).toEqual({ kind: 'i2c-scl', bus: 1 }); + }); +}); diff --git a/frontend/src/utils/boardProtocols.ts b/frontend/src/utils/boardProtocols.ts index e1bf91dc..4d3b68e0 100644 --- a/frontend/src/utils/boardProtocols.ts +++ b/frontend/src/utils/boardProtocols.ts @@ -236,6 +236,20 @@ function normalizePinName(boardKind: string, pinName: string): string | null { // wemos-lolin32-lite — works, mirroring tableFor(). esp32-c3 has its own pins. const isEsp32 = boardKind.startsWith('esp32'); const isEsp32C3 = boardKind.startsWith('esp32-c3'); + + // Arduino Mega exposes 4 hardware UARTs + I2C by silkscreen label. Its UART + // pins are also numbered (0/1, 18/19, 16/17, 14/15) and classify on those, + // but the dedicated SDA/SCL pins are ONLY labelled, so I2C links drawn on + // them never classified. Map every Mega function label here. + if (boardKind === 'arduino-mega') { + const mega: Record = { + TX: '1', RX: '0', TX0: '1', RX0: '0', + TX1: '18', RX1: '19', TX2: '16', RX2: '17', TX3: '14', RX3: '15', + SDA: '20', SCL: '21', + }; + if (mega[trimmed]) return mega[trimmed]; + } + if (trimmed === 'TX' || trimmed === 'TX0' || trimmed === 'TXD' || trimmed === 'TXD0') { if (boardKind === 'arduino-uno' || boardKind === 'arduino-nano') return '1'; if (boardKind === 'raspberry-pi-pico' || boardKind === 'pi-pico-w') return '0';