Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve USB device detection logic #39

Merged
merged 3 commits into from
Oct 6, 2022
Merged

improve USB device detection logic #39

merged 3 commits into from
Oct 6, 2022

Conversation

barbibulle
Copy link
Collaborator

Some USB devices don't use compliant descriptors for their interfaces. This PR allows forcing the auto-detection logic to use those devices nonetheless.
Also included is better way to detect compliant devices (devices where the BT class is set on the interfaces but not on the whole device), so they can be used as usb:<index> rather than requiring an explicit usb:<vendor>:<product>.
Finally, a --verbose option is added to usb_probe.py that will list all the endpoints for each enumerated device.

Copy link
Collaborator

@turon turon left a comment

Choose a reason for hiding this comment

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

Both my USB dongles, both before and with this change generate the following error for usb_probe:

$ python3 ./apps/usb_probe.py --verbose
Traceback (most recent call last):
  File "/home/mturon/src/nest/ble/bumble/./apps/usb_probe.py", line 228, in <module>
    main(verbose)
  File "/home/mturon/src/nest/ble/bumble/./apps/usb_probe.py", line 199, in main
    if device.getSerialNumber() and not serial_number_collision:
  File "/home/mturon/src/nest/connectedhomeip/turon/.environment/pigweed-venv/lib/python3.10/site-packages/usb1/__init__.py", line 2019, in getSerialNumber
    return self.open().getSerialNumber()
  File "/home/mturon/src/nest/connectedhomeip/turon/.environment/pigweed-venv/lib/python3.10/site-packages/usb1/__init__.py", line 2055, in open
    mayRaiseUSBError(libusb1.libusb_open(self.device_p, byref(handle)))
  File "/home/mturon/src/nest/connectedhomeip/turon/.environment/pigweed-venv/lib/python3.10/site-packages/usb1/__init__.py", line 127, in mayRaiseUSBError
    __raiseUSBError(value)
  File "/home/mturon/src/nest/connectedhomeip/turon/.environment/pigweed-venv/lib/python3.10/site-packages/usb1/__init__.py", line 119, in raiseUSBError
    raise __STATUS_TO_EXCEPTION_DICT.get(value, __USBError)(value)
usb1.USBErrorAccess: LIBUSB_ERROR_ACCESS [-3]

Interestingly, the python3 ./apps/scan.py usb:0 command works fine for both (vendor:product syntax is not required).

Copy link
Collaborator

@tavip tavip left a comment

Choose a reason for hiding this comment

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

LGTM!

@tavip
Copy link
Collaborator

tavip commented Oct 4, 2022

@turon My guess is that this is caused by a permission denied error on some USB device. I'll try to replicate it locally and should have a PR to fix it soon after.

Some USB device properties are only accessible if the user has the
appropriate permissions. Handle libusb1 errors to graciously skip
showing details for these devices.
@tavip
Copy link
Collaborator

tavip commented Oct 4, 2022

@turon I have pushed a commit on top to fix the issue you reporeted, could you please give it a try?

@AriZuu
Copy link

AriZuu commented Oct 5, 2022

I tried this change with Asus BT-400 usb bluetooth adapter, which reports itself as deviceclass 0xff.
Output from usb_probe.py:

ID 0B05:17CB
Bus/Device: 000/002
Class: Vendor Specific
Subclass/Protocol: 1/1
Bumble Transport Names: usb:0B05:17CB or usb:0B05:17CB/5CF37081438F
Serial: 5CF37081438F
Manufacturer: Broadcom Corp
Product: BCM20702A0
Configuration 1
Interface: 0 (Vendor Specific, 1/1)
Endpoint 0x81: INTERRUPT IN
Endpoint 0x82: BULK IN
Endpoint 0x02: BULK OUT
Interface: 1/0 (Vendor Specific, 1/1)
Endpoint 0x83: ISOCHRONOUS IN
Endpoint 0x03: ISOCHRONOUS OUT
Interface: 1/1 (Vendor Specific, 1/1)
Endpoint 0x83: ISOCHRONOUS IN
Endpoint 0x03: ISOCHRONOUS OUT
Interface: 1/2 (Vendor Specific, 1/1)
Endpoint 0x83: ISOCHRONOUS IN
Endpoint 0x03: ISOCHRONOUS OUT
Interface: 1/3 (Vendor Specific, 1/1)
Endpoint 0x83: ISOCHRONOUS IN
Endpoint 0x03: ISOCHRONOUS OUT
Interface: 1/4 (Vendor Specific, 1/1)
Endpoint 0x83: ISOCHRONOUS IN
Endpoint 0x03: ISOCHRONOUS OUT
Interface: 1/5 (Vendor Specific, 1/1)
Endpoint 0x83: ISOCHRONOUS IN
Endpoint 0x03: ISOCHRONOUS OUT
Interface: 2 (Vendor Specific, 255/255)
Endpoint 0x84: BULK IN
Endpoint 0x04: BULK OUT
Interface: 3 (Application Specific, 1/1)

