| # Copyright 2021-2022 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # ----------------------------------------------------------------------------- |
| # This tool lists all the USB devices, with details about each device. |
| # For each device, the different possible Bumble transport strings that can |
| # refer to it are listed. If the device is known to be a Bluetooth HCI device, |
| # its identifier is printed in reverse colors, and the transport names in cyan color. |
| # For other devices, regardless of their type, the transport names are printed |
| # in red. Whether that device is actually a Bluetooth device or not depends on |
| # whether it is a Bluetooth device that uses a non-standard Class, or some other |
| # type of device (there's no way to tell). |
| # ----------------------------------------------------------------------------- |
| |
| # ----------------------------------------------------------------------------- |
| # Imports |
| # ----------------------------------------------------------------------------- |
| import os |
| import logging |
| import usb1 |
| from colors import color |
| |
| |
| # ----------------------------------------------------------------------------- |
| # Constants |
| # ----------------------------------------------------------------------------- |
| USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xE0 |
| USB_DEVICE_SUBCLASS_RF_CONTROLLER = 0x01 |
| USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER = 0x01 |
| |
| USB_DEVICE_CLASSES = { |
| 0x00: 'Device', |
| 0x01: 'Audio', |
| 0x02: 'Communications and CDC Control', |
| 0x03: 'Human Interface Device', |
| 0x05: 'Physical', |
| 0x06: 'Still Imaging', |
| 0x07: 'Printer', |
| 0x08: 'Mass Storage', |
| 0x09: 'Hub', |
| 0x0A: 'CDC Data', |
| 0x0B: 'Smart Card', |
| 0x0D: 'Content Security', |
| 0x0E: 'Video', |
| 0x0F: 'Personal Healthcare', |
| 0x10: 'Audio/Video', |
| 0x11: 'Billboard', |
| 0x12: 'USB Type-C Bridge', |
| 0x3C: 'I3C', |
| 0xDC: 'Diagnostic', |
| USB_DEVICE_CLASS_WIRELESS_CONTROLLER: ( |
| 'Wireless Controller', |
| { |
| 0x01: { |
| 0x01: 'Bluetooth', |
| 0x02: 'UWB', |
| 0x03: 'Remote NDIS', |
| 0x04: 'Bluetooth AMP' |
| } |
| } |
| ), |
| 0xEF: 'Miscellaneous', |
| 0xFE: 'Application Specific', |
| 0xFF: 'Vendor Specific' |
| } |
| |
| |
| # ----------------------------------------------------------------------------- |
| def main(): |
| logging.basicConfig(level = os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper()) |
| |
| with usb1.USBContext() as context: |
| bluetooth_device_count = 0 |
| devices = {} |
| |
| for device in context.getDeviceIterator(skip_on_error=True): |
| device_class = device.getDeviceClass() |
| device_subclass = device.getDeviceSubClass() |
| device_protocol = device.getDeviceProtocol() |
| |
| device_id = (device.getVendorID(), device.getProductID()) |
| |
| device_is_bluetooth_hci = ( |
| device_class == USB_DEVICE_CLASS_WIRELESS_CONTROLLER and |
| device_subclass == USB_DEVICE_SUBCLASS_RF_CONTROLLER and |
| device_protocol == USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER |
| ) |
| |
| device_class_details = '' |
| device_class_info = USB_DEVICE_CLASSES.get(device_class) |
| if device_class_info is not None: |
| if type(device_class_info) is tuple: |
| device_class = device_class_info[0] |
| device_subclass_info = device_class_info[1].get(device_subclass) |
| if device_subclass_info: |
| device_class_details = f' [{device_subclass_info.get(device_protocol)}]' |
| else: |
| device_class = device_class_info |
| |
| if device_is_bluetooth_hci: |
| bluetooth_device_count += 1 |
| fg_color = 'black' |
| bg_color = 'yellow' |
| else: |
| fg_color = 'yellow' |
| bg_color = 'black' |
| |
| # Compute the different ways this can be referenced as a Bumble transport |
| bumble_transport_names = [] |
| basic_transport_name = f'usb:{device.getVendorID():04X}:{device.getProductID():04X}' |
| |
| if device_is_bluetooth_hci: |
| bumble_transport_names.append(f'usb:{bluetooth_device_count - 1}') |
| |
| serial_number_collision = False |
| if device_id in devices: |
| for device_serial in devices[device_id]: |
| if device_serial == device.getSerialNumber(): |
| serial_number_collision = True |
| |
| if device_id not in devices: |
| bumble_transport_names.append(basic_transport_name) |
| else: |
| bumble_transport_names.append(f'{basic_transport_name}#{len(devices[device_id])}') |
| |
| if device.getSerialNumber() and not serial_number_collision: |
| bumble_transport_names.append(f'{basic_transport_name}/{device.getSerialNumber()}') |
| |
| print(color(f'ID {device.getVendorID():04X}:{device.getProductID():04X}', fg=fg_color, bg=bg_color)) |
| if bumble_transport_names: |
| print(color(' Bumble Transport Names:', 'blue'), ' or '.join(color(x, 'cyan' if device_is_bluetooth_hci else 'red') for x in bumble_transport_names)) |
| print(color(' Bus/Device: ', 'green'), f'{device.getBusNumber():03}/{device.getDeviceAddress():03}') |
| if device.getSerialNumber(): |
| print(color(' Serial: ', 'green'), device.getSerialNumber()) |
| print(color(' Class: ', 'green'), device_class) |
| print(color(' Subclass/Protocol: ', 'green'), f'{device_subclass}/{device_protocol}{device_class_details}') |
| print(color(' Manufacturer: ', 'green'), device.getManufacturer()) |
| print(color(' Product: ', 'green'), device.getProduct()) |
| print() |
| |
| devices.setdefault(device_id, []).append(device.getSerialNumber()) |
| |
| |
| # ----------------------------------------------------------------------------- |
| if __name__ == '__main__': |
| main() |