Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Practical Bluetooth Low Energy on Android

Practical Bluetooth Low Energy on Android

Bluetooth Low Energy was announced for Android 4.3 (Jelly Bean MR2, API level 18) and the API has received some updates with each new version since then. Unfortunately, the way the API works and the number of undocumented "features" in the Bluetooth stack on Android has made it very difficult to work with. Regardless if you're doing something simple like scanning for beacons or doing two-way communciation with another device, you're bound to run into problems that can be both hard to identify and difficult to fix.

In this session we will go through the most common pitfalls regarding Bluetooth LE on Android and how to deal with them. You'll learn how to properly scan for devices, connect to them, and exchange data. An introduction to Bluetooth LE concepts, such as GATT, services, characteristics and descriptors will also be included. After this session, you'll be better prepared to implement Bluetooth LE in your own applications.

Erik Hellman

June 17, 2016
Tweet

More Decks by Erik Hellman

Other Decks in Programming

Transcript

  1. Practical Bluetooth Low Energy
    on Android
    Erik Hellman
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  2. Harald "Bluetooth" Gormsson
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  3. ~ 1000 years later...
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  4. 1994 - Bluetooth is invented
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  5. Mr Bluetooth, Jim Kardach, Intel
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  6. The Logo
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  7. Versions
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  8. Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  9. Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  10. Beacon vs. Peripheral
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  11. Beacon Advertisements
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  12. BLE Advertisement Packets
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  13. Advertisement payload
    · 128-bit Service UUIDs
    · 16-bit Service Class UUID
    · Shortened Local Name
    · Complete Local Name
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  14. 16-bit UUID?
    Regular UUID:
    75BEB663-74FC-4871-9737-AD184157450E
    Bluetooth SIG base UUID:
    XXXXXXXX-0000-1000-8000-00805F9B34FB
    32 bits le!, top 16 are always 0
    0x180D (Heart Rate Service) is the same as
    0000180D-0000-1000-8000-00805F9B34FB
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  15. Pro tip 1: Advertisement != Actual
    The advertisement does not have to match
    the actual services on a peripheral!
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  16. Scanning on Android (API lvl 21+) - Basic
    private void startScanningForDevices(BluetoothAdapter adapter) {
    bluetoothLeScanner = adapter.getBluetoothLeScanner();
    callback = new MyScanCallback(this);
    bluetoothLeScanner.startScan(callback);
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  17. Scanning on Android - Name or address
    ScanSettings scanSettings = new ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build();
    // Name or address of peripherla is known
    ScanFilter.Builder builder = new ScanFilter.Builder();
    builder.setDeviceAddress(deviceAddress); // MAC address
    builder.setDeviceName(deviceName); // Name of device
    ScanFilter scanFilter = builder.build();
    bluetoothLeScanner.startScan(Collections.singletonList(scanFilter),
    scanSettings, callback);
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  18. Scanning on Android - Service UUID
    ScanFilter scanFilter = new ScanFilter.Builder()
    .setServiceUuid(SERVICE_UUID)
    .build();
    // Peripheral advertisement contains a service UUID
    bluetoothLeScanner.startScan(Collections.singletonList(scanFilter),
    scanSettings, callback);
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  19. Scanning on Android - Service data
    // Scan for peripherals with a certain state in the serviceData
    ScanFilter scanFilter = new ScanFilter.Builder()
    .setServiceData(SERVICE_DATA_UUID, serviceData)
    .build();
    bluetoothLeScanner.startScan(Collections.singletonList(scanFilter),
    scanSettings, new MyLeScanCallback());
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  20. Scanning on Android - Manufacturer data
    // Scan for specific manufacturer data
    ScanFilter scanFilter = new ScanFilter.Builder()
    .setManufacturerData(MFR_ID, mfrData)
    .build();
    bluetoothLeScanner.startScan(Collections.singletonList(scanFilter),
    scanSettings, new MyLeScanCallback());
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  21. ScanCallback
    public class MyScanCallback extends ScanCallback {
    // fields and constructor omitted
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
    // Will execute on the main thread!
    // Do the work below on a worker thread instead!
    if(shouldWeConnect(result)) {
    BluetoothDevice device = result.getDevice();
    scanner.stopScan(this);
    device.connectGatt(context, false, gattCallback);
    }
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  22. ScanResult - Manufacturer data
    private boolean shouldWeConnect(ScanResult result) {
    ScanRecord scanRecord = result.getScanRecord();
    byte[] mfrData = scanRecord.getManufacturerSpecificData(MFR_ID);
    List serviceUuids = scanRecord.getServiceUuids()
    Map serviceData = scanRecord.getServiceDate();
    return // some custom filtering...;
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  23. Pro tip 2: Skip discovery if possible!
    If you can assume the device is present:
    public void connectToDevice(BluetoothAdapter adapter, String address,
    MyGattCallback gattCallback) {
    BluetoothDevice device = adapter.getRemoteDevice(address);
    // TODO Set a timeout somewhere...
    device.connectGatt(context, false, gattCallback);
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  24. Peripheral Structure
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  25. Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  26. GATT on Android
    1. Connect to GATT
    2. Discover services
    3. Enable notifications
    4. Start communicating!
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  27. private void connectToPeripheral(BluetoothDevice device) {
    MyGattCallback gattCallback = new MyGattCallback();
    // Always use false on auto-connect parameter
    device.connectGatt(context, false, gattCallback);
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  28. Pro tip nr 3: Don't use auto-
    connect
    Auto-connect is flaky and doesn't behave as expected.
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  29. BluetoothGattCallback
    public class MyGattCallback extends BluetoothGattCallback {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    super.onConnectionStateChange(gatt, status, newState);
    if(newState == BluetoothProfile.STATE_CONNECTED) {
    // DON'T CALL THIS HERE!!!
    gatt.discoverServices();
    Log.d(TAG, "Callback on thread: " + Thread.currentThread().getName());
    }
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  30. Pro tip nr 4: BLE and Threads
    All BLE callbacks happen on a Binder thread!
    Never block the BluetoothGattCallback methods!
    Use a HandlerThread for all GATT operations!
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  31. Android BLE and Threads 1/3
    public class MyGattCallback extends BluetoothGattCallback implements Handler.Callback {
    public static final int MSG_DISCOVER_SERVICES = 10;
    private Handler bleHandler;
    public MyGattCallback() {
    HandlerThread handlerThread = new HandlerThread("BLE-Worker");
    handlerThread.start();
    bleHandler = new Handler(handlerThread.getLooper(), this);
    }
    public void dispose() {
    // TODO Probably disconnect as well?
    bleHandler.removeCallbacksAndMessages(null);
    bleHandler.getLooper().quit();
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  32. Android BLE and Threads 2/3
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt,
    int status, int newState) {
    super.onConnectionStateChange(gatt, status, newState);
    if(newState == BluetoothProfile.STATE_CONNECTED) {
    bleHandler.obtainMessage(MSG_DISCOVER_SERVICES, gatt).sendToTarget();
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  33. Android BLE and Threads 3/3
    @Override
    public boolean handleMessage(Message msg) {
    switch (msg.what) {
    case MSG_DISCOVER_SERVICES:
    BluetoothGatt gatt = (BluetoothGatt) msg.obj;
    gatt.discoverServices();
    break;
    }
    return true;
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  34. Ready to read and write!
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    super.onServicesDiscovered(gatt, status);
    bleHandler.obtainMessage(MSG_SERVICES_DISCOVERED, gatt).sendToTarget();
    }
    @Override
    public boolean handleMessage(Message msg) {
    switch (msg.what) {
    case MSG_SERVICES_DISCOVERED:
    // Discovery successful, fetch services, chars and descs...
    BluetoothGatt gatt = (BluetoothGatt) msg.obj;
    commService = gatt.getService(DATA_SERVICE_ID);
    inputChar = commService.getCharacteristic(INPUT_CHAR_ID);
    outputChar = commService.getCharacteristic(OUTPUT_CHAR_ID);
    break;
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  35. Pro tip 5: Services must be
    discovered!
    discoverServices() must be
    called before communicating
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  36. Reading data is asynchronous
    public void readData(BluetoothGatt gatt,
    BluetoothGattCharacteristic c) {
    gatt.readCharacteristic(c);
    }
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
    BluetoothGattCharacteristic c,
    int status) {
    if(status == BluetoothGatt.GATT_SUCCESS
    && INPUT_CHAR_UUID.equals(c.getUuid()) {
    byte[] value = c.getValue();
    bleHandler.obtainMessage(MSG_NOTIFY_DATA_CHANGED, value)
    .sendToTarget();
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  37. Subscribing for data change notifications
    private void enableDataNotifications(BluetoothGatt gatt
    BluetoothGattCharacteristic c) {
    gatt.setCharacteristicNotification(c, true);
    // This is usually needed as well...
    BluetoothGattDescriptor desc = c.getDescriptor(INPUT_DESC_ID);
    // Could also be ENABLE_NOTIFICATION_VALUE
    desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    gatt.writeDescriptor(desc);
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  38. Subscribing for data change notifications
    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
    BluetoothGattCharacteristic c) {
    super.onCharacteristicChanged(gatt, c);
    if(INPUT_CHAR_UUID.equals(c.getUuid()) {
    byte[] value = c.getValue();
    bleHandler.obtainMessage(MSG_NOTIFY_DATA_CHANGED, value)
    .sendToTarget();
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  39. Pro tip 6: If possible, enable
    notifications
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  40. Writing data (max 20 bytes)
    // Should always be called on our BLE thread!
    private void writeDataToChar(byte[] data) {
    if(data.length > 20) {
    throw new IllegalArgumentException();
    }
    outputChar.setValue(data);
    gatt.writeCharacteristic(characteristic);
    }
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt,
    BluetoothGattCharacteristic c,
    int status) {
    if (status == BluetoothGatt.GATT_SUCCESS
    && c.getUuid().equals(outputChar.getUuid())) {
    // Write operation successful, proceed with next chunk!
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  41. Pro tip 7: 20 byte write limit
    The max size of a write operation is 20 bytes!
    (except for devices supporting
    BluetoothGatt.requestMtu())
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  42. close() vs disconnect()
    close() == "unregister app"
    disconnect() == cut connection
    When in doubt, do both.
    gatt.disconnect();
    gatt.close();
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  43. Errors!
    Sony Mobile owners will see this...
    03-06 13:00:11.994: D/BluetoothGatt(26771): registerApp()
    03-06 13:00:11.994: D/BluetoothGatt(26771): registerApp() - UUID='...'
    03-06 13:00:12.004: D/BluetoothGatt(26771): onClientRegistered() - status=0 clientIf=5
    03-06 13:00:42.004: D/BluetoothGatt(26771): onClientConnectionState() - status=133 clientIf=5 device='...'
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  44. Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  45. Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  46. Pro tip 8: Most error codes are
    undocumented!
    Treat everything but GATT_SUCCESS as error
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  47. Pro tip 9: When an error occurs;
    disconnect, close and
    reconnect.
    Most BLE errors are "unrecoverable"
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  48. Debugging
    · Don't put breakpoints in your
    BluetoothGattCallback callbacks
    · Log all BluetoothGattCallback calls and
    their data
    · Enable Bluetooth HCI snoop log
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  49. Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide

  50. Thank you for listening!
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View Slide