Skip to content

Re-resolve the NanoKVM USB stick when it moves to another port - #43

Merged
thirteen37 merged 1 commit into
mainfrom
fix/nanokvm-usb-port-move
Aug 23, 2026
Merged

thirteen37 merged 1 commit into
mainfrom
fix/nanokvm-usb-port-move

Conversation

@thirteen37

Copy link
Copy Markdown
Owner

Problem

Connecting to a NanoKVM USB device failed with "Could not find USB video capture device … It may have been unplugged" while the stick sat there working fine.

Device.videoDeviceUniqueID and serialDevicePath both persist a USB location, not device identity:

  • a UVC uniqueID embeds the locationID it enumerated at
  • /dev/cu.usbserial-NNNN encodes the same thing

Move the stick to a different port and both saved values dangle. Confirmed against the hardware — the saved entry pointed at 0x1120000345f2131 / /dev/cu.usbserial-1140, the stick now enumerates as 0x2120000345f2131 / /dev/cu.usbserial-2140:

AVCaptureDevice(uniqueID: "0x1120000345f2131") -> nil
open("/dev/cu.usbserial-1140")                 -> -1 errno=2 No such file or directory

Fix

Re-resolve both identifiers against what is actually attached, before opening anything.

  • Camera — falls back to the single attached device sharing the saved uniqueID's VID/PID tail (345f2131 = MACROSILICON MS2131).
  • Serial — falls back to the port hanging off that camera's parent hub. On a NanoKVM-USB the capture chip and the CH340 are two functions behind the stick's own hub, so the pairing is exact rather than a guess.

Ambiguity (two identical sticks attached) still fails, now with an error telling you to re-select in Edit Device.

USBLocationID carries the locationID arithmetic as pure functions so it is testable without hardware. USBSerialPort now reports the locationID that the IOKit parent walk was already positioned to read.

Verification

Feeding the resolvers the actual stale values from devices.json recovers the current device:

saved video : 0x1120000345f2131
saved serial: /dev/cu.usbserial-1140
resolved video : 0x2120000345f2131
resolved serial: /dev/cu.usbserial-2140

Confirmed working end-to-end against the stick by @thirteen37.

7 new unit tests. KVMCore package, macOS and iPadOS suites all pass.

Also

Corrects a stale claim in CLAUDE.md: the macOS app is not sandboxed — the App Sandbox was dropped to get raw /dev/cu.* access, and device.camera is the only entitlement.

Not included

The session re-resolves on every connect rather than writing the resolved values back, so Edit Device still shows the old ID as "(not connected)" until you re-pick. Connecting works regardless.

🤖 Generated with Claude Code

https://claude.ai/code/session_013CARgLMkYsjjJYFysqzWih

Device.videoDeviceUniqueID and serialDevicePath both persist a USB
location rather than device identity: a UVC uniqueID embeds the
locationID it enumerated at, and /dev/cu.usbserial-NNNN encodes the same
thing. Replug the stick into a different port and both saved values
dangle — AVCaptureDevice(uniqueID:) returns nil, open() returns ENOENT —
so connect() fails with "may have been unplugged" while the hardware sits
there working.

Resolve both against what is actually attached before opening anything.
The camera falls back to the single attached device sharing the saved
uniqueID's VID/PID tail. The serial bridge then falls back to the port
hanging off that camera's parent hub: on a NanoKVM-USB the capture chip
and the CH340 are two functions behind the stick's own hub, so the
pairing is exact rather than a guess. Ambiguity — two identical sticks —
still fails, now saying to re-select in Edit Device.

USBLocationID carries the locationID arithmetic as pure functions so it
is testable without hardware; USBSerialPort now reports the locationID
that the IOKit parent walk was already positioned to read.

Also correct CLAUDE.md: the macOS app is not sandboxed. The App Sandbox
was dropped to get raw /dev/cu.* access, and device.camera is the only
entitlement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013CARgLMkYsjjJYFysqzWih
@thirteen37
thirteen37 merged commit 76549ad into main Aug 23, 2026
3 checks passed
@thirteen37
thirteen37 deleted the fix/nanokvm-usb-port-move branch August 23, 2026 08:00
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