Hardware · ESP32-P4
An I²C and GPIO deck built on the M5Stack Tab5, for interrogating SAO badges and other self-powered I²C targets. It streams structured NDJSON to a Mac over USB-C, with or without an SD card — and it refuses to do the things that destroy targets.
A bench tool for the badge table, not a product.
You sit down at a badge table in poor light with someone else's hardware in front of you. You want to know what's on its I²C bus, read its identification descriptor, and watch a wake line — without turning their badge into a souvenir.
HackDeck does that from a serial shell, and writes every observation as one self-framing JSON record per line so the capture survives a dropped byte and can be replayed later. The headless path is the one that ships; the screen is a nicety it doesn't depend on.
Sweeps the external bus and reports hits with device hints, distinguishing "no devices" from "no bus at all."
Reads registers off a chosen address. 8-bit indices only — 16-bit is not a free upgrade, see below.
Watches a line — such as the DC34 badge's open-drain wake pin — which Grove Port.A has no conductor for.
Most of the engineering here is in what it declines to do.
The badge rail is 3.0 V. Grove Port.A supplies 5 V. That mismatch is the single thing most likely to destroy a target, so the harness carries no 5 V conductor at all.
-y, by design.Target facts are confirmed, not guessed: the 3.0 V rail, the devices at 0x3C and 0x19, and GPIO4-as-open-drain-wake all come from DEF CON's published SAO spec sheet, archived in the repo.
The decision most likely to trip up someone reading an older draft.
Grove Port.A is HY2.0-4P: GND, 5 V, G53, G54. That is ground plus exactly two usable IOs and no pin for the badge's wake line — which is the entire reason the GPIO monitor exists. Port.A can run Scanner and Explorer, or GPIO work, but not both at once.
So the default build puts external I²C on the J9 "2X5PIN EXT" header on the back, at 0.1" pitch. No boot strap, no reset line, no console UART and no internal I²C anywhere near it — fewer neighbours means fewer ways to destroy something in bad light.
| Signal | Tab5 pin | J9 pin |
|---|---|---|
| External SDA | GPIO49 | 9 |
| External SCL | GPIO50 | 10 |
| Wake line / GPIO tool | GPIO1 | 8 |
| Spare | GPIO0 | 7 |
| GND | — | 1, 3, 4 |
One newline-terminated JSON object per record, self-framing.
A dropped byte costs at most one record. The host capture tool ignores any line not starting
with {, so framework log output in a dev build cannot corrupt a capture.
// a real line off the board — the first confirmed target, 2026-07-30
{"k":"scan_hit","boot":1,"seq":9,"ms":120581,
"tool":"i2c_scan","res":"ok","addr":"0x28",
"hint":"MFRC522/WS1850S RFID / BNO055 IMU?",
"host_ts":"2026-07-30T16:24:37.324826+00:00"}
Stated plainly, because a bench tool that overstates itself is worse than none.