But run_scanner.py example fails:

python run_scanner.py usb:0B05:17CB
<<< connecting to HCI...
Traceback (most recent call last):
File "/usr/home/ari/bumble-envi/run_scanner.py", line 83, in
asyncio.run(main())
File "/usr/local/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/local/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
return future.result()
File "/usr/home/ari/bumble-envi/run_scanner.py", line 40, in main
async with await open_transport_or_link(sys.argv[1]) as (hci_source, hci_sink):
File "/home/ari/bumble/bumble/transport/init.py", line 95, in open_transport_or_link
return await open_transport(name)
File "/home/ari/bumble/bumble/transport/init.py", line 71, in open_transport
return await open_usb_transport(spec[0] if spec else None)
File "/home/ari/bumble/bumble/transport/usb.py", line 400, in open_usb_transport
raise ValueError('no compatible interface found for device')
ValueError: no compatible interface found for device

With v0.0.121 scanner works if I comment out this line in usb.py:

device.getDeviceClass() == USB_DEVICE_CLASS_WIRELESS_CONTROLLER and

python run_scanner.py usb:0B05:17CB! with exclamation mark works, however.

@turon
Copy link
Collaborator

turon commented Oct 5, 2022

That works better now for both dongles I have. Error -3 output remains though if that is important:

[Broadcom]

$ python3 ./apps/usb_probe.py 
ID 1D6B:0003
  Bus/Device:             004/001
  Class:                  Hub
  Subclass/Protocol:      0/3
  LIBUSB_ERROR_ACCESS [-3]
ID 1050:0200
  Bus/Device:             003/007
  Class:                  Device
  Subclass/Protocol:      0/0
  Bumble Transport Names: usb:1050:0200
  Manufacturer:           Yubico
  Product:                Yubico Gnubby (gnubby1)

ID 04F2:B6EA
  Bus/Device:             003/004
  Class:                  Miscellaneous
  Subclass/Protocol:      2/1
  LIBUSB_ERROR_ACCESS [-3]
ID 06CB:00FC
  Bus/Device:             003/022
  Class:                  Vendor Specific
  Subclass/Protocol:      16/255
  LIBUSB_ERROR_ACCESS [-3]
ID 8087:0026
  Bus/Device:             003/006
  Class:                  Wireless Controller
  Subclass/Protocol:      1/1 [Bluetooth]
  Bumble Transport Names: usb:0 or usb:8087:0026
  Manufacturer:           None
  Product:                None

ID 0B05:17CB
  Bus/Device:             003/023
  Class:                  Vendor Specific
  Subclass/Protocol:      1/1
  Bumble Transport Names: usb:0B05:17CB or usb:0B05:17CB/5CF370921F5E
  Serial:                 5CF370921F5E
  Manufacturer:           Broadcom Corp
  Product:                BCM20702A0

ID 1D6B:0002
  Bus/Device:             003/001
  Class:                  Hub
  Subclass/Protocol:      0/1
  LIBUSB_ERROR_ACCESS [-3]
ID 1D6B:0003
  Bus/Device:             002/001
  Class:                  Hub
  Subclass/Protocol:      0/3
  LIBUSB_ERROR_ACCESS [-3]
ID 1D6B:0002
  Bus/Device:             001/001
  Class:                  Hub
  Subclass/Protocol:      0/1
  LIBUSB_ERROR_ACCESS [-3]

[CSR]

