Skip to content

Differentiating Identical Devices

dbankieris edited this page Nov 29, 2022 · 9 revisions

If you're lucky enough to have a device that populates the serial number field in its USB descriptor (use HID Scanner to find out), you can use UsbDevice::setSerialNumber to differentiate otherwise identical devices. Unfortunately, manufactures often leave this field empty. In that case, the only way to tell them apart is by which USB port they're plugged into.

One solution is to write a udev rule that creates a symbolic link based on the USB port. Then you can use UsbDevice::setPath to select a specific device. Note that this only works for Linux systems. IDF ultimately opens devices via hid_open_path, which takes a platform-specific path that you can only safely get from hid_enumerate. On Linux, it's a file system path that you can resolve a symbolic link to. On Mac, it's not.

Start by plugging and unplugging your device while watching /dev to see which hidraw file it's currently associated with. While you could pass that to setPath, there's no guarantee the path will be the same every time you plug it in or turn on the computer. The fool-proof way is to use the path through the bus, which doesn't change from boot to boot. Once you've got the hidraw path, use udevadm on that to get the information necessary to create the proper rule.

udevadm info --attribute-walk --name=/dev/hidraw2

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:14.0/usb3/3-9/3-9.2/3-9.2:1.0/0003:054C:0268.0003/hidraw/hidraw2':
    KERNEL=="hidraw2"
    SUBSYSTEM=="hidraw"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb3/3-9/3-9.2/3-9.2:1.0/0003:054C:0268.0003':
    KERNELS=="0003:054C:0268.0003"
    SUBSYSTEMS=="hid"
    DRIVERS=="sony"
    ATTRS{country}=="00"

  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb3/3-9/3-9.2/3-9.2:1.0':
    KERNELS=="3-9.2:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="usbhid"
    ATTRS{bInterfaceClass}=="03"
    ATTRS{bInterfaceSubClass}=="00"
    ATTRS{bInterfaceProtocol}=="00"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{authorized}=="1"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceNumber}=="00"

  looking at parent device '/devices/pci0000:00/0000:00:14.0/usb3/3-9/3-9.2':
    KERNELS=="3-9.2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="9.2"
    ATTRS{idVendor}=="054c"
    ATTRS{speed}=="12"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{busnum}=="3"
    ATTRS{devnum}=="5"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="500mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="0100"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="20"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Sony"
    ATTRS{removable}=="unknown"
    ATTRS{idProduct}=="0268"
    ATTRS{bDeviceClass}=="00"
    ATTRS{product}=="PLAYSTATION(R)3 Controller"

We only want to act when a new device is plugged in:

ACTION=="add"

We want to match against the hidraw* node, so we'll need the following udev key:

SUBSYSTEM=="hidraw"

To match against the USB port, we need a key that represents that. I'm not really sure what the best choice is, but this seems to work:

KERNELS=="3-9.2:1.0"

We should make sure we only match against the device we're looking for (use HID Scanner to get the vendor and product IDs):

ENV{ID_VENDOR_ID}=="054c", ENV{ID_MODEL_ID}=="0268"

If ENV doesn't work, you might try ATTRS (with idVendor and idProduct) instead. I'm not clear on how they differ, but here's a start.

Finally, we want to make a symlink. The final rules looks like this:

ACTION=="add", SUBSYSTEM=="hidraw", KERNELS=="3-9.2:1.0", ENV{ID_VENDOR_ID}=="054c", ENV{ID_MODEL_ID}=="0268", SYMLINK+="my_controller"

You might have already created a udev file for device permissions, in which case you can add your new rule there as well. The symlink will be created under /dev when a device matching your rule is plugged in, and it will be removed when that device is unplugged. Finally, you can now use setPath with that symlink, which will point to whatever hidraw file the device happened to get associated with this time.