Skip to content

fbuild-serial: vendor a serial-probe CLI + shared BOARD_FINGERPRINTS table (companion to closed #684, parallel to FastLED #3339) #686

Description

@zackees

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:

  1. ci/util/serial_probe.py — agent-facing CLI for ad-hoc port probe with the correct DTR/RTS=True default for USB-VCOM bridges.
  2. 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

  • fbuild-serial exposes a board_hint(vid, pid) and a vcom_for_env(env) API backed by a single data table.
  • fbuild-cli ships a serial probe subcommand with at least list, read, and find modes (parameter shape can differ from FastLED's; the capability is what matters).
  • One of: (a) FastLED's Python helpers read fbuild's data file at runtime, OR (b) the two tables are kept in sync via a CI check that diffs them. (a) preferred, (b) acceptable.
  • The new probe API picks the right DTR/RTS for the target board family by consulting 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions