Skip to content

feat(cli): fbuild port scan with VID:PID friendly-name resolution (#741) - #742

Merged
zackees merged 1 commit into
mainfrom
feat/741-port-scan
Jun 21, 2026
Merged

feat(cli): fbuild port scan with VID:PID friendly-name resolution (#741)#742
zackees merged 1 commit into
mainfrom
feat/741-port-scan

Conversation

@zackees

@zackeeszackees commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

Adds fbuild port scan — enumerates every visible serial port and renders a two-row block per port with vendor + friendly product names resolved from VID:PID.

COM25 303A:1001 USB Serial Device (COM25) ser=80:F1:B2:D1:DF:B1
└─ Espressif Systems / ESP32-S3 USB-CDC
COM10 1FC9:0132 USB Serial Device (COM10) ser=0B03400A
└─ NXP Semiconductors / LPC-Link2 CMSIS-DAP
4 USB ports, 3 non-USB ports

Row 2 resolves the VID:PID via the tiered fbuild_core::usb resolver:

  • Tier 1: embedded vendor archive (always available)
  • Tier 2: best-effort online overlay fetched from FastLED/fbuild's online-data branch into a 7-day TTL cache under fbuild_paths::get_cache_root().join("usb/")
  • Tier 3: synthetic Device 0xPPPP placeholder

A small inline supplement (FRIENDLY_PRODUCTS) covers common embedded VID:PIDs (ESP32-S3/C3/S2 USB-CDC, NXP LPC-Link2 / MCU-Link) that FastLED/boards' canonical vidpid table doesn't yet carry — those entries should be removed here as they land upstream.

Implementation notes

  • --offline flag skips the network fetch; tier-1 still provides vendor names
  • Atomic cache writes via .tmp sibling + rename — Ctrl+C mid-fetch won't poison the cache
  • reqwest::blocking runs on a dedicated OS thread so its internal tokio runtime doesn't panic from nesting inside the CLI dispatcher's #[tokio::main] outer runtime
  • Pure render_scan() function tested in isolation (11 unit tests pinning layout, summary pluralization, descriptor preference, supplement hits, non-USB ports)

Test plan

  • cargo test -p fbuild-cli --bin fbuild cli::port_scan — 11/11 pass
  • Live fbuild port scan on a system with 4 ESP32-S3 + 1 NXP device — friendly names render correctly
  • cargo clippy -p fbuild-cli --all-targets -- -D warnings — clean
  • cargo fmt — applied

Closes#741

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added fbuild port scan command to enumerate serial ports with USB vendor/product resolution
    • --offline flag available to skip online overlay updates
    • Automatic local caching of USB vendor data with 7-day refresh

Adds `fbuild port scan` — enumerates every visible serial port and
renders a two-row block per port:
COM25 303A:1001 USB Serial Device (COM25) ser=80:F1:B2:...
└─ Espressif Systems / ESP32-S3 USB-CDC
Row 2 resolves the VID:PID via the tiered fbuild_core::usb resolver
(tier 1 embedded vendor archive, tier 2 best-effort online overlay
fetched from FastLED/fbuild's online-data branch into a 7-day TTL
cache under fbuild_paths::get_cache_root().join("usb/")).
Beyond the resolver, the product column falls through this preference
chain so common embedded VID:PIDs the canonical FastLED/boards vidpid
table doesn't yet carry still render a friendly name:
1. Resolver product if non-synthetic (overlay hit)
2. Small inline supplement (ESP32 series, NXP LPC-Link2 / MCU-Link)
3. OS descriptor when chip-specific (e.g. macOS / Linux CP2102 strings)
4. Synthetic `Device 0xPPPP` placeholder
The blocking HTTP fetch runs on a dedicated OS thread so reqwest's
internal tokio runtime sees a clean async-free context — calling it
directly from inside the CLI dispatcher's #[tokio::main] outer runtime
would panic.
Closes#741
@coderabbitai

coderabbitaiBot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a15a87a5-d267-4f35-b08a-dee490c0f69d

📥 Commits

Reviewing files that changed from the base of the PR and between b54461b and b4e20f2.

📒 Files selected for processing (4)
  • crates/fbuild-cli/src/cli/args.rs
  • crates/fbuild-cli/src/cli/dispatch.rs
  • crates/fbuild-cli/src/cli/mod.rs
  • crates/fbuild-cli/src/cli/port_scan.rs

📝 Walkthrough

Walkthrough

A new fbuild port scan subcommand is added to the CLI. It enumerates all visible serial ports, optionally refreshes a cached USB VID overlay (7-day TTL, fetched from USB_VID_JSON_URL on a dedicated thread), then renders two rows per port showing the port path/VID:PID and the resolved vendor/product name, plus a summary count line.

Changes

fbuild port scan subcommand

Layer / File(s)Summary
CLI registration and entry point
crates/fbuild-cli/src/cli/mod.rs, crates/fbuild-cli/src/cli/args.rs, crates/fbuild-cli/src/cli/dispatch.rs, crates/fbuild-cli/src/cli/port_scan.rs
Declares the port_scan module, adds Commands::Port { action: PortAction } to the top-level enum, wires run_port into the dispatcher's match arm, and defines PortAction::Scan { offline } with the run_port entry point.
Scan execution and online overlay cache fetch
crates/fbuild-cli/src/cli/port_scan.rs
Implements scan execution: computes the overlay cache path, checks a 7-day TTL, fetches the JSON overlay via reqwest::blocking on a dedicated OS thread, writes it atomically via .tmp+rename, installs it into fbuild_core::usb, then calls serialport::available_ports and prints the rendered output.
render_scan formatter, product label helpers, and unit tests
crates/fbuild-cli/src/cli/port_scan.rs
Adds the pure render_scan formatter (two rows + blank separators per port, plural-correct summary), USB row rendering via fbuild_core::usb::resolve, friendly_product_name label selection (resolver → supplement table → OS descriptor → synthetic fallback), is_generic_descriptor classifier, the FRIENDLY_PRODUCTS table, render_non_usb, and unit tests covering all formatting behaviors.

Sequence Diagram(s)

sequenceDiagram
actor User
participant fbuild_cli as fbuild CLI dispatcher
participant run_scan as run_scan()
participant CacheFS as Cache FS
participant USB_VID_JSON_URL
participant fbuild_core_usb as fbuild_core::usb
participant serialport
User->>fbuild_cli: fbuild port scan [--offline]
fbuild_cli->>run_scan: run_port(PortAction::Scan { offline })
alt not --offline
run_scan->>CacheFS: check overlay cache age (7-day TTL)
alt cache stale or missing
run_scan->>USB_VID_JSON_URL: GET overlay JSON (dedicated OS thread, 15s timeout)
USB_VID_JSON_URL-->>run_scan: JSON bytes
run_scan->>CacheFS: write .tmp → rename to cache file
end
run_scan->>fbuild_core_usb: install_overlay(bytes)
end
run_scan->>serialport: available_ports()
serialport-->>run_scan: Vec<SerialPortInfo>
loop per port
run_scan->>fbuild_core_usb: resolve(vid, pid)
fbuild_core_usb-->>run_scan: UsbInfo { vendor, product }
end
run_scan-->>User: rendered two-row-per-port output + summary
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A bunny hops up to the serial port,
sniffing each VID with a curious snort.
└─ Espressif / ESP32-S3 it cries!
Two tidy rows gleam before my eyes.
From cached overlays to synthetic fall-through,
every device gets a label brand new! 🐇✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/741-port-scan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zackees
zackees merged commit a5baf02 into mainJun 21, 2026
84 of 91 checks passed
@zackees
zackees deleted the feat/741-port-scan branch June 21, 2026 08:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

feat(cli): fbuild port scan — enumerate ports + resolve VID:PID to vendor/product via FastLED/boards

1 participant

@zackees