TL;DR
Companion to the closed #684 (hard_reset_blocking DTR=low fix) and the merged FastLED/FastLED#3339. That PR landed two new artifacts on the FastLED side that fbuild doesn't have a counterpart for, and that should be shared between the two repos so future board ports only add data in one place:
ci/util/serial_probe.py — agent-facing CLI for ad-hoc port probe with the correct DTR/RTS=True default for USB-VCOM bridges.
BOARD_FINGERPRINTS: dict[(vid, pid), str] and ENVIRONMENT_TO_VCOM_VID_PID tables — VID:PID-keyed board identification and PlatformIO-env-to-VCOM-port lookup.
Neither has a parallel in fbuild-serial / fbuild-cli today, which means:
- Agents debugging fbuild-side flows reach for ad-hoc probes that ignore the correct DTR/RTS for the target board — the exact failure mode that FastLED/FastLED#3336 caught on the FastLED side.
- When a new board is ported, its VID:PID has to be added to two places (FastLED's
port_utils.py plus whatever fbuild-side decision point names the port). They will drift.
This issue asks fbuild to adopt the same data + the same probing surface.
What's missing on fbuild
1. No port-probe CLI / Rust helper
FastLED's serial_probe.py is small (≈300 LOC) and exposes three subcommands:
$ uv run --no-project --with pyserial python ci/util/serial_probe.py list
COM25 303A:1001 ser=80:F1:B2:D1:DF:B1 USB Serial Device (COM25) [Espressif native USB CDC (ESP32-S3/C3/C6/H2/P4)]
COM20 16C0:0483 ser=15821020 USB Serial Device (COM20) [LPC11U35 VCOM bridge (LPC845-BRK USART0) OR PJRC Teensy USB-Serial]
COM10 1FC9:0132 ser=0B03400A USB Serial Device (COM10) [NXP CMSIS-DAP debug (LPC845-BRK / LPC11U35)]
$ uv run --no-project --with pyserial python ci/util/serial_probe.py find-lpc
COM20 ...
$ uv run --no-project --with pyserial python ci/util/serial_probe.py read COM20 --seconds 4 --send '{"jsonrpc":"2.0","id":1,"method":"echo","params":[4242]}'
src/fl/remote/remote.cpp.hpp(151): INFO: Stored request ID for echo (id=1)
src/fl/remote/rpc/rpc.cpp.hpp(102): WARN: RPC: Method not found: echo
REMOTE: {...}
fbuild has fbuild-serial::manager (long-lived monitor) and the ESP-specific fbuild-deploy/esp32_native, but no general-purpose port-probe surface. Useful sites for one inside fbuild:
Rust side gets a clean reusable building block; agents and humans get a built-in tool that already knows the DTR/RTS rules, so they stop reinventing wrong ones.
2. No shared board-fingerprint data
FastLED's two tables in ci/util/serial_probe.py and ci/util/port_utils.py:
BOARD_FINGERPRINTS: dict[tuple[int, int], str] = {
(0x1FC9, 0x0132): "NXP CMSIS-DAP debug (LPC845-BRK / LPC11U35)",
(0x16C0, 0x0483): "LPC11U35 VCOM bridge (LPC845-BRK USART0) OR PJRC Teensy USB-Serial",
(0x303A, 0x1001): "Espressif native USB CDC (ESP32-S3/C3/C6/H2/P4)",
(0x10C4, 0xEA60): "Silicon Labs CP2102 USB-UART (ESP32 dev kit)",
(0x1A86, 0x7523): "WCH CH340 USB-Serial",
(0x0403, 0x6001): "FTDI FT232R USB-UART",
# ... etc
}
ENVIRONMENT_TO_VCOM_VID_PID: dict[str, tuple[int, int]] = {
"lpc845brk": (0x16C0, 0x0483),
"lpc845": (0x16C0, 0x0483),
"lpc804": (0x16C0, 0x0483),
"lpcxpresso845max": (0x16C0, 0x0483),
"lpcxpresso804": (0x16C0, 0x0483),
}
Two natural homes inside this repo:
crates/fbuild-serial/boards.toml (or boards.json) — a vendored data file shipped with the crate. Both BOARD_FINGERPRINTS and ENVIRONMENT_TO_VCOM_VID_PID live here; the crate exposes pub fn board_hint(vid: u16, pid: u16) -> Option<&'static str> and pub fn vcom_for_env(env: &str) -> Option<(u16, u16)>.
- Cross-repo source-of-truth file — fbuild owns the canonical TOML, FastLED's Python helpers read it at runtime via a small loader. Removes the two-places-to-edit problem permanently. Slightly more design work; better long-term.
Either path closes the drift gap. (1) is the cheap version; (2) is the right one if the maintainer wants it.
Why this matters
These aren't speculative quality-of-life features — they're the structural lessons from #3300 / #3325 / #3336 / #3339 turned into shared infrastructure. The two sessions burnt on the LPC bring-up were entirely caused by host-side bugs that didn't have a canonical helper to consult; the fix on the FastLED side prevents that on FastLED's hot path, but fbuild's hot path is a separate code surface that can rediscover the same bug independently the moment a contributor writes one ad-hoc serialport::new(port).open()? without re-asserting DTR. A shared CLI + shared data table is the durable fix.
Acceptance criteria
Out of scope
Refs
TL;DR
Companion to the closed #684 (
hard_reset_blockingDTR=low fix) and the merged FastLED/FastLED#3339. That PR landed two new artifacts on the FastLED side that fbuild doesn't have a counterpart for, and that should be shared between the two repos so future board ports only add data in one place:ci/util/serial_probe.py— agent-facing CLI for ad-hoc port probe with the correct DTR/RTS=True default for USB-VCOM bridges.BOARD_FINGERPRINTS: dict[(vid, pid), str]andENVIRONMENT_TO_VCOM_VID_PIDtables — VID:PID-keyed board identification and PlatformIO-env-to-VCOM-port lookup.Neither has a parallel in
fbuild-serial/fbuild-clitoday, which means:port_utils.pyplus whatever fbuild-side decision point names the port). They will drift.This issue asks fbuild to adopt the same data + the same probing surface.
What's missing on fbuild
1. No port-probe CLI / Rust helper
FastLED's
serial_probe.pyis small (≈300 LOC) and exposes three subcommands:fbuild has
fbuild-serial::manager(long-lived monitor) and the ESP-specificfbuild-deploy/esp32_native, but no general-purpose port-probe surface. Useful sites for one inside fbuild:fbuild serial probe list— same as FastLED'slist. Belongs infbuild-cli.fbuild serial probe read <port> [--send X] [--seconds N]— open with correct DTR/RTS for the board family (using theBoardFamily::idle_dtr_rts()API fbuild-serial: hard_reset_blocking leaves DTR=low — fatal for CDC-ACM bridges (LPC11U35, FTDI CDC). Mirror the FastLED #3300/#3339 lessons. #684 hinted at), dump bytes.fbuild serial probe find --vid-pid VID:PID— return device path or non-zero exit.Rust side gets a clean reusable building block; agents and humans get a built-in tool that already knows the DTR/RTS rules, so they stop reinventing wrong ones.
2. No shared board-fingerprint data
FastLED's two tables in
ci/util/serial_probe.pyandci/util/port_utils.py:Two natural homes inside this repo:
crates/fbuild-serial/boards.toml(orboards.json) — a vendored data file shipped with the crate. BothBOARD_FINGERPRINTSandENVIRONMENT_TO_VCOM_VID_PIDlive here; the crate exposespub fn board_hint(vid: u16, pid: u16) -> Option<&'static str>andpub fn vcom_for_env(env: &str) -> Option<(u16, u16)>.Either path closes the drift gap. (1) is the cheap version; (2) is the right one if the maintainer wants it.
Why this matters
These aren't speculative quality-of-life features — they're the structural lessons from #3300 / #3325 / #3336 / #3339 turned into shared infrastructure. The two sessions burnt on the LPC bring-up were entirely caused by host-side bugs that didn't have a canonical helper to consult; the fix on the FastLED side prevents that on FastLED's hot path, but fbuild's hot path is a separate code surface that can rediscover the same bug independently the moment a contributor writes one ad-hoc
serialport::new(port).open()?without re-asserting DTR. A shared CLI + shared data table is the durable fix.Acceptance criteria
fbuild-serialexposes aboard_hint(vid, pid)and avcom_for_env(env)API backed by a single data table.fbuild-cliships aserial probesubcommand with at leastlist,read, andfindmodes (parameter shape can differ from FastLED's; the capability is what matters).BoardFamily::idle_dtr_rts()(the API hinted at in fbuild-serial: hard_reset_blocking leaves DTR=low — fatal for CDC-ACM bridges (LPC11U35, FTDI CDC). Mirror the FastLED #3300/#3339 lessons. #684) — no more silent ESP-defaults on non-ESP boards.Out of scope
Refs
ci/util/serial_probe.py,ci/util/port_utils.py::ENVIRONMENT_TO_VCOM_VID_PID) are the proximate cause for this issue.