$ python3 ./apps/usb_probe.py 
ID 1D6B:0003
  Bus/Device:             004/001
  Class:                  Hub
  Subclass/Protocol:      0/3
  LIBUSB_ERROR_ACCESS [-3]
ID 1050:0200
  Bus/Device:             003/007
  Class:                  Device
  Subclass/Protocol:      0/0
  Bumble Transport Names: usb:1050:0200
  Manufacturer:           Yubico
  Product:                Yubico Gnubby (gnubby1)

ID 04F2:B6EA
  Bus/Device:             003/004
  Class:                  Miscellaneous
  Subclass/Protocol:      2/1
  LIBUSB_ERROR_ACCESS [-3]
ID 06CB:00FC
  Bus/Device:             003/022
  Class:                  Vendor Specific
  Subclass/Protocol:      16/255
  LIBUSB_ERROR_ACCESS [-3]
ID 8087:0026
  Bus/Device:             003/006
  Class:                  Wireless Controller
  Subclass/Protocol:      1/1 [Bluetooth]
  Bumble Transport Names: usb:0 or usb:8087:0026
  Manufacturer:           None
  Product:                None

ID 0A12:0001
  Bus/Device:             003/024
  Class:                  Wireless Controller
  Subclass/Protocol:      1/1 [Bluetooth]
  LIBUSB_ERROR_ACCESS [-3]
ID 1D6B:0002
  Bus/Device:             003/001
  Class:                  Hub
  Subclass/Protocol:      0/1
  LIBUSB_ERROR_ACCESS [-3]
ID 1D6B:0003
  Bus/Device:             002/001
  Class:                  Hub
  Subclass/Protocol:      0/3
  LIBUSB_ERROR_ACCESS [-3]
ID 1D6B:0002
  Bus/Device:             001/001
  Class:                  Hub
  Subclass/Protocol:      0/1
  LIBUSB_ERROR_ACCESS [-3]

@barbibulle
Copy link
Collaborator Author

With v0.0.121 scanner works if I comment out this line in usb.py:

device.getDeviceClass() == USB_DEVICE_CLASS_WIRELESS_CONTROLLER and

python run_scanner.py usb:0B05:17CB! with exclamation mark works, however.

This is the expected behavior. A recent change updated the USB discovery logic to not hardcode the USB endpoint addresses as was done previously, because some controllers don't use the "usual" endpoint addresses of (0x81, 0x82, 0x02) (they are "usual", but as it turns out, not specifically required to be exactly that by the BT specification), so the updated logic is to discover the endpoint addresses dynamically, for endpoint with the normative class/subclass/protocol. The BT-400 dongle doesn't use the normative class/subclass/protocol, but does use (0x81, 0x82, 0x02), so that's why it worked before, when those addresses were hardcoded and there was no check for class/subclass/protocol. As a result, the '!' suffix is introduced in this PR to allow overriding the check for class/subclass/protocol.
NOTE: the reason why the BT-400 works on Windows and Linux even though its descriptors are not standard is that the drivers have 0B05:17CB specifically listed as a 'Broadcom based ASUS dongle', so it gets used that way. Also, looking at the Linux driver, it seems that this specific dongle may require downloading a FW patch to it at init time, which the Bumble library doesn't support (yet). That dongle seems to still work fine, at least in limited testing (I have one and tried it), but it may be the case that without loading a FW patch some of the functionality isn't complete or there may be some other known issues that would normally be fixed by the patch. It would be great to eventually support loading those FW patches, like the Linux driver supports (and I assume the Windows driver as well) as a future enhancement to this library.

@barbibulle
Copy link
Collaborator Author

That works better now for both dongles I have. Error -3 output remains though if that is important:

This error is normal on Linux. Getting the manufacturer name and serial number for a USB device requires actually opening the device to read those strings (as opposed to the other properties that are available purely from the USB descriptors without requiring libusb to open the device). On a platform like macOS, there's no issue with that, but on Linux you don't have the permission to open all the devices, so reading those properties will fail. This isn't a problem for our use case, since you wouldn't be able to use those with the Bumble library either, so the fact that usb_probe.py can't fully list all properties isn't a limitation.

@barbibulle barbibulle merged commit 2fc7a0b into main Oct 6, 2022
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.

5 participants