Skip to content

Instantly share code, notes, and snippets.

@trarbr
Last active June 30, 2022 03:33
Show Gist options
  • Save trarbr/27352eaf7365d8986c17ccc6c2698070 to your computer and use it in GitHub Desktop.
Save trarbr/27352eaf7365d8986c17ccc6c2698070 to your computer and use it in GitHub Desktop.
Debugging Bluetooth with HCI packet logs

Are you using Blue Heron on your Nerves device? Are you having trouble figuring out why things aren't working? Fear not! The HCI packet log may be able to help you. And this gist will let you know how to read it with Wireshark.

The Blue Heron readme states:

This project includes a Logger backend to dump PKTLOG format. This is the same format that Android, IOS, btstack, hcidump, and bluez use.

By default, Blue Heron will write this PKGLOG to /tmp/hcidump.pklg on your Nerves device. You can transfer it to your computer with scp (scp nerves.local:/tmp/hcidump.pklg . should copy it to your local directory).

The HCI packet log contains all packets that cross the Host Controller Interface. The Controller is embedded in hardware and controls the radiowaves used for Bluetooth. Blue Heron acts as Host, and sends commands to (and receives events from) the Controller.

Wireshark is a really useful tool for debugging all kinds of network communication. It's also very handy when debugging Bluetooth, as it can decode the PKGLOG format. It runs on Linux, MacOS, Windows and more.

This gist uses a HCI packet log that was posted to the the nerves-bluetooth Slack channel during a debugging session. The question (paraphrased) was:

Why is my Nerves device not able to connect to my GoPro 8 camera? When I scan for Bluetooth devices I find the camera, but when I call BlueHeron.ATT.Client.create_connection/2, I never get the %ConnectionComplete{} event.

The HCI packet log can be downloaded here: https://paste.c-net.org/TouchesLugosi If you download the file and open it in Wireshark, you'll see something like this:

image

Each row in the table is a packet. If you click on a row, details about the row are shown below.

The first commands (and corresponding events) are sent by Blue Heron to initialize the Controller. This generally works but may require tweaking for certain Bluetooth chips. In this case, the board used is a Raspberry Pi Zero W (not a 2W), and it should Just Work.

Packet 39 is where the real fun starts. A "LE Set Scan Enable" command is sent with scan_enable: true. When the Controller receives this command it starts scanning for advertising Bluetooth devices in range. When the Controller picks up an advertising packet from another device, it is reported back to the Host in "LE Advertising Report" events.

image

Packets 48 and 49 contain advertising information from the GoPro camera. Packet 48 has the BD_ADDR (Bluetooth device address) which you need to connect to the device:

image

In packet 50, the Host tells the Controller to create a connection with the GoPro camera ("LE Create Connection"):

image

You'll note that the "LE Create Connection" uses the same BD_ADDR as that which was received in the "LE Advertising Report".

In packet 55, the Controller replies with a "Command Status" event, saying that the command is pending.

image

One would then expect it to follow up with an "LE Connection Complete" event a little later. This should happen for all connection attempts, even if they fail. However, that did not happen in this case. There was never an "LE Connection Complete" event. Later attempts to send a new "LE Create Connection" command (packet 480) receives a "Command Status" reply (packet 489) with status "Disallowed":

image

This is because the Controller can only handle one "LE Create Connection" attempt at a time, and the first request is still pending.

So why is this not working? Because Bluetooth addressing are not as simply as just knowing the address.

If you go back to the "LE Advertisement Report" in packet 48, you'll note that it says "Peer Address Type: Random Device Address". There are a few different address types in Bluetooth, but I won't get into them here. Instead, I'll simply say that what's in "LE Create Connection" has to match the "LE Advertisement Report". And if you look in the "LE Create Connection" command, you'll note it says "Peer Address Type: Public Device Address". That's because Blue Heron uses that by default. And so the connection attempt will never succeed.

The answer in this case is to add an extra parameter to BlueHeron.ATT.Client.create_connection/2. Instead of BlueHeron.ATT.Client.create_connection(state.conn, peer_address: addr), do: BlueHeron.ATT.Client.create_connection(state.conn, peer_address: addr, peer_address_type: 1)

I hope this walkthrough can help you debug your next issue with Blue Heron. And if you need help, you're always welcome in the nerves-bluetooth Slack channel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment