Scanning for Bluetooth Beacons on the Linux command line

Overview

Small Single Board Computers (SBC) such as Raspberry Pi 3 (RPi3) with their built in BLE 4.0 controllers are enough to scan for Bluetooth Low Energy beacons such as those that use the Eddystone format beacons.

>

The BlueZ maintainers however are not so keen on supporting scanning for beacons with their command=line tools as there is a concern that such activity is quite resource intensive.

Many tutorials on the internet are done with command-line tools with the now deprecated tools of hcitool and hcidump. This 'HowTo' looks at how to scan for beacons without using those tools.

Scanning for Beacons

The go-to tool when using Bluetooth from the Linux command-line is bluetoothctl.

In a terminal use the BlueZ command-line tool to put the RPi3 into scanning mode by issuing the `scan on` command. It is also a good idea to clear all filtering options. My session looked like this:

    pi@RPi3:~ $ bluetoothctl
    [bluetooth]# set-scan-filter-clear
    SetDiscoveryFilter success
    [bluetooth]# scan on
    Discovery started
    [CHG] Controller B8:27:EB:22:57:E0 Discovering: yes
    [CHG] Device F1:55:90:65:29:DC RSSI: -55
    [CHG] Device F1:55:90:65:29:DC ServiceData Key: 0000feaa-0000-1000-8000-00805f9b34fb
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0xf6
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x21
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x09
    [bluetooth]# scan off

The 16-bit UUID for the Eddystone Service is 0xfeaa.

Once we know the Service UUID we can filter to see just Eddystone beacons in bluetoothctl. For example:

    [bluetooth]# set-scan-filter-uuids 0xfeaa
    SetDiscoveryFilter success
    [bluetooth]# scan on
    Discovery started
    [CHG] Controller B8:27:EB:22:57:E0 Discovering: yes
    [CHG] Device F1:55:90:65:29:DC RSSI: -76
    [CHG] Device F1:55:90:65:29:DC RSSI: -78
    [CHG] Device F1:55:90:65:29:DC ServiceData Key: 0000feaa-0000-1000-8000-00805f9b34fb
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0xf6
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x21
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x0a

In this example it has not made any difference. However, if you have other Bluetooth devices advertising nearby it will be very helpful to reduce how much is reported.

Detailed Reporting of Bluetooth Activity

To view all the activity on the RPi's Bluetooth controller there is a Bluetooth monitoring tool from BlueZ called "btmon". This has a very verbose output as it does tell us everything that is happening with the Bluetooth controller and is typically used for debugging low-level issues.

To run btmon in a terminal use the following command:

sudo btmon

With bluetoothctl scanning in the first terminal, btmon will report such things as an "LE Advertising Report" which will contain information about nearby devices that are advertising. Our beacon should be in amongst all the output in btmon. For example:

    > HCI Event: LE Meta Event (0x3e) plen 41                    [hci0] 1316.838336
          LE Advertising Report (0x02)
            Num reports: 1
            Event type: Non connectable undirected - ADV_NONCONN_IND (0x03)
            Address type: Random (0x01)
            Address: F1:55:90:65:29:DC (Static)
            Data length: 29
            Flags: 0x06
              LE General Discoverable Mode
              BR/EDR Not Supported
            16-bit Service UUIDs (complete): 1 entry
              Google (0xfeaa)
            Service Data (UUID 0xfeaa): 00f600000000000000000021000000000009
            RSSI: -55 dBm (0xc9)

Duplicates

Beacons are broadcasting/advertising very frequently (every second or so) and you will notice that the BlueZ tools are not reporting all occurrences of those adverts. This is because the BlueZ tools are filtering duplicate adverts. This is done because of concerns about the machine resources being consumed in doing such activity.

The BlueZ project has deprecated the command-line tool hcitool that did allow this. This is because the tool used the Host Controller Interface (HCI) which is a very low-level interface and did not protect the user from being able to do very bad things to their system. The new BlueZ tools give the user more protection however it does mean the tools do not report duplicates.

If you still have hcitool on your system then you can see every broadcast from your beacon show up in btmon using the following command:
    sudo hcitool lescan --duplicates

Some of the Bluetooth libraries that act as scanners can read all beacon broadcasts.
One of the best Python libraries I've found for scanning for beacons is aioblescan.

Other Beacon Types

Much of what is described above should work for any BLE beacons. I've focused on Eddystone as it is an open specification and most widely supported. One interesting place that has Eddystone support is the MakeCode editor for micro:bit which supports very easy access to programming Eddystone beacons.

Jos Ryke (@josryke) has posted on Twitter a great summary on how the packets vary between the different beacon types.

Beacon Packet Summary

With the aid of Jos's diagram and the Generic Access Profile specification we can see that Eddystone adverts have a type of 03 (Complete List of 16-bit Service Class UUIDs) where as ibeacon has a type of FF (Manufacturer Specific Data).

This means data being broadcast is reported slightly differently. For example, for ibeacon the Major and Minor values are Manufacturer Data while for Eddystone UID Namespace and Instance values are Service Data.

In bluetoothctl this presents itself as the following:
For ibeacon:

    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Key: 0x004c
    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Value: 0x10
    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Value: 0x05
    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Value: 0x0b
    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Value: 0x10
    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Value: 0x88
    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Value: 0xed
    [CHG] Device 56:9E:9F:3D:3A:EB ManufacturerData Value: 0x6e
    [CHG] Device 56:9E:9F:3D:3A:EB RSSI: -80
And for Eddystone UID:
    [CHG] Device F1:55:90:65:29:DC ServiceData Key: 0000feaa-0000-1000-8000-00805f9b34fb
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0xf6
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x21
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x00
    [CHG] Device F1:55:90:65:29:DC ServiceData Value: 0x09

By looking at the list Company Identifiers we can learn that the ManufacturerData Key of 0x004c is for Apple.

license