forked from auracaster/bumble_mirror
* add usb_probe tool and improve compatibility with older/non-compliant devices * fix logic test * add doc
158 lines
6.5 KiB
Python
158 lines
6.5 KiB
Python
# 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()
|