Driving blth without hardware#
macOS exposes no raw HCI on its built-in radio, and this stack is an HCI host -- it needs to speak H4 to a controller. This example stands up a software controller with Bumble so you can exercise the whole stack (scan, connect, GATT, pairing) over a socket, no dongle required.
virtual_controller.py builds one simulated RF link with two controllers on it:
- a connectable, pairable peripheral FakeSensor (
F0:F1:F2:F3:F4:F5) with a GATT service (0x1815) holding one readable/writable characteristic; - a bare controller exposed over TCP, which
blthdrives with--tcp.
Run it#
Start the controller (a venv keeps Bumble out of your system Python):
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python3 virtual_controller.py
Then, in another terminal:
blth scan --tcp 127.0.0.1:9000
blth gatt --tcp 127.0.0.1:9000 --peer F0:F1:F2:F3:F4:F5 --peer-random
blth pair --tcp 127.0.0.1:9000 --peer F0:F1:F2:F3:F4:F5 --peer-random \
--random C0:11:22:33:44:55
scanlists FakeSensor with its RSSI and name.gattconnects, discovers the services and characteristics, and reads a value.pairconnects and runs LE Secure Connections Just Works pairing, printing the bond.
Profile the connection data path:
obs run --out /tmp/blth_gatt -- blth gatt --tcp 127.0.0.1:9000 \
--peer F0:F1:F2:F3:F4:F5 --peer-random
obs report /tmp/blth_gatt
Why the address flags#
Bumble's LocalLink sources every LE packet from the sending controller's
random address, so a link peer is only reachable as a random-address device.
That is why the peripheral advertises a static random address and the commands
pass --peer-random; pair additionally needs --random ADDR so our own side
is a random-address device too and the pairing DHKey check (which mixes both
addresses) agrees with the wire. A real controller uses one consistent address
and needs none of this.
Real hardware#
For real over-the-air scanning, plug a USB-UART HCI controller into the Mac
(e.g. an nRF52840 running Zephyr's hci_uart, which enumerates as
/dev/cu.usbmodem*) and drop --tcp for --device /dev/cu.usbmodemXXXX --baud 1000000. A plain USB Bluetooth dongle will not work: macOS claims it as the
system adapter.
On a Linux host (e.g. a Raspberry Pi CM5)#
Linux exposes raw HCI, so blth can drive the host's own radio directly with
--hci N (an HCI user channel over hci<N>). Bring the controller down first so
nothing else manages it:
sudo hciconfig hci0 down # or: sudo btmgmt --index 0 power off
sudo blth serve --hci 0 --name my-cm5
serve advertises a demo GATT server and prints each client connect / write /
subscribe. Now scan and connect from your Mac or phone with any BLE app (or
blth scan on the Mac with an external dongle). --hci also works with scan,
gatt, and pair. On a CM5, plugging in the
external antenna kit (and
enabling it in the firmware config) gives that advertisement real range.