diff --git a/apps/usb_probe.py b/apps/usb_probe.py index 00a04fa..66e27f9 100644 --- a/apps/usb_probe.py +++ b/apps/usb_probe.py @@ -31,6 +31,7 @@ import logging import sys import click import usb1 +from bumble.transport.usb import load_libusb from colors import color @@ -169,6 +170,7 @@ def is_bluetooth_hci(device): def main(verbose): logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper()) + load_libusb() with usb1.USBContext() as context: bluetooth_device_count = 0 devices = {} diff --git a/bumble/transport/pyusb.py b/bumble/transport/pyusb.py index 26bfeda..ecd905d 100644 --- a/bumble/transport/pyusb.py +++ b/bumble/transport/pyusb.py @@ -17,6 +17,7 @@ # ----------------------------------------------------------------------------- import asyncio import logging +import libusb_package import usb.core import usb.util import threading @@ -203,13 +204,13 @@ async def open_pyusb_transport(spec): # Find the device according to the spec moniker if ':' in spec: vendor_id, product_id = spec.split(':') - device = usb.core.find( + device = libusb_package.find( idVendor=int(vendor_id, 16), idProduct=int(product_id, 16) ) else: device_index = int(spec) devices = list( - usb.core.find( + libusb_package.find( find_all=1, bDeviceClass=USB_DEVICE_CLASS_WIRELESS_CONTROLLER, bDeviceSubClass=USB_DEVICE_SUBCLASS_RF_CONTROLLER, diff --git a/bumble/transport/usb.py b/bumble/transport/usb.py index a11dfb0..29f93f2 100644 --- a/bumble/transport/usb.py +++ b/bumble/transport/usb.py @@ -17,9 +17,12 @@ # ----------------------------------------------------------------------------- import asyncio import logging +import libusb_package import usb1 import threading import collections +import ctypes +import platform from colors import color from .common import Transport, ParserSource @@ -33,6 +36,19 @@ logger = logging.getLogger(__name__) # ----------------------------------------------------------------------------- +def load_libusb(): + ''' + Attempt to load the libusb-1.0 C library from libusb_package in site-packages. + If library exists, we create a DLL object and initialize the usb1 backend. + This only needs to be done once, but bufore a usb1.USBContext is created. + If library does not exists, do nothing and usb1 will search default system paths + when usb1.USBContext is created. + ''' + if libusb_path := libusb_package.get_library_path(): + dll_loader = ctypes.WinDLL if platform.system() == 'Windows' else ctypes.CDLL + libusb_dll = dll_loader(libusb_path, use_errno=True, use_last_error=True) + usb1.loadLibrary(libusb_dll) + async def open_usb_transport(spec): ''' Open a USB transport. @@ -305,6 +321,7 @@ async def open_usb_transport(spec): self.context.close() # Find the device according to the spec moniker + load_libusb() context = usb1.USBContext() context.open() try: diff --git a/docs/mkdocs/src/transports/usb.md b/docs/mkdocs/src/transports/usb.md index d794e33..e400630 100644 --- a/docs/mkdocs/src/transports/usb.md +++ b/docs/mkdocs/src/transports/usb.md @@ -22,10 +22,10 @@ the first USB interface of the device will be used, regardless of the interface This may be useful for some devices that use a custom class/subclass but may nonetheless work as-is. !!! examples - `usb:04b4:f901` + `usb:04b4:f901` The USB dongle with `` equal to `04b4` and `` equal to `f901` - `usb:0` + `usb:0` The first Bluetooth HCI dongle that's declared as such by Class/Subclass/Protocol `usb:04b4:f901/0016A45B05D8` @@ -42,6 +42,11 @@ This may be useful for some devices that use a custom class/subclass but may non The library includes two different implementations of the USB transport, implemented using different python bindings for `libusb`. Using the transport prefix `pyusb:` instead of `usb:` selects the implementation based on [PyUSB](https://pypi.org/project/pyusb/), using the synchronous API of `libusb`, whereas the default implementation is based on [libusb1](https://pypi.org/project/libusb1/), using the asynchronous API of `libusb`. In order to use the alternative PyUSB-based implementation, you need to ensure that you have installed that python module, as it isn't installed by default as a dependency of Bumble. +## Libusb + +The `libusb-1.0` shared library is required to use both `usb` and `pyusb` transports. This library should be installed automatically with Bumble, as part of the `libusb_package` Python package. +If your OS or architecture is not supported by `libusb_package`, you can install a system-wide library with `brew install libusb` for Mac or `apt install libusb-1.0-0` for Linux. + ## Listing Available USB Devices ### With `usb_probe` @@ -65,4 +70,4 @@ Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 003 Device 014: ID 0b05:17cb ASUSTek Computer, Inc. Broadcom BCM20702A0 Bluetooth ``` -The device id for the Bluetooth interface in this case is `0b05:17cb`. \ No newline at end of file +The device id for the Bluetooth interface in this case is `0b05:17cb`. diff --git a/setup.cfg b/setup.cfg index 441fbaa..469ab79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,6 +37,7 @@ install_requires = cryptography == 35; platform_system!='Emscripten' grpcio >= 1.46; platform_system!='Emscripten' libusb1 >= 2.0.1; platform_system!='Emscripten' + libusb-package == 1.0.26.0; platform_system!='Emscripten' prompt_toolkit >= 3.0.16; platform_system!='Emscripten' protobuf >= 3.12.4 pyee >= 8.2.2