Skip to content

fix(serial): list Windows Status=Unknown ports so Teensy (16C0) is visible (#962)#1011

Merged
zackees merged 1 commit into
mainfrom
fix/962-teensy-16c0-port-scan
Jul 9, 2026
Merged

fix(serial): list Windows Status=Unknown ports so Teensy (16C0) is visible (#962)#1011
zackees merged 1 commit into
mainfrom
fix/962-teensy-16c0-port-scan

Conversation

@zackees

@zackees zackees commented Jul 9, 2026

Copy link
Copy Markdown
Member

What

Fixes #962fbuild port scan now lists PJRC/Teensy (VID 16C0) serial ports on Windows, resolves them, and surfaces the composite MI_xx interface index.

Root cause

serialport::available_ports() on Windows:

  1. enumerates the "Ports" setup class with DIGCF_PRESENT, and
  2. skips any devnode whose CM_Get_DevNode_Status problem code isn't 0.

A Teensy's serial functions enumerate as composite MI_00 interfaces whose usbser function driver never starts — PnP Status = Unknown. To SetupAPI they are non-present phantoms (CR_NO_SUCH_DEVINST), so they're dropped by DIGCF_PRESENT before the problem-code filter even runs. Result: a physically-attached Teensy is invisible to port scan (and to the Teensy deploy port-discovery snapshot, which used the same call).

Confirmed on real hardware — 4 Teensy COM ports (16C0:0483 ×2, 16C0:0489 ×2) present in Windows PnP but absent from port scan; also affected some Status=Unknown ESP32 ports.

Fix

New blessed enumerator fbuild_serial::ports::available_ports() (the ban_direct_serialport wrapper crate). A Windows fork of serialport's SetupAPI walk that:

  • enumerates with flags = 0 (not DIGCF_PRESENT) so phantom devnodes are returned;
  • recovers VID/PID from the composite child's own hardware id when the phantom parent is unreachable (upstream returns None here → no VID/PID);
  • includes a non-present port only if it's a USB serial device, so stale non-USB junk is never resurrected;
  • de-dups by COM name; keeps the DEVICEMAP\SERIALCOMM fallback for parity.

port scan and Teensy port_discovery now route through it. Non-Windows delegates straight to serialport.

Also: enable serialport's usbportinfo-interface feature and render the composite MI_xx index (if=MI_00) so a Teensy's Serial vs Serial+MIDI functions are distinguishable. Teensy product names come from the embedded FastLED/boards VID:PID archive — no hardcoded table.

Verification (real hardware)

COM19  16C0:0483  ...  if=MI_00
       └─ ... / Teensyduino Serial
COM21  16C0:0489  ...  if=MI_00
       └─ ... / Teensyduino MIDI + Serial

Tests: fbuild-serial (148), fbuild-core (237), port_scan (16) all green; new unit tests cover phantom-composite parsing (no parent), the MI index render, and archive-driven PJRC resolution.

Follow-up (tracked under #959)

The remaining hardcoded FRIENDLY_PRODUCTS entries (Espressif 303A, NXP MCU-Link 1FC9:0143) still aren't in the embedded archive. Migrating them into the FastLED/boards other branch and deleting the in-code table is the #959 "build-time embedding" cleanup, done there to avoid regressing offline ESP32 names.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved serial port detection on Windows, especially for composite USB devices like Teensy.
    • Port listings now show the USB interface index when available, helping distinguish multiple interfaces on the same device.
  • Bug Fixes

    • Fixed cases where some Windows composite ports were missing from scans.
    • Improved detection of devices in unusual or partially unavailable states so they can still appear when appropriate.

…sible (#962)

`fbuild port scan` (and the Teensy deploy port-discovery snapshot) missed
every PJRC/Teensy serial port on Windows. Upstream `serialport::available_ports`
enumerates the Ports class with `DIGCF_PRESENT` and additionally skips any
devnode whose `CM_Get_DevNode_Status` problem code isn't 0. A Teensy's serial
functions enumerate as composite MI_00 interfaces whose function driver never
starts (PnP `Status = Unknown`), so they are non-present phantoms and get
dropped twice over — a physically-attached Teensy was invisible.

Add a blessed `fbuild_serial::ports::available_ports()`: a Windows fork of
serialport's SetupAPI walk that (1) enumerates with `flags = 0` so phantom
devnodes are returned, (2) recovers VID/PID from the composite child's own
hardware id when the phantom parent is unreachable, and (3) includes a
non-present port only when it is a USB serial device (never resurrecting stale
non-USB junk). Route `port scan` and Teensy `port_discovery` through it.

Also enable serialport's `usbportinfo-interface` feature and surface the
composite `MI_xx` index in `port scan`, so a Teensy's Serial vs Serial+MIDI
functions are distinguishable. Teensy VID:PID product names come from the
embedded FastLED/boards archive (no hardcoded table).

Verified on real hardware: COM19 16C0:0483 -> Teensyduino Serial (MI_00),
COM21/COM7 16C0:0489 -> Teensyduino MIDI+Serial; previously-hidden
Status=Unknown ESP32 ports now appear too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a cross-platform "blessed" serial port enumerator (fbuild_serial::ports::available_ports()) with a Windows-specific SetupDi-based implementation preserving non-OK/phantom devnode ports and parsing composite MI_xx interface indices. Callers switch to this enumerator, CLI output surfaces the interface index, and a Teensy resolver test is added.

Changes

Composite serial port enumeration and interface surfacing

Layer / File(s) Summary
Dependency setup
Cargo.toml, crates/fbuild-serial/Cargo.toml
Adds serialport usbportinfo-interface feature and a Windows-only windows-sys dependency with device/registry features.
Blessed available_ports() implementation
crates/fbuild-serial/src/lib.rs, crates/fbuild-serial/src/ports.rs
Adds new ports module exporting available_ports(); on non-Windows delegates to serialport, on Windows implements SetupDi-based device-class GUID enumeration, hardware-ID/MI_xx interface parsing, registry COM port lookup, and a registry SERIALCOMM fallback merge, plus unit tests.
Callers switch to blessed enumerator
crates/fbuild-deploy/src/esp32_native/transport.rs, crates/fbuild-deploy/src/teensy/port_discovery.rs
Sets interface: None in the ESP32 fallback UsbPortInfo; Teensy list_ports() now calls fbuild_serial::ports::available_ports() instead of serialport::available_ports().
CLI interface surfacing and tests
crates/fbuild-cli/src/cli/port_scan.rs
Switches run_scan to the blessed enumerator, threads interface: Option<u8> through USB rendering helpers to output if=MI_XX, and updates/adds tests validating the new output.
Teensy resolver test
crates/fbuild-core/src/usb/resolver.rs
Adds a unit test asserting embedded-archive resolution of PJRC/Teensy vendor/product names for serial and midi mode PIDs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PortScanCLI
  participant available_ports
  participant WindowsEnumerator
  participant SetupDiRegistry

  PortScanCLI->>available_ports: request port list
  available_ports->>WindowsEnumerator: enumerate device-class GUIDs (Windows)
  WindowsEnumerator->>SetupDiRegistry: query hardware IDs, status, PortName
  SetupDiRegistry-->>WindowsEnumerator: composite MI_xx info, COM names
  WindowsEnumerator->>SetupDiRegistry: merge SERIALCOMM fallback entries
  WindowsEnumerator-->>available_ports: deduplicated port list with interface index
  available_ports-->>PortScanCLI: SerialPortInfo list
  PortScanCLI->>PortScanCLI: render_usb_port appends if=MI_XX
Loading

Possibly related PRs

  • FastLED/fbuild#982: Adds/exercises the same embedded VID:PID overlay resolver logic for Teensy/PJRC product resolution touched by the new resolver test.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main Windows Teensy port-scan fix.
Linked Issues check ✅ Passed The PR addresses both goals: Windows Teensy ports are enumerated and shown with MI_xx, and PJRC/Teensy VID:PIDs are covered by resolver tests.
Out of Scope Changes check ✅ Passed All changes stay within Windows serial enumeration and PJRC/Teensy resolution scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 fix/962-teensy-16c0-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.

@zackees
zackees merged commit 9b72690 into main Jul 9, 2026
88 of 93 checks passed
@zackees
zackees deleted the fix/962-teensy-16c0-port-scan branch July 9, 2026 18:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/fbuild-serial/src/ports.rs`:
- Around line 375-396: The property lookup in `property` is passing the buffer
length in `u16` elements instead of bytes to
`SetupDiGetDeviceRegistryPropertyW`, which can truncate valid `REG_SZ` values.
Update the `property_buf` size argument to use the byte size of the buffer in
the `property` method so the API receives the full capacity. Keep the existing
`value_type` check and string parsing logic in place after the buffer sizing
fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aa4c3c47-c282-4a34-93c2-213d8fceeb78

📥 Commits

Reviewing files that changed from the base of the PR and between ed5f908 and 0b6a84f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • Cargo.toml
  • crates/fbuild-cli/src/cli/port_scan.rs
  • crates/fbuild-core/src/usb/resolver.rs
  • crates/fbuild-deploy/src/esp32_native/transport.rs
  • crates/fbuild-deploy/src/teensy/port_discovery.rs
  • crates/fbuild-serial/Cargo.toml
  • crates/fbuild-serial/src/lib.rs
  • crates/fbuild-serial/src/ports.rs

Comment on lines +375 to +396
fn property(&mut self, property_id: u32) -> Option<String> {
let mut value_type = 0;
let mut property_buf = [0u16; MAX_PATH as usize];
let res = unsafe {
SetupDiGetDeviceRegistryPropertyW(
self.hdi,
&self.devinfo_data,
property_id,
&mut value_type,
property_buf.as_mut_ptr() as *mut u8,
property_buf.len() as u32,
ptr::null_mut(),
)
};
if res == FALSE || value_type != REG_SZ {
return None;
}
from_utf16_lossy_trimmed(&property_buf)
.split(';')
.next_back()
.map(str::to_string)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n "SetupDiGetDeviceRegistryPropertyW|PropertyBufferSize|property_buf.len\(\) as u32|MAX_PATH as usize" crates/fbuild-serial src -S

Repository: FastLED/fbuild

Length of output: 1107


🌐 Web query:

SetupDiGetDeviceRegistryPropertyW PropertyBufferSize bytes documentation

💡 Result:

In the Windows API function SetupDiGetDeviceRegistryPropertyW, the PropertyBufferSize parameter specifies the size, in bytes, of the buffer provided via the PropertyBuffer parameter [1][2][3]. This parameter serves two primary purposes in managing memory for device property retrieval: 1. Standard Retrieval: When PropertyBuffer points to a valid memory location, PropertyBufferSize tells the function how many bytes are available to store the retrieved property data [1][3]. 2. Buffer Size Inquiry: You can determine the required size for a property without pre-allocating a large buffer by performing a two-step process [4][2]: - Set the PropertyBuffer parameter to NULL and the PropertyBufferSize parameter to zero [1][3]. - When called this way, the function returns FALSE (as the buffer is too small), and the RequiredSize parameter is populated with the number of bytes necessary to hold the requested property data [4][1][3]. You can then allocate a buffer of the size specified in RequiredSize and call the function again to retrieve the actual data [4]. It is important to note that the RequiredSize value reflects the total bytes needed for the property, and the function will return an error if the provided PropertyBufferSize is smaller than this value [2][3].

Citations:


🏁 Script executed:

sed -n '360,395p' crates/fbuild-serial/src/ports.rs

Repository: FastLED/fbuild

Length of output: 1529


Pass the property buffer size in bytes, not u16 elements. SetupDiGetDeviceRegistryPropertyW expects PropertyBufferSize in bytes, so property_buf.len() as u32 halves the usable capacity and causes valid manufacturer/friendly-name values over 260 bytes to fail and return None.

Proposed fix
             SetupDiGetDeviceRegistryPropertyW(
                 self.hdi,
                 &self.devinfo_data,
                 property_id,
                 &mut value_type,
                 property_buf.as_mut_ptr() as *mut u8,
-                property_buf.len() as u32,
+                (property_buf.len() * 2) as u32,
                 ptr::null_mut(),
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn property(&mut self, property_id: u32) -> Option<String> {
let mut value_type = 0;
let mut property_buf = [0u16; MAX_PATH as usize];
let res = unsafe {
SetupDiGetDeviceRegistryPropertyW(
self.hdi,
&self.devinfo_data,
property_id,
&mut value_type,
property_buf.as_mut_ptr() as *mut u8,
property_buf.len() as u32,
ptr::null_mut(),
)
};
if res == FALSE || value_type != REG_SZ {
return None;
}
from_utf16_lossy_trimmed(&property_buf)
.split(';')
.next_back()
.map(str::to_string)
}
fn property(&mut self, property_id: u32) -> Option<String> {
let mut value_type = 0;
let mut property_buf = [0u16; MAX_PATH as usize];
let res = unsafe {
SetupDiGetDeviceRegistryPropertyW(
self.hdi,
&self.devinfo_data,
property_id,
&mut value_type,
property_buf.as_mut_ptr() as *mut u8,
(property_buf.len() * 2) as u32,
ptr::null_mut(),
)
};
if res == FALSE || value_type != REG_SZ {
return None;
}
from_utf16_lossy_trimmed(&property_buf)
.split(';')
.next_back()
.map(str::to_string)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fbuild-serial/src/ports.rs` around lines 375 - 396, The property
lookup in `property` is passing the buffer length in `u16` elements instead of
bytes to `SetupDiGetDeviceRegistryPropertyW`, which can truncate valid `REG_SZ`
values. Update the `property_buf` size argument to use the byte size of the
buffer in the `property` method so the API receives the full capacity. Keep the
existing `value_type` check and string parsing logic in place after the buffer
sizing fix.

@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Jul 10, 2026
Sign up for free to 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.

fbuild port scan misses all PJRC/Teensy (VID 16C0) serial ports + no vendor/product resolution for them

1 participant