Recover CDC interface number on macOS#26
Merged
Conversation
Reported on macOS: `faultycmd info` against a freshly-flashed
FaultyCat skipped every CDC port with "could not determine its
interface number". pyserial on the user's Mac surfaces:
hwid='USB VID:PID=1209:FA17 SER=FLT3-... LOCATION=20-2'
location='20-2'
None of the existing parsers fire on that shape:
- `_WIN_MI_RE` looks for `MI_XX` in hwid (Windows-only).
- `_LOCATION_IFACE_RE` looks for a trailing `.<n>` in `location`
(older pyserial + Linux). macOS location is just the bus
topology `20-2`, with no interface suffix.
- `udevadm` doesn't exist on macOS.
Two new fallbacks pick up the slack:
4. iInterface string match. pyserial may populate
`port.interface` with the firmware's iInterface descriptor
(we set one per CDC in `usb/src/usb_descriptors.c`:
`FaultyCat EMFI Control`, `FaultyCat Crowbar Control`,
`FaultyCat Scanner Shell`, `FaultyCat Target UART`). When
present, a direct lookup recovers the role. Cross-platform —
not macOS-specific, just hadn't been wired before.
5. Trailing-digit parse of the `/dev/cu.usbmodem<...><N>`
device name. The `<N>` is the **data** interface number of
the CDC pair; subtracting 1 yields the control interface
number that the rest of the code uses (data = control + 1
per the CDC class spec). For our 4-CDC composite the
trailing digits are 1, 3, 5, 7 → control IFs 0, 2, 4, 6 →
emfi, crowbar, scanner, target. Single-digit parse is
sufficient (composite tops out at 10 interfaces, well below
the 2-digit boundary).
Tests:
- New `macos_environment` fixture mirrors the exact shape from
the bug report (location=`20-2`, no iInterface, four
`usbmodem142{01,03,05,07}` devices + one unrelated Arduino
usbmodem to confirm filtering).
- New `macos_with_iinterface_environment` fixture exercises the
other fallback path (string match fires before device-name
parse).
- 8 new test cases (discover_macos_..., cdc_for_{role}_macos,
macos_via_iinterface). 23 / 23 usb tests passing.
- All Linux / Windows tests still pass — the new fallbacks only
run when the prior platform-specific parsers return None.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`ruff-format` collapses the multi-line implicit string concatenation in `_macos_port`'s hwid argument onto a single line. No semantic change — just keeps the file conformant with the project's configured line-length and the implicit-string-concatenation style ruff enforces. Pre-commit was running clean afterwards. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Recover CDC interface number on macOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
faultycmd infoagainst a freshly-flashed FaultyCat on macOS skippedevery CDC port with
could not determine its interface number. pyserialon macOS surfaces a hwid + location shape that none of the existing
parsers fire on:
Two new fallbacks in
usb._interface_from_portpick up the slack:port.interfacewith the firmware's iInterface descriptor (set in
usb/src/usb_descriptors.c:FaultyCat EMFI Control,FaultyCat Crowbar Control,FaultyCat Scanner Shell,FaultyCat Target UART). When present, a direct lookup recoversthe role. Cross-platform — not macOS-specific, just hadn't been
wired before.
interface number as the trailing digit of
/dev/cu.usbmodem<...><N>.For our 4-CDC composite the trailing digits are
1, 3, 5, 7→control IFs
0, 2, 4, 6→ emfi, crowbar, scanner, target.Subtract 1 because the rest of the code uses control IFs (data =
control + 1 per the CDC class spec).
The new fallbacks only run when the prior platform-specific parsers
return
None, so Linux / Windows behaviour is unchanged.Commits (2)
6ad91b8fix(host): recover CDC interface number on macOSae614d8style(tests): apply ruff auto-fix on_macos_porthwidFiles changed
host/faultycmd-py/src/faultycmd/usb.py— extended docstring +_INTERFACE_BY_STRINGtable +_MACOS_USBMODEM_RE+ two newfallback branches in
_interface_from_port.host/faultycmd-py/tests/test_usb.py—FakePort.interfacefield,_macos_porthelper, two new fixtures (macos_environment+macos_with_iinterface_environment), 8 new test cases coveringboth fallback paths.
Reproduction (before this PR)
Test plan
pytest host/faultycmd-py/tests/test_usb.py— 23/23 passing(was 15 before; +8 new macOS cases).
pytest host/faultycmd-py/tests/— 167/167 passing (noregression on the other 144 tests).
ruff check host/faultycmd-py/src host/faultycmd-py/tests—clean.
pre-commit run --all-files— all 12 hooks pass.faultycmd infoagainst a flashed board lists 4CDCs with their roles (emfi, crowbar, scanner, target-uart) and no
skip warnings.
faultycmd emfi pingreaches the board andreturns PONG.
faultycmd tuiopens with the firmware versionin the header subtitle and a working diag tail.
faultycmd infostillenumerates ttyACM0..3 as before.
Notes for reviewers
BEFORE the device-name parse, because it's the more robust signal
(matches across any platform where pyserial populates the field,
not just macOS).
_MACOS_USBMODEM_REanchors on.usbmodemnot on/dev/cu.sothe same regex would also match a hypothetical
/dev/tty.usbmodem*if a future macOS release switches the prefix.
vX.Y.Z.Wfrom adraft release. The bug existed since v3 was first ported to macOS;
this PR is what makes that port actually work in practice.