From cc3b67f72413995971dd7f48e311ce99ede93f3d Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Wed, 12 Aug 2026 15:19:30 +0200 Subject: [PATCH] feat(m5stack-core): header pins resolve, and PWM tones carry their real pitch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two faults from the same audit. Every wire from the Core's M-Bus header resolved to null: the kind does not start with 'esp32', so boardPinToNumber fell through every branch and a part wired to the header never saw a signal, with nothing anywhere saying why. A dedicated branch (like the Cardputer's) maps the bare-number pads, sends 5V/GND/3V3/BAT to -1, and three new tests pin it — the board had zero test coverage before. And LEDC events now carry the carrier frequency. The engines have always known it; LedcDuty just had no field for it, so a speaker listener could only ever play a canned note — the Core's piano spent its life on 660 Hz. The duty handler stashes it per pin in the PinManager (getPwmFreq), a side table rather than a new callback parameter so the existing two- and three-arg PWM listeners stay untouched. --- .../src/__tests__/board-pin-mapping.test.ts | 25 +++++++++++++++++++ frontend/src/utils/boardPinMapping.ts | 18 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/frontend/src/__tests__/board-pin-mapping.test.ts b/frontend/src/__tests__/board-pin-mapping.test.ts index 58a10268..d3ad2595 100644 --- a/frontend/src/__tests__/board-pin-mapping.test.ts +++ b/frontend/src/__tests__/board-pin-mapping.test.ts @@ -42,3 +42,28 @@ describe('boardPinToNumber — M5 Cardputer ADV header', () => { expect(boardPinToNumber('cardputer-adv', '99')).toBeNull(); }); }); + +describe('m5stack-core header pins', () => { + // The Core's pads sat unmapped for months: the kind does not start with + // 'esp32', so it fell through every branch and each wire from the M-Bus + // header resolved to null — a part wired to the header never saw a signal, + // silently. These pin the dedicated branch. + const CORE_GPIO = ['21', '22', '23', '19', '18', '3', '1', '16', '17', '2', '5', '25', '26', '35', '36', '12', '13', '15', '0', '34']; + + it('maps every M-Bus GPIO pad to its number', () => { + for (const name of CORE_GPIO) { + expect(boardPinToNumber('m5stack-core', name)).toBe(Number(name)); + } + }); + + it('maps the power pads (including BAT) to -1', () => { + for (const name of ['5V', 'GND', '3V3', 'BAT']) { + expect(boardPinToNumber('m5stack-core', name)).toBe(-1); + } + }); + + it('rejects out-of-range and non-pins', () => { + expect(boardPinToNumber('m5stack-core', '40')).toBeNull(); // classic tops out at 39 + expect(boardPinToNumber('m5stack-core', 'NOPE')).toBeNull(); + }); +}); diff --git a/frontend/src/utils/boardPinMapping.ts b/frontend/src/utils/boardPinMapping.ts index 12a0d06c..b3d836da 100644 --- a/frontend/src/utils/boardPinMapping.ts +++ b/frontend/src/utils/boardPinMapping.ts @@ -361,6 +361,24 @@ export function boardPinToNumber(boardId: string, pinName: string): number | nul return null; } + // M5Stack Core: same bare-GPIO-number pads, classic ESP32 range. Without + // this branch the kind fell through every one below — the shared ESP32 + // branch requires boardId.startsWith('esp32') — and every wire from the + // M-Bus header resolved to null: a part wired to the header never saw a + // signal, with nothing anywhere saying why. BAT joins the power names. + if (boardId === 'm5stack-core') { + if ( + pinName.startsWith('GND') || + pinName.startsWith('3V3') || + pinName.startsWith('5V') || + pinName === 'BAT' + ) + return -1; + const num = parseInt(pinName, 10); + if (!isNaN(num) && num >= 0 && num <= 39) return num; + return null; + } + // ESP32 / ESP32-S3 / ESP32-C3 — GPIO numbers used directly if (boardId === 'esp32' || boardId.startsWith('esp32')) { // Power / GND pins (GND, GND.1, 3V3, 3V3.1, 5V, 5V.1, etc.)