fix(uart): la Pi puede hablar por serie con la placa de al lado

Dos piezas que faltaban para que pi-to-arduino-led-control fuera algo
mas que un guion imprimiendo lo que "habria enviado".

1) classifyPin no reconocia los pads del header por su nombre. Se llaman
   GPIO14 / GPIO15 en el dibujo de la placa y en todos los cables de los
   ejemplos, pero solo se aceptaban numeros fisicos: parseInt('GPIO14')
   daba NaN, el pin no clasificaba como nada y el Interconnect nunca
   construia la ruta. Ahora se acepta el prefijo GPIO/BCM y la numeracion
   fisica sigue funcionando.

2) Seam de serie para placas que no tienen ni simulador ni bridge: el
   motor de navegador corre el Python de la Pi en la propia pestana.
   registerSerialSink(placa, fn) recibe los bytes que le llegan y
   feedBoardSerialOut(placa, ch) anuncia los que envia, que es lo que el
   enrutado por cables ya sabia repartir.
This commit is contained in:
David Montero Crespo 2026-07-29 07:40:13 +02:00
parent 8e1001740a
commit 9160624da4
3 changed files with 66 additions and 0 deletions

View File

@ -0,0 +1,27 @@
/**
* A Raspberry Pi header pad wired to an Arduino serial pin must be seen
* as a UART link.
*
* The pads are LABELLED 'GPIO14' / 'GPIO15' that is what the board art
* shows and what every example wire uses. `normalizePinName` only accepted
* physical pin numbers for the Pi, so parseInt('GPIO14') was NaN, the pin
* classified as nothing, and Interconnect never built the route: a Pi
* sending commands to an Arduino was talking into the void.
*/
import { describe, it, expect } from 'vitest';
import { classifyPin, isUartWire } from '../utils/boardProtocols';
describe('Raspberry Pi UART pins', () => {
it('classifies the BCM-named header pads', () => {
expect(classifyPin('raspberry-pi-3', 'GPIO14')).toEqual({ kind: 'uart-tx', uart: 0 });
expect(classifyPin('raspberry-pi-3', 'GPIO15')).toEqual({ kind: 'uart-rx', uart: 0 });
// Physical numbering keeps working (pin 8 = BCM14, pin 10 = BCM15).
expect(classifyPin('raspberry-pi-3', '8')).toEqual({ kind: 'uart-tx', uart: 0 });
expect(classifyPin('raspberry-pi-3', '10')).toEqual({ kind: 'uart-rx', uart: 0 });
});
it('recognises the Pi <-> Arduino cross-board wires', () => {
expect(isUartWire('raspberry-pi-3', 'GPIO14', 'arduino-uno', '0')).toBeTruthy();
expect(isUartWire('arduino-uno', '1', 'raspberry-pi-3', 'GPIO15')).toBeTruthy();
});
});

View File

@ -182,12 +182,44 @@ function pushPinState(boardId: string, pin: number, state: boolean): void {
}
}
// ── Serial seam for boards driven from outside the sim/bridge pair ───────
//
// A QEMU-Linux board can also run its Python in the tab (no WebSocket, no
// bridge object). Such a board still has UART wires on the canvas, so it
// needs both directions of the routing: a sink to receive bytes, and a way
// to announce the ones it sends. Both are plain callbacks — nothing here
// knows what is on the other end.
const serialSinks = new Map<string, (ch: string, uart: number) => void>();
/** Receive UART bytes addressed to this board. Returns an unregister fn. */
export function registerSerialSink(
boardId: string,
sink: (ch: string, uart: number) => void,
): () => void {
serialSinks.set(boardId, sink);
return () => {
if (serialSinks.get(boardId) === sink) serialSinks.delete(boardId);
};
}
/** Announce a UART byte this board just transmitted, so the wires route it. */
export function feedBoardSerialOut(boardId: string, ch: string, uart = 0): void {
const subs = boards.get(boardId)?.serialFanout.get(uart);
if (subs) for (const cb of subs) cb(ch);
}
/** Push a UART byte into the receiving board's UART RX. */
function pushSerialByte(boardId: string, ch: string, uart: number): void {
if (!runtime) return;
const entry = boards.get(boardId);
if (!entry) return;
const sink = serialSinks.get(boardId);
if (sink) {
sink(ch, uart);
return;
}
if (isBrowserSim(entry.kind)) {
const sim = runtime.getBoardSimulator(boardId);
// RP2040Simulator doesn't yet expose feedUart per-UART — fall back

View File

@ -191,6 +191,13 @@ function normalizePinName(boardKind: string, pinName: string): string | null {
boardKind.startsWith('raspberry-pi-4') ||
boardKind.startsWith('raspberry-pi-5')
) {
// The header pads are LABELLED 'GPIO14' (that is what the board art
// and every example wire use); only physical numbers were accepted
// here, so parseInt('GPIO14') was NaN and the pin classified as
// nothing at all — a Pi TX wired to an Arduino RX was never seen as a
// UART link, and the two boards could not talk.
const bcm = trimmed.match(/^(?:GPIO|BCM)(\d+)$/);
if (bcm) return bcm[1];
const phys = parseInt(trimmed, 10);
if (!isNaN(phys)) {
const bcm = PI3_PHYSICAL_TO_BCM[phys];