Merge pull request #234 from davidmonterocrespo24/fix/esp32-uart-pin-classify

fix(interconnect): classify ESP32 UART pin names so multi-board Serial works
This commit is contained in:
David Montero Crespo 2026-06-12 03:19:55 -03:00 committed by GitHub
commit d69897409a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 69 additions and 9 deletions

View File

@ -0,0 +1,44 @@
/**
* Regression: ESP32 UART pin names must classify as UART so the multi-board
* Interconnect bridges Serial between two ESP32s.
*
* Bug: a user wired two esp32-devkit-c-v4 boards TX2->RX2 (Serial2) and got no
* data on the receiver. classifyPin() returned 'digital' because:
* - the TX/RX aliases only matched boardKind === 'esp32' exactly (not the
* 'esp32-devkit-c-v4' / 'esp32-cam' / 'esp32-s3' variants), and
* - TX2/RX2 were not handled at all.
* So the wires were treated as raw digital and the byte-level UART shortcut was
* never installed.
*/
import { describe, it, expect } from 'vitest';
import { classifyPin, isUartWire } from '../utils/boardProtocols';
describe('ESP32 UART pin classification (multi-board Serial)', () => {
const variants = ['esp32', 'esp32-devkit-c-v4', 'esp32-cam', 'esp32-s3'];
for (const bk of variants) {
it(`${bk}: TX/RX -> UART0, TX2/RX2 -> UART2`, () => {
expect(classifyPin(bk, 'TX')).toEqual({ kind: 'uart-tx', uart: 0 });
expect(classifyPin(bk, 'RX')).toEqual({ kind: 'uart-rx', uart: 0 });
expect(classifyPin(bk, 'TX2')).toEqual({ kind: 'uart-tx', uart: 2 });
expect(classifyPin(bk, 'RX2')).toEqual({ kind: 'uart-rx', uart: 2 });
// GPIO-numbered pins keep working too.
expect(classifyPin(bk, 'GPIO17')).toEqual({ kind: 'uart-tx', uart: 2 });
expect(classifyPin(bk, '16')).toEqual({ kind: 'uart-rx', uart: 2 });
});
}
it('esp32-c3 uses its own UART0 pins (21/20)', () => {
expect(classifyPin('esp32-c3', 'TX')).toEqual({ kind: 'uart-tx', uart: 0 });
expect(classifyPin('esp32-c3', 'RX')).toEqual({ kind: 'uart-rx', uart: 0 });
// c3 has no UART2 — TX2 is not a UART pin.
expect(classifyPin('esp32-c3', 'TX2').kind).not.toBe('uart-tx');
});
it('a TX2 -> RX2 wire between two ESP32s is a UART link', () => {
expect(isUartWire('esp32-devkit-c-v4', 'TX2', 'esp32-devkit-c-v4', 'RX2')).toBeTruthy();
expect(isUartWire('esp32', 'TX', 'esp32', 'RX')).toBeTruthy();
// GPIO-labelled pins resolve to the same link.
expect(isUartWire('esp32-devkit-c-v4', 'GPIO17', 'esp32-devkit-c-v4', 'GPIO16')).toBeTruthy();
});
});

View File

@ -199,8 +199,11 @@ function normalizePinName(boardKind: string, pinName: string): string | null {
return null;
}
// GP-prefix: "GP10" → "10"
if (trimmed.startsWith('GP')) {
// GP-prefix: "GP10" → "10" (RP2040). Exclude "GPIO..." (ESP32) — that is
// handled below; without this guard `parseInt("IO17")` = NaN swallowed every
// GPIOnn pin into null, so ESP32 wires drawn on GPIO-labelled pins never
// classified as UART/I2C/SPI.
if (trimmed.startsWith('GP') && !trimmed.startsWith('GPIO')) {
const n = parseInt(trimmed.substring(2), 10);
return isNaN(n) ? null : String(n);
}
@ -226,26 +229,39 @@ function normalizePinName(boardKind: string, pinName: string): string | null {
return isNaN(n) ? null : String(n);
}
// TX/RX aliases — board-specific
if (trimmed === 'TX') {
// UART/I2C function-name aliases — board-specific. ESP32 silkscreens label
// pins by function (TX/RX = UART0, TX2/RX2 = UART2), so wires drawn against
// those labels must resolve to GPIO numbers. Use startsWith('esp32') (not an
// exact match) so every variant — esp32-devkit-c-v4, esp32-cam, esp32-s3,
// wemos-lolin32-lite — works, mirroring tableFor(). esp32-c3 has its own pins.
const isEsp32 = boardKind.startsWith('esp32');
const isEsp32C3 = boardKind.startsWith('esp32-c3');
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';
if (boardKind === 'esp32') return '1';
if (isEsp32C3) return '21';
if (isEsp32) return '1';
}
if (trimmed === 'RX') {
if (trimmed === 'RX' || trimmed === 'RX0' || trimmed === 'RXD' || trimmed === 'RXD0') {
if (boardKind === 'arduino-uno' || boardKind === 'arduino-nano') return '0';
if (boardKind === 'raspberry-pi-pico' || boardKind === 'pi-pico-w') return '1';
if (boardKind === 'esp32') return '3';
if (isEsp32C3) return '20';
if (isEsp32) return '3';
}
// ESP32 UART2 (Serial2) default pins: TX2 = GPIO17, RX2 = GPIO16. c3 has no UART2.
if ((trimmed === 'TX2' || trimmed === 'TXD2') && isEsp32 && !isEsp32C3) return '17';
if ((trimmed === 'RX2' || trimmed === 'RXD2') && isEsp32 && !isEsp32C3) return '16';
if (trimmed === 'SDA') {
if (boardKind === 'arduino-uno' || boardKind === 'arduino-nano') return '18';
if (boardKind === 'raspberry-pi-pico' || boardKind === 'pi-pico-w') return '4';
if (boardKind === 'esp32') return '21';
if (isEsp32C3) return '5';
if (isEsp32) return '21';
}
if (trimmed === 'SCL') {
if (boardKind === 'arduino-uno' || boardKind === 'arduino-nano') return '19';
if (boardKind === 'raspberry-pi-pico' || boardKind === 'pi-pico-w') return '5';
if (boardKind === 'esp32') return '22';
if (isEsp32C3) return '6';
if (isEsp32) return '22';
}
// STM32 port labels (PA9, PB12, PC13…) are used verbatim as table keys.