Compare commits

...

8 Commits

Author SHA1 Message Date
Gilles Boccon-Gibod
6ff52df8bd better/safer Linux recommendations 2022-10-10 20:11:55 -07:00
Gilles Boccon-Gibod
86618e52ef Refactor and improve the doc for Bumble on Linux 2022-10-09 12:56:06 -07:00
Gilles Boccon-Gibod
fbb46dd736 Merge pull request #41 from google/gbg/cli-scripts
use arg-less main() functions in all scripts
2022-10-07 16:16:35 -07:00
Gilles Boccon-Gibod
d1e119f176 use arg-less main() functions in all scripts 2022-10-07 13:56:42 -07:00
Gilles Boccon-Gibod
2fc7a0bf04 Merge pull request #39 from google/gbg/usb-descriptors
improve USB device detection logic
2022-10-06 15:39:32 -07:00
Gilles Boccon-Gibod
d6c4644b23 reorder the order of printing 2022-10-06 10:40:28 -07:00
Octavian Purdila
df1962e8da apps/usb_probe.py: handle libusb1 exceptions
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.
2022-10-04 23:38:13 +00:00
Gilles Boccon-Gibod
d2227f017f improve USB device detection logic 2022-10-04 09:59:48 -07:00
9 changed files with 308 additions and 95 deletions

View File

@@ -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()

View File

@@ -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)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -1,47 +1,85 @@
: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: You can use the `hciconfig` command with no arguments to get a list of HCI interfaces seen by
``` the kernel.
$ 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: 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

View File

@@ -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.

View File

@@ -67,6 +67,6 @@ development =
invoke >= 1.4 invoke >= 1.4
nox >= 2022 nox >= 2022
documentation = documentation =
mkdocs >= 1.2.3 mkdocs >= 1.4.0
mkdocs-material >= 8.1.9 mkdocs-material >= 8.5.6
mkdocstrings[python] >= 0.19.0 mkdocstrings[python] >= 0.19.0