Skip to content

Recover CDC interface number on macOS#26

Merged
HeikkiRadu merged 3 commits into
rewrite/v3from
feat/release-versioning
May 28, 2026
Merged

Recover CDC interface number on macOS#26
HeikkiRadu merged 3 commits into
rewrite/v3from
feat/release-versioning

Conversation

@HeikkiRadu

@HeikkiRadu HeikkiRadu commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

faultycmd info against a freshly-flashed FaultyCat on macOS skipped
every CDC port with could not determine its interface number. pyserial
on macOS surfaces a hwid + location shape that none of the existing
parsers fire on:

hwid='USB VID:PID=1209:FA17 SER=FLT3-E6633C805B3A3827 LOCATION=20-2'
location='20-2'

Two new fallbacks in usb._interface_from_port pick up the slack:

  1. iInterface string match. pyserial may populate port.interface
    with 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 recovers
    the role. Cross-platform — not macOS-specific, just hadn't been
    wired before.
  2. Device-name trailing-digit parse. macOS encodes the data
    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)

  1. 6ad91b8 fix(host): recover CDC interface number on macOS
  2. ae614d8 style(tests): apply ruff auto-fix on _macos_port hwid

Files changed

  • host/faultycmd-py/src/faultycmd/usb.py — extended docstring +
    _INTERFACE_BY_STRING table + _MACOS_USBMODEM_RE + two new
    fallback branches in _interface_from_port.
  • host/faultycmd-py/tests/test_usb.pyFakePort.interface field,
    _macos_port helper, two new fixtures (macos_environment +
    macos_with_iinterface_environment), 8 new test cases covering
    both fallback paths.

Reproduction (before this PR)

$ faultycmd info
found FaultyCat CDC at /dev/cu.usbmodem14201 but could not determine its interface number
  (hwid='USB VID:PID=1209:FA17 SER=FLT3-E6633C805B3A3827 LOCATION=20-2', location='20-2') — skipping
[ditto for ...14203, ...14205, ...14207]
No FaultyCat CDC found. Check that the board is plugged in and re-enumerated as 1209:fa17.

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 (no
    regression 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.
  • On macOS: faultycmd info against a flashed board lists 4
    CDCs with their roles (emfi, crowbar, scanner, target-uart) and no
    skip warnings.
  • On macOS: faultycmd emfi ping reaches the board and
    returns PONG.
  • On macOS: faultycmd tui opens with the firmware version
    in the header subtitle and a working diag tail.
  • Regression check on Linux: faultycmd info still
    enumerates ttyACM0..3 as before.

Notes for reviewers

  • The fallback ordering is intentional: iInterface string is checked
    BEFORE the device-name parse, because it's the more robust signal
    (matches across any platform where pyserial populates the field,
    not just macOS).
  • The _MACOS_USBMODEM_RE anchors on .usbmodem not on /dev/cu. so
    the same regex would also match a hypothetical /dev/tty.usbmodem*
    if a future macOS release switches the prefix.
  • Verified on the reporter's MacBook with firmware vX.Y.Z.W from a
    draft release. The bug existed since v3 was first ported to macOS;
    this PR is what makes that port actually work in practice.

HeikkiRadu and others added 3 commits May 28, 2026 13:49
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>
@HeikkiRadu HeikkiRadu merged commit 7d7c67e into rewrite/v3 May 28, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant