Making Sense of Bluetooth Low Energy Advertisements

Blog Post 3: Parsing BLE Advertisements

In this series of posts I’m describing the implementation of an Android application that scans for BLE devices and displays the result in a list. The code for the application is here. Below is a screenshot of the application after scanning. In this screenshot the application has found three broadcasting BLE devices.

blescan

If you read my other posts, you’ve got an understanding of the preparation code for scanning and the scanning process. Now I’m going to talk about the format and BLE Advertisements and how to parse them.

All the code we are discussing lives in GitHub here.

When scanning for BLE devices in Android an app finds out about them via the callback interface BluetoothAdapter.LeScanCallback. Below is the callback implementation in my app. This code is found in the ScanActivity class.

Notice in the code that on every callback, if we haven’t already put the device in our list all we do is create a DeviceScanData() object and put it in a list. (And notify the ListView that the underlying data has changed and that it should refresh.)

BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback(){
  @Override
  public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {

    if (!contains(deviceScanList, device)){
      DeviceScanData discovered = new DeviceScanData(device, rssi, new Advertisement(scanRecord));
      deviceScanList.add(discovered);
      // update the ui but make sure it happens on the UI thread
      Handler mainThreadHandler = new Handler(ScanActivity.this.getMainLooper());
      mainThreadHandler.post(new Runnable() {
        @Override
        public void run() {
         devicesAdapter.notifyDataSetChanged();
        }
      });
    }
  }
};

The class DeviceScanData is just a model object to hold all the data returned by a scan, namely the device, the rssi and the scanRecord. But it doesn’t just hold the scanRecord, notice that we create an instance of com.ranchosoftware.ranchobleexample.utility.Advertisement from the scanRecord. For completeness sake, below is the DeviceScanData class.

public class DeviceScanData {
  private BluetoothDevice device;
  private Advertisement advertisement;
  private int rssi;

  public DeviceScanData(BluetoothDevice device, int rssi, Advertisement advertisement){
    this.device = device;
    this.advertisement = advertisement;
    this.rssi = rssi;
  }

  public BluetoothDevice getDevice() {
    return device;
  }

  public Advertisement getAdvertisement() {
    return advertisement;
  }

  public int getRssi() {
    return rssi;
  }
}

Notice that member advertisement. This class is meant to wrap and parse the scanRecord returned by the scan callback. The scanRecord first comes to us from the callback as an array of bytes. This array must be carefully parsed to extract the data values. The format of the array is a repeated sequence of records where the first byte of the record defines the length of the record, the second byte is a code that defines the type of the record and the rest of the record is in a format that depends on the type. So assuming we have a byte array called record, each ad record maps out like this:

record[0]: contains the byte length of the record, call this n.
record[1]: contains an AD Type code for the record. This value also defines the expected format for the data to follow.
record[2 to n-1]: contains the data. Format is dictated by the AD Type. The formats for the AD Types are found in this Bluetooth Specification.

In my app, I have defined an enumeration, GattAdType that has all the possible AD Types values. Not all can actually be present in the primary advertisement, only a subset. The list of code values is defined here . The code for GattAdType is show below. Notice that this is an enumeration not just a list of int manifest constants. Each enumeration has maps to a “code” which is the byte value of the AD Type. This idiom of creating manifest constants as an enumeration rather than simply ints is just aimed at providing a little more type safety — although at a minor price in code complexity.

The enum values shown in  red below are the types that are allowed in the primary advertisement broadcast. But remember that all values are optional, and the length of the broadcast data has limits so, in practice the records are designed to be as minimal as possible.

public enum GattAdType {

  // Values take from https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile

  Flags((byte) 0x01),
  IncompleteListof16_bitServiceClassUUIDs((byte) 0x02),
  CompleteListof16_bitServiceClassUUIDs((byte) 0x03),
  IncompleteListof32_bitServiceClassUUIDs((byte) 0x04),
  CompleteListof32_bitServiceClassUUIDs((byte) 0x05),
  IncompleteListof128_bitServiceClassUUIDs ((byte) 0x06),
  CompleteListof128_bitServiceClassUUIDs ((byte) 0x07),
  ShortenedLocalName ((byte) 0x08),
  CompleteLocalName((byte) 0x09),
  TxPowerLevel ((byte) 0x0A),
  ClassofDevice((byte) 0x0D),
  SimplePairingHashC ((byte) 0x0E),
  SimplePairingHashC_192 ((byte) 0x0E),
  SimplePairingRandomizerR ((byte) 0x0F),
  SimplePairingRandomizerR_192 ((byte) 0x0F),
  DeviceID ((byte) 0x10),
  SecurityManagerTKValue ((byte) 0x10),
  SecurityManagerOutofBandFlags((byte) 0x11),
  SlaveConnectionIntervalRange ((byte) 0x12),
  Listof16_bitServiceSolicitationUUIDs ((byte) 0x14),
  Listof32_bitServiceSolicitationUUIDs ((byte) 0x1F),
  Listof128_bitServiceSolicitationUUIDs((byte) 0x15),
  ServiceData((byte) 0x16),
  ServiceData_16_bitUUID ((byte) 0x16),
  ServiceData_32_bitUUID ((byte) 0x20),
  ServiceData_128_bitUUID((byte) 0x21),
  PublicTargetAddress((byte) 0x17),
  RandomTargetAddress((byte) 0x18),
  Appearance ((byte) 0x19),
  AdvertisingInterval((byte) 0x1A),
  LEBluetoothDeviceAddress ((byte) 0x1B),
  LERole ((byte) 0x1C),
  SimplePairingHashC_256 ((byte) 0x1D),
  SimplePairingRandomizerR_256 ((byte) 0x1E),
  ThreeDInformationData((byte) 0x3D),
  ManufacturerSpecificData ((byte) 0xFF);

  private final byte code ;
  GattAdType(byte code){
    this.code = code;
  }

  public int getCode(){
    return code;
  }

  public static GattAdType forCode(int code)
  {
    for (GattAdType type : GattAdType.values())
    {
      if (type.getCode() == code)
        return type;
    }
    return null;
  }

When the app receives the scanRecord byte array in the LeScanCallback, the record simply contains a series concatenated AD Type records. We need code to parse out each individual AD Type record. That is the job of the class com.ranchosoftware.ranchobleexample.utility.Advertisement. This class attempts to parse the relevant AD Records and translate the byte array into a more convenient representation. The code for the parsing is relatively complex (and maybe could be refactored to be simpler) and the class is relatively long, so I refer you to the GitHub repo to view the code.

There is one additional capability within Advertisement worth discussing — Apple Beacons, aka iBeacon.  An Apple beacon device uses the advertising record to send out a Manufacturer Specific Data record that follows an Apple defined format. The format has been reverse engineered by many. Here’s a link on stack overflow that explains the format. When the Advertisement class receives manufacturer specific data, it attempts to determine if the data is likely to be an Apple Beacon. If it seems to fit the pattern, it is parsed and translated into an com.ranchosoftware.ranchobleexample.utility.IBeaconAdvertisement object. That object is designed to be a concrete representation of an Apple IBeacon. The code is below

public class IBeaconAdvertisement {
  private String company;
  private UUID proximityUuid;
  private int major;
  private int minor;
  private int calibratedPower;

  IBeaconAdvertisement(String company, UUID proximityUuid, int major, int minor, int calibratedPower){
    this.company = company;
    this.proximityUuid = proximityUuid;
    this.major = major;
    this.minor = minor;
    this.calibratedPower = calibratedPower;
  }

  public String getCompany() {
    return company;
  }

  public UUID getProximityUuid() {
    return proximityUuid;
  }

  public int getMajor() {
    return major;
  }

  public int getMinor() {
    return minor;
  }

  public int getCalibratedPower() {
    return calibratedPower;
  }
}

 

With that, I’ve just finished discussing all the important parts of the RanchoBleExample. I hope you’ve enjoyed the discussion and the code.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s