forked from auracaster/bumble_mirror
Compare commits
8 Commits
v0.0.122
...
gbg/improv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ff52df8bd | ||
|
|
86618e52ef | ||
|
|
fbb46dd736 | ||
|
|
d1e119f176 | ||
|
|
2fc7a0bf04 | ||
|
|
d6c4644b23 | ||
|
|
df1962e8da | ||
|
|
d2227f017f |
@@ -90,7 +90,7 @@ class SnoopPacketReader:
|
|||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--format', type=click.Choice(['h4', 'snoop']), default='h4', help='Format of the input file')
|
@click.option('--format', type=click.Choice(['h4', 'snoop']), default='h4', help='Format of the input file')
|
||||||
@click.argument('filename')
|
@click.argument('filename')
|
||||||
def show(format, filename):
|
def main(format, filename):
|
||||||
input = open(filename, 'rb')
|
input = open(filename, 'rb')
|
||||||
if format == 'h4':
|
if format == 'h4':
|
||||||
packet_reader = PacketReader(input)
|
packet_reader = PacketReader(input)
|
||||||
@@ -117,4 +117,4 @@ def show(format, filename):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
show()
|
main()
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
import click
|
||||||
import usb1
|
import usb1
|
||||||
from colors import color
|
from colors import color
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@ from colors import color
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Constants
|
# Constants
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
USB_DEVICE_CLASS_DEVICE = 0x00
|
||||||
USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xE0
|
USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xE0
|
||||||
USB_DEVICE_SUBCLASS_RF_CONTROLLER = 0x01
|
USB_DEVICE_SUBCLASS_RF_CONTROLLER = 0x01
|
||||||
USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER = 0x01
|
USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER = 0x01
|
||||||
@@ -75,9 +78,81 @@ USB_DEVICE_CLASSES = {
|
|||||||
0xFF: 'Vendor Specific'
|
0xFF: 'Vendor Specific'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
USB_ENDPOINT_IN = 0x80
|
||||||
|
USB_ENDPOINT_TYPES = ['CONTROL', 'ISOCHRONOUS', 'BULK', 'INTERRUPT']
|
||||||
|
|
||||||
|
USB_BT_HCI_CLASS_TUPLE = (
|
||||||
|
USB_DEVICE_CLASS_WIRELESS_CONTROLLER,
|
||||||
|
USB_DEVICE_SUBCLASS_RF_CONTROLLER,
|
||||||
|
USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
def main():
|
def show_device_details(device):
|
||||||
|
for configuration in device:
|
||||||
|
print(f' Configuration {configuration.getConfigurationValue()}')
|
||||||
|
for interface in configuration:
|
||||||
|
for setting in interface:
|
||||||
|
alternateSetting = setting.getAlternateSetting()
|
||||||
|
suffix = f'/{alternateSetting}' if interface.getNumSettings() > 1 else ''
|
||||||
|
(class_string, subclass_string) = get_class_info(
|
||||||
|
setting.getClass(),
|
||||||
|
setting.getSubClass(),
|
||||||
|
setting.getProtocol()
|
||||||
|
)
|
||||||
|
details = f'({class_string}, {subclass_string})'
|
||||||
|
print(f' Interface: {setting.getNumber()}{suffix} {details}')
|
||||||
|
for endpoint in setting:
|
||||||
|
endpoint_type = USB_ENDPOINT_TYPES[endpoint.getAttributes() & 3]
|
||||||
|
endpoint_direction = 'OUT' if (endpoint.getAddress() & USB_ENDPOINT_IN == 0) else 'IN'
|
||||||
|
print(f' Endpoint 0x{endpoint.getAddress():02X}: {endpoint_type} {endpoint_direction}')
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def get_class_info(cls, subclass, protocol):
|
||||||
|
class_info = USB_DEVICE_CLASSES.get(cls)
|
||||||
|
protocol_string = ''
|
||||||
|
if class_info is None:
|
||||||
|
class_string = f'0x{cls:02X}'
|
||||||
|
else:
|
||||||
|
if type(class_info) is tuple:
|
||||||
|
class_string = class_info[0]
|
||||||
|
subclass_info = class_info[1].get(subclass)
|
||||||
|
if subclass_info:
|
||||||
|
protocol_string = subclass_info.get(protocol)
|
||||||
|
if protocol_string is not None:
|
||||||
|
protocol_string = f' [{protocol_string}]'
|
||||||
|
|
||||||
|
else:
|
||||||
|
class_string = class_info
|
||||||
|
|
||||||
|
subclass_string = f'{subclass}/{protocol}{protocol_string}'
|
||||||
|
|
||||||
|
return (class_string, subclass_string)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
def is_bluetooth_hci(device):
|
||||||
|
# Check if the device class indicates a match
|
||||||
|
if (device.getDeviceClass(), device.getDeviceSubClass(), device.getDeviceProtocol()) == USB_BT_HCI_CLASS_TUPLE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# If the device class is 'Device', look for a matching interface
|
||||||
|
if device.getDeviceClass() == USB_DEVICE_CLASS_DEVICE:
|
||||||
|
for configuration in device:
|
||||||
|
for interface in configuration:
|
||||||
|
for setting in interface:
|
||||||
|
if (setting.getClass(), setting.getSubClass(), setting.getProtocol()) == USB_BT_HCI_CLASS_TUPLE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
@click.command()
|
||||||
|
@click.option('--verbose', is_flag=True, default=False, help='Print more details')
|
||||||
|
def main(verbose):
|
||||||
logging.basicConfig(level = os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
|
logging.basicConfig(level = os.environ.get('BUMBLE_LOGLEVEL', 'WARNING').upper())
|
||||||
|
|
||||||
with usb1.USBContext() as context:
|
with usb1.USBContext() as context:
|
||||||
@@ -91,23 +166,28 @@ def main():
|
|||||||
|
|
||||||
device_id = (device.getVendorID(), device.getProductID())
|
device_id = (device.getVendorID(), device.getProductID())
|
||||||
|
|
||||||
device_is_bluetooth_hci = (
|
(device_class_string, device_subclass_string) = get_class_info(
|
||||||
device_class == USB_DEVICE_CLASS_WIRELESS_CONTROLLER and
|
device_class,
|
||||||
device_subclass == USB_DEVICE_SUBCLASS_RF_CONTROLLER and
|
device_subclass,
|
||||||
device_protocol == USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
|
device_protocol
|
||||||
)
|
)
|
||||||
|
|
||||||
device_class_details = ''
|
try:
|
||||||
device_class_info = USB_DEVICE_CLASSES.get(device_class)
|
device_serial_number = device.getSerialNumber()
|
||||||
if device_class_info is not None:
|
except usb1.USBError:
|
||||||
if type(device_class_info) is tuple:
|
device_serial_number = None
|
||||||
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
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
device_manufacturer = device.getManufacturer()
|
||||||
|
except usb1.USBError:
|
||||||
|
device_manufacturer = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
device_product = device.getProduct()
|
||||||
|
except usb1.USBError:
|
||||||
|
device_product = None
|
||||||
|
|
||||||
|
device_is_bluetooth_hci = is_bluetooth_hci(device)
|
||||||
if device_is_bluetooth_hci:
|
if device_is_bluetooth_hci:
|
||||||
bluetooth_device_count += 1
|
bluetooth_device_count += 1
|
||||||
fg_color = 'black'
|
fg_color = 'black'
|
||||||
@@ -123,33 +203,35 @@ def main():
|
|||||||
if device_is_bluetooth_hci:
|
if device_is_bluetooth_hci:
|
||||||
bumble_transport_names.append(f'usb:{bluetooth_device_count - 1}')
|
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:
|
if device_id not in devices:
|
||||||
bumble_transport_names.append(basic_transport_name)
|
bumble_transport_names.append(basic_transport_name)
|
||||||
else:
|
else:
|
||||||
bumble_transport_names.append(f'{basic_transport_name}#{len(devices[device_id])}')
|
bumble_transport_names.append(f'{basic_transport_name}#{len(devices[device_id])}')
|
||||||
|
|
||||||
if device.getSerialNumber() and not serial_number_collision:
|
if device_serial_number is not None:
|
||||||
bumble_transport_names.append(f'{basic_transport_name}/{device.getSerialNumber()}')
|
if device_id not in devices or device_serial_number not in devices[device_id]:
|
||||||
|
bumble_transport_names.append(f'{basic_transport_name}/{device_serial_number}')
|
||||||
|
|
||||||
|
# Print the results
|
||||||
print(color(f'ID {device.getVendorID():04X}:{device.getProductID():04X}', fg=fg_color, bg=bg_color))
|
print(color(f'ID {device.getVendorID():04X}:{device.getProductID():04X}', fg=fg_color, bg=bg_color))
|
||||||
if bumble_transport_names:
|
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(' 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}')
|
print(color(' Bus/Device: ', 'green'), f'{device.getBusNumber():03}/{device.getDeviceAddress():03}')
|
||||||
if device.getSerialNumber():
|
print(color(' Class: ', 'green'), device_class_string)
|
||||||
print(color(' Serial: ', 'green'), device.getSerialNumber())
|
print(color(' Subclass/Protocol: ', 'green'), device_subclass_string)
|
||||||
print(color(' Class: ', 'green'), device_class)
|
if device_serial_number is not None:
|
||||||
print(color(' Subclass/Protocol: ', 'green'), f'{device_subclass}/{device_protocol}{device_class_details}')
|
print(color(' Serial: ', 'green'), device_serial_number)
|
||||||
print(color(' Manufacturer: ', 'green'), device.getManufacturer())
|
if device_manufacturer is not None:
|
||||||
print(color(' Product: ', 'green'), device.getProduct())
|
print(color(' Manufacturer: ', 'green'), device_manufacturer)
|
||||||
|
if device_product is not None:
|
||||||
|
print(color(' Product: ', 'green'), device_product)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
show_device_details(device)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
devices.setdefault(device_id, []).append(device.getSerialNumber())
|
devices.setdefault(device_id, []).append(device_serial_number)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ logger = logging.getLogger(__name__)
|
|||||||
async def open_usb_transport(spec):
|
async def open_usb_transport(spec):
|
||||||
'''
|
'''
|
||||||
Open a USB transport.
|
Open a USB transport.
|
||||||
The parameter string has this syntax:
|
The moniker string has this syntax:
|
||||||
either <index> or
|
either <index> or
|
||||||
<vendor>:<product> or
|
<vendor>:<product> or
|
||||||
<vendor>:<product>/<serial-number>] or
|
<vendor>:<product>/<serial-number>] or
|
||||||
@@ -47,15 +47,21 @@ async def open_usb_transport(spec):
|
|||||||
/<serial-number> suffix or #<index> suffix max be specified when more than one device with
|
/<serial-number> suffix or #<index> suffix max be specified when more than one device with
|
||||||
the same vendor and product identifiers are present.
|
the same vendor and product identifiers are present.
|
||||||
|
|
||||||
|
In addition, if the moniker ends with the symbol "!", the device will be used in "forced" mode:
|
||||||
|
the first USB interface of the device will be used, regardless of the interface class/subclass.
|
||||||
|
This may be useful for some devices that use a custom class/subclass but may nonetheless work as-is.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
0 --> the first BT USB dongle
|
0 --> the first BT USB dongle
|
||||||
04b4:f901 --> the BT USB dongle with vendor=04b4 and product=f901
|
04b4:f901 --> the BT USB dongle with vendor=04b4 and product=f901
|
||||||
04b4:f901#2 --> the third USB device with vendor=04b4 and product=f901
|
04b4:f901#2 --> the third USB device with vendor=04b4 and product=f901
|
||||||
04b4:f901/00E04C239987 --> the BT USB dongle with vendor=04b4 and product=f901 and serial number 00E04C239987
|
04b4:f901/00E04C239987 --> the BT USB dongle with vendor=04b4 and product=f901 and serial number 00E04C239987
|
||||||
|
usb:0B05:17CB! --> the BT USB dongle vendor=0B05 and product=17CB, in "forced" mode.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
USB_RECIPIENT_DEVICE = 0x00
|
USB_RECIPIENT_DEVICE = 0x00
|
||||||
USB_REQUEST_TYPE_CLASS = 0x01 << 5
|
USB_REQUEST_TYPE_CLASS = 0x01 << 5
|
||||||
|
USB_DEVICE_CLASS_DEVICE = 0x00
|
||||||
USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xE0
|
USB_DEVICE_CLASS_WIRELESS_CONTROLLER = 0xE0
|
||||||
USB_DEVICE_SUBCLASS_RF_CONTROLLER = 0x01
|
USB_DEVICE_SUBCLASS_RF_CONTROLLER = 0x01
|
||||||
USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER = 0x01
|
USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER = 0x01
|
||||||
@@ -63,6 +69,12 @@ async def open_usb_transport(spec):
|
|||||||
USB_ENDPOINT_TRANSFER_TYPE_INTERRUPT = 0x03
|
USB_ENDPOINT_TRANSFER_TYPE_INTERRUPT = 0x03
|
||||||
USB_ENDPOINT_IN = 0x80
|
USB_ENDPOINT_IN = 0x80
|
||||||
|
|
||||||
|
USB_BT_HCI_CLASS_TUPLE = (
|
||||||
|
USB_DEVICE_CLASS_WIRELESS_CONTROLLER,
|
||||||
|
USB_DEVICE_SUBCLASS_RF_CONTROLLER,
|
||||||
|
USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
|
||||||
|
)
|
||||||
|
|
||||||
READ_SIZE = 1024
|
READ_SIZE = 1024
|
||||||
|
|
||||||
class UsbPacketSink:
|
class UsbPacketSink:
|
||||||
@@ -280,6 +292,13 @@ async def open_usb_transport(spec):
|
|||||||
context.open()
|
context.open()
|
||||||
try:
|
try:
|
||||||
found = None
|
found = None
|
||||||
|
|
||||||
|
if spec.endswith('!'):
|
||||||
|
spec = spec[:-1]
|
||||||
|
forced_mode = True
|
||||||
|
else:
|
||||||
|
forced_mode = False
|
||||||
|
|
||||||
if ':' in spec:
|
if ':' in spec:
|
||||||
vendor_id, product_id = spec.split(':')
|
vendor_id, product_id = spec.split(':')
|
||||||
serial_number = None
|
serial_number = None
|
||||||
@@ -291,10 +310,14 @@ async def open_usb_transport(spec):
|
|||||||
device_index = int(device_index_str)
|
device_index = int(device_index_str)
|
||||||
|
|
||||||
for device in context.getDeviceIterator(skip_on_error=True):
|
for device in context.getDeviceIterator(skip_on_error=True):
|
||||||
|
try:
|
||||||
|
device_serial_number = device.getSerialNumber()
|
||||||
|
except usb1.USBError:
|
||||||
|
device_serial_number = None
|
||||||
if (
|
if (
|
||||||
device.getVendorID() == int(vendor_id, 16) and
|
device.getVendorID() == int(vendor_id, 16) and
|
||||||
device.getProductID() == int(product_id, 16) and
|
device.getProductID() == int(product_id, 16) and
|
||||||
(serial_number is None or device.getSerialNumber() == serial_number)
|
(serial_number is None or serial_number == device_serial_number)
|
||||||
):
|
):
|
||||||
if device_index == 0:
|
if device_index == 0:
|
||||||
found = device
|
found = device
|
||||||
@@ -302,13 +325,27 @@ async def open_usb_transport(spec):
|
|||||||
device_index -= 1
|
device_index -= 1
|
||||||
device.close()
|
device.close()
|
||||||
else:
|
else:
|
||||||
|
# Look for a compatible device by index
|
||||||
|
def device_is_bluetooth_hci(device):
|
||||||
|
# Check if the device class indicates a match
|
||||||
|
if (device.getDeviceClass(), device.getDeviceSubClass(), device.getDeviceProtocol()) == \
|
||||||
|
USB_BT_HCI_CLASS_TUPLE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# If the device class is 'Device', look for a matching interface
|
||||||
|
if device.getDeviceClass() == USB_DEVICE_CLASS_DEVICE:
|
||||||
|
for configuration in device:
|
||||||
|
for interface in configuration:
|
||||||
|
for setting in interface:
|
||||||
|
if (setting.getClass(), setting.getSubClass(), setting.getProtocol()) == \
|
||||||
|
USB_BT_HCI_CLASS_TUPLE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
device_index = int(spec)
|
device_index = int(spec)
|
||||||
for device in context.getDeviceIterator(skip_on_error=True):
|
for device in context.getDeviceIterator(skip_on_error=True):
|
||||||
if (
|
if device_is_bluetooth_hci(device):
|
||||||
device.getDeviceClass() == USB_DEVICE_CLASS_WIRELESS_CONTROLLER and
|
|
||||||
device.getDeviceSubClass() == USB_DEVICE_SUBCLASS_RF_CONTROLLER and
|
|
||||||
device.getDeviceProtocol() == USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
|
|
||||||
):
|
|
||||||
if device_index == 0:
|
if device_index == 0:
|
||||||
found = device
|
found = device
|
||||||
break
|
break
|
||||||
@@ -329,9 +366,8 @@ async def open_usb_transport(spec):
|
|||||||
setting = None
|
setting = None
|
||||||
for setting in interface:
|
for setting in interface:
|
||||||
if (
|
if (
|
||||||
setting.getClass() != USB_DEVICE_CLASS_WIRELESS_CONTROLLER or
|
not forced_mode and
|
||||||
setting.getSubClass() != USB_DEVICE_SUBCLASS_RF_CONTROLLER or
|
(setting.getClass(), setting.getSubClass(), setting.getProtocol()) != USB_BT_HCI_CLASS_TUPLE
|
||||||
setting.getProtocol() != USB_DEVICE_PROTOCOL_BLUETOOTH_PRIMARY_CONTROLLER
|
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ nav:
|
|||||||
- HCI Bridge: apps_and_tools/hci_bridge.md
|
- HCI Bridge: apps_and_tools/hci_bridge.md
|
||||||
- Golden Gate Bridge: apps_and_tools/gg_bridge.md
|
- Golden Gate Bridge: apps_and_tools/gg_bridge.md
|
||||||
- Show: apps_and_tools/show.md
|
- Show: apps_and_tools/show.md
|
||||||
|
- GATT Dump: apps_and_tools/gatt_dump.md
|
||||||
|
- Pair: apps_and_tools/pair.md
|
||||||
|
- Unbond: apps_and_tools/unbond.md
|
||||||
|
- USB Probe: apps_and_tools/usb_probe.md
|
||||||
- Hardware:
|
- Hardware:
|
||||||
- Overview: hardware/index.md
|
- Overview: hardware/index.md
|
||||||
- Platforms:
|
- Platforms:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# This requirements file is for python3
|
# This requirements file is for python3
|
||||||
mkdocs == 1.2.3
|
mkdocs == 1.4.0
|
||||||
mkdocs-material == 7.1.7
|
mkdocs-material == 8.5.6
|
||||||
mkdocs-material-extensions == 1.0.1
|
mkdocs-material-extensions == 1.0.3
|
||||||
pymdown-extensions == 8.2
|
pymdown-extensions == 9.6
|
||||||
mkdocstrings == 0.15.1
|
mkdocstrings-python == 0.7.1
|
||||||
@@ -13,17 +13,29 @@ type of device (there's no way to tell).
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
This command line tool takes no arguments.
|
This command line tool may be invoked with no arguments, or with `--verbose`
|
||||||
|
for extra details.
|
||||||
When installed from PyPI, run as
|
When installed from PyPI, run as
|
||||||
```
|
```
|
||||||
$ bumble-usb-probe
|
$ bumble-usb-probe
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or, for extra details, with the `--verbose` argument
|
||||||
|
```
|
||||||
|
$ bumble-usb-probe --v
|
||||||
|
```
|
||||||
|
|
||||||
When running from the source distribution:
|
When running from the source distribution:
|
||||||
```
|
```
|
||||||
$ python3 apps/usb-probe.py
|
$ python3 apps/usb-probe.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3 apps/usb-probe.py --verbose
|
||||||
|
```
|
||||||
|
|
||||||
!!! example
|
!!! example
|
||||||
```
|
```
|
||||||
$ python3 apps/usb_probe.py
|
$ python3 apps/usb_probe.py
|
||||||
|
|||||||
@@ -1,48 +1,86 @@
|
|||||||
:material-linux: LINUX PLATFORM
|
:material-linux: LINUX PLATFORM
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
In addition to all the standard functionality available from the project by running the python tools and/or writing your own apps by leveraging the API, it is also possible on Linux hosts to interface the Bumble stack with the native BlueZ stack, and with Bluetooth controllers.
|
Using Bumble With Physical Bluetooth Controllers
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
Using Bumble With BlueZ
|
A Bumble application can interface with a local Bluetooth controller on a Linux host.
|
||||||
-----------------------
|
The 3 main types of physical Bluetooth controllers are:
|
||||||
|
|
||||||
A Bumble virtual controller can be attached to the BlueZ stack.
|
* Bluetooth USB Dongle
|
||||||
Attaching a controller to BlueZ can be done by either simulating a UART HCI interface, or by using the VHCI driver interface if available.
|
* HCI over UART (via a serial port)
|
||||||
In both cases, the controller can run locally on the Linux host, or remotely on a different host, with a bridge between the remote controller and the local BlueZ host, which may be useful when the BlueZ stack is running on an embedded system, or a host on which running the Bumble controller is not convenient.
|
* Kernel-managed Bluetooth HCI (HCI Sockets)
|
||||||
|
|
||||||
### Using VHCI
|
!!! tip "Conflicts with the kernel and BlueZ"
|
||||||
|
If your use a USB dongle that is recognized by your kernel as a supported Bluetooth device, it is
|
||||||
|
likely that the kernel driver will claim that USB device and attach it to the BlueZ stack.
|
||||||
|
If you want to claim ownership of it to use with Bumble, you will need to set the state of the corresponding HCI interface as `DOWN`.
|
||||||
|
HCI interfaces are numbered, starting from 0 (i.e `hci0`, `hci1`, ...).
|
||||||
|
|
||||||
With the [VHCI transport](../transports/vhci.md) you can attach a Bumble virtual controller to the BlueZ stack. Once attached, the controller will appear just like any other controller, and thus can be used with the standard BlueZ tools.
|
For example, to bring `hci0` down:
|
||||||
|
|
||||||
!!! example "Attaching a virtual controller"
|
|
||||||
With the example app `run_controller.py`:
|
|
||||||
```
|
```
|
||||||
PYTHONPATH=. python3 examples/run_controller.py F6:F7:F8:F9:FA:FB examples/device1.json vhci
|
$ sudo hciconfig hci0 down
|
||||||
```
|
|
||||||
|
|
||||||
You should see a 'Virtual Bus' controller. For example:
|
|
||||||
```
|
|
||||||
$ hciconfig
|
|
||||||
hci0: Type: Primary Bus: Virtual
|
|
||||||
BD Address: F6:F7:F8:F9:FA:FB ACL MTU: 27:64 SCO MTU: 0:0
|
|
||||||
UP RUNNING
|
|
||||||
RX bytes:0 acl:0 sco:0 events:43 errors:0
|
|
||||||
TX bytes:274 acl:0 sco:0 commands:43 errors:0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And scanning for devices should show the virtual 'Bumble' device that's running as part of the `run_controller.py` example app:
|
You can use the `hciconfig` command with no arguments to get a list of HCI interfaces seen by
|
||||||
|
the kernel.
|
||||||
|
|
||||||
|
Also, if `bluetoothd` is running on your system, it will likely re-claim the interface after you
|
||||||
|
close it, so you may need to bring the interface back `UP` before using it again, or to disable
|
||||||
|
`bluetoothd` altogether (see the section further below about BlueZ and `bluetoothd`).
|
||||||
|
|
||||||
|
### Using a USB Dongle
|
||||||
|
|
||||||
|
See the [USB Transport page](../transports/usb.md) for general information on how to use HCI USB controllers.
|
||||||
|
|
||||||
|
!!! tip "USB Permissions"
|
||||||
|
By default, when running as a regular user, you won't have the permission to use
|
||||||
|
arbitrary USB devices.
|
||||||
|
You can change the permissions for a specific USB device based on its bus number and
|
||||||
|
device number (you can use `lsusb` to find the Bus and Device numbers for your Bluetooth
|
||||||
|
dongle).
|
||||||
|
|
||||||
|
Example:
|
||||||
```
|
```
|
||||||
pi@raspberrypi:~ $ sudo hcitool -i hci2 lescan
|
$ sudo chmod o+w /dev/bus/usb/001/004
|
||||||
LE Scan ...
|
|
||||||
F0:F1:F2:F3:F4:F5 Bumble
|
|
||||||
```
|
```
|
||||||
|
This will change the permissions for Device 4 on Bus 1.
|
||||||
|
|
||||||
|
Note that the USB Bus number and Device number may change depending on where you plug the USB
|
||||||
|
dongle and what other USB devices and hubs are also plugged in.
|
||||||
|
|
||||||
|
If you need to make the permission changes permanent across reboots, you can create a `udev`
|
||||||
|
rule for your specific Bluetooth dongle. Visit [this Arch Linux Wiki page](https://wiki.archlinux.org/title/udev) for a
|
||||||
|
good overview of how you may do that.
|
||||||
|
|
||||||
|
### Using HCI over UART
|
||||||
|
|
||||||
|
See the [Serial Transport page](../transports/serial.md) for general information on how to use HCI over a UART (serial port).
|
||||||
|
|
||||||
### Using HCI Sockets
|
### Using HCI Sockets
|
||||||
|
|
||||||
HCI sockets provide a way to send/receive HCI packets to/from a Bluetooth controller managed by the kernel.
|
HCI sockets provide a way to send/receive HCI packets to/from a Bluetooth controller managed by the kernel.
|
||||||
The HCI device referenced by an `hci-socket` transport (`hciX`, where `X` is an integer, with `hci0` being the first controller device, and so on) must be in the `DOWN` state before it can be opened as a transport.
|
See the [HCI Socket Transport page](../transports/hci_socket.md) for details on the `hci-socket` tansport syntax.
|
||||||
You can bring a HCI controller `UP` or `DOWN` with `hciconfig`.
|
|
||||||
|
|
||||||
|
The HCI device referenced by an `hci-socket` transport (`hci<X>`, where `<X>` is an integer, with `hci0` being the first controller device, and so on) must be in the `DOWN` state before it can be opened as a transport.
|
||||||
|
You can bring a HCI controller `UP` or `DOWN` with `hciconfig hci<X> up` and `hciconfig hci<X> up`.
|
||||||
|
|
||||||
|
!!! tip "HCI Socket Permissions"
|
||||||
|
By default, when running as a regular user, you won't have the permission to use
|
||||||
|
an HCI socket to a Bluetooth controller (you may see an exception like `PermissionError: [Errno 1] Operation not permitted`).
|
||||||
|
|
||||||
|
If you want to run without using `sudo`, you need to manage the capabilities by adding the appropriate entries in `/etc/security/capability.conf` to grant a user or group the `cap_net_admin` capability.
|
||||||
|
See [this manpage](https://manpages.ubuntu.com/manpages/bionic/man5/capability.conf.5.html) for details.
|
||||||
|
|
||||||
|
Alternatively, if you are just experimenting temporarily, the `capsh` command may be useful in order
|
||||||
|
to execute a single command with enhanced permissions, as in this example:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" --keep=1 --user=$USER --addamb=cap_net_admin -- -c "<path/to/executable> <executable-args>"
|
||||||
|
```
|
||||||
|
Where `<path/to/executable>` is the path to your `python3` executable or to one of the Bumble bundled command-line applications.
|
||||||
|
|
||||||
!!! tip "List all available controllers"
|
!!! tip "List all available controllers"
|
||||||
The command
|
The command
|
||||||
```
|
```
|
||||||
@@ -72,29 +110,16 @@ You can bring a HCI controller `UP` or `DOWN` with `hciconfig`.
|
|||||||
```
|
```
|
||||||
$ hciconfig hci0 down
|
$ hciconfig hci0 down
|
||||||
```
|
```
|
||||||
(or `hciX` with `X` being the index of the controller device you want to use), but a simpler solution is to just stop the `bluetoothd` daemon, with a command like:
|
(or `hci<X>` with `<X>` being the index of the controller device you want to use), but a simpler solution is to just stop the `bluetoothd` daemon, with a command like:
|
||||||
```
|
```
|
||||||
$ sudo systemctl stop bluetooth.service
|
$ sudo systemctl stop bluetooth.service
|
||||||
```
|
```
|
||||||
You can always re-start the daemon with
|
You can always re-start the daemon with
|
||||||
```
|
```
|
||||||
$ sudo systemctl start bluetooth.service
|
$ sudo systemctl start bluetooth.service
|
||||||
```
|
|
||||||
|
|
||||||
### Using a Simulated UART HCI
|
Bumble on the Raspberry Pi
|
||||||
|
--------------------------
|
||||||
### Bridge to a Remote Controller
|
|
||||||
|
|
||||||
|
|
||||||
Using Bumble With Bluetooth Controllers
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
A Bumble application can interface with a local Bluetooth controller.
|
|
||||||
If your Bluetooth controller is a standard HCI USB controller, see the [USB Transport page](../transports/usb.md) for details on how to use HCI USB controllers.
|
|
||||||
If your Bluetooth controller is a standard HCI UART controller, see the [Serial Transport page](../transports/serial.md).
|
|
||||||
Alternatively, a Bumble Host object can communicate with one of the platform's controllers via an HCI Socket.
|
|
||||||
|
|
||||||
`<details to be filled in>`
|
|
||||||
|
|
||||||
### Raspberry Pi 4 :fontawesome-brands-raspberry-pi:
|
### Raspberry Pi 4 :fontawesome-brands-raspberry-pi:
|
||||||
|
|
||||||
@@ -102,9 +127,10 @@ You can use the Bluetooth controller either via the kernel, or directly to the d
|
|||||||
|
|
||||||
#### Via The Kernel
|
#### Via The Kernel
|
||||||
|
|
||||||
Use an HCI Socket transport
|
Use an HCI Socket transport (see section above)
|
||||||
|
|
||||||
#### Directly
|
#### Directly
|
||||||
|
|
||||||
In order to use the Bluetooth controller directly on a Raspberry Pi 4 board, you need to ensure that it isn't being used by the BlueZ stack (which it probably is by default).
|
In order to use the Bluetooth controller directly on a Raspberry Pi 4 board, you need to ensure that it isn't being used by the BlueZ stack (which it probably is by default).
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -136,3 +162,47 @@ should detach the controller from the stack, after which you can use the HCI UAR
|
|||||||
python3 run_scanner.py serial:/dev/serial1,3000000
|
python3 run_scanner.py serial:/dev/serial1,3000000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Using Bumble With BlueZ
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
In addition to all the standard functionality available from the project by running the python tools and/or writing your own apps by leveraging the API, it is also possible on Linux hosts to interface the Bumble stack with the native BlueZ stack, and with Bluetooth controllers.
|
||||||
|
|
||||||
|
A Bumble virtual controller can be attached to the BlueZ stack.
|
||||||
|
Attaching a controller to BlueZ can be done by either simulating a UART HCI interface, or by using the VHCI driver interface if available.
|
||||||
|
In both cases, the controller can run locally on the Linux host, or remotely on a different host, with a bridge between the remote controller and the local BlueZ host, which may be useful when the BlueZ stack is running on an embedded system, or a host on which running the Bumble controller is not convenient.
|
||||||
|
|
||||||
|
### Using VHCI
|
||||||
|
|
||||||
|
With the [VHCI transport](../transports/vhci.md) you can attach a Bumble virtual controller to the BlueZ stack. Once attached, the controller will appear just like any other controller, and thus can be used with the standard BlueZ tools.
|
||||||
|
|
||||||
|
!!! example "Attaching a virtual controller"
|
||||||
|
With the example app `run_controller.py`:
|
||||||
|
```
|
||||||
|
python3 examples/run_controller.py F6:F7:F8:F9:FA:FB examples/device1.json vhci
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see a 'Virtual Bus' controller. For example:
|
||||||
|
```
|
||||||
|
$ hciconfig
|
||||||
|
hci0: Type: Primary Bus: Virtual
|
||||||
|
BD Address: F6:F7:F8:F9:FA:FB ACL MTU: 27:64 SCO MTU: 0:0
|
||||||
|
UP RUNNING
|
||||||
|
RX bytes:0 acl:0 sco:0 events:43 errors:0
|
||||||
|
TX bytes:274 acl:0 sco:0 commands:43 errors:0
|
||||||
|
```
|
||||||
|
|
||||||
|
And scanning for devices should show the virtual 'Bumble' device that's running as part of the `run_controller.py` example app:
|
||||||
|
```
|
||||||
|
pi@raspberrypi:~ $ sudo hcitool -i hci2 lescan
|
||||||
|
LE Scan ...
|
||||||
|
F0:F1:F2:F3:F4:F5 Bumble
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a Simulated UART HCI
|
||||||
|
|
||||||
|
### Bridge to a Remote Controller
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ The USB transport interfaces with a local Bluetooth USB dongle.
|
|||||||
|
|
||||||
## Moniker
|
## Moniker
|
||||||
The moniker for a USB transport is either:
|
The moniker for a USB transport is either:
|
||||||
|
|
||||||
* `usb:<index>`
|
* `usb:<index>`
|
||||||
* `usb:<vendor>:<product>`
|
* `usb:<vendor>:<product>`
|
||||||
* `usb:<vendor>:<product>/<serial-number>`
|
* `usb:<vendor>:<product>/<serial-number>`
|
||||||
@@ -16,6 +17,10 @@ In the `usb:<vendor>:<product>#<index>` form, matching devices are the ones with
|
|||||||
|
|
||||||
`<vendor>` and `<product>` are a vendor ID and product ID in hexadecimal.
|
`<vendor>` and `<product>` are a vendor ID and product ID in hexadecimal.
|
||||||
|
|
||||||
|
In addition, if the moniker ends with the symbol "!", the device will be used in "forced" mode:
|
||||||
|
the first USB interface of the device will be used, regardless of the interface class/subclass.
|
||||||
|
This may be useful for some devices that use a custom class/subclass but may nonetheless work as-is.
|
||||||
|
|
||||||
!!! examples
|
!!! examples
|
||||||
`usb:04b4:f901`
|
`usb:04b4:f901`
|
||||||
The USB dongle with `<vendor>` equal to `04b4` and `<product>` equal to `f901`
|
The USB dongle with `<vendor>` equal to `04b4` and `<product>` equal to `f901`
|
||||||
@@ -29,6 +34,10 @@ In the `usb:<vendor>:<product>#<index>` form, matching devices are the ones with
|
|||||||
`usb:04b4:f901/#1`
|
`usb:04b4:f901/#1`
|
||||||
The second USB dongle with `<vendor>` equal to `04b4` and `<product>` equal to `f901`
|
The second USB dongle with `<vendor>` equal to `04b4` and `<product>` equal to `f901`
|
||||||
|
|
||||||
|
`usb:0B05:17CB!`
|
||||||
|
The BT USB dongle vendor=0B05 and product=17CB, in "forced" mode.
|
||||||
|
|
||||||
|
|
||||||
## Alternative
|
## Alternative
|
||||||
The library includes two different implementations of the USB transport, implemented using different python bindings for `libusb`.
|
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.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user