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

Practical Bluetooth LE on Android - GDG Tartu

Practical Bluetooth LE on Android - GDG Tartu

How to use Bluetooth LE on Android - from GDG Tartu, Aug 3, 2017

Erik Hellman

August 03, 2017
Tweet

More Decks by Erik Hellman

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. 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 full-size slide

  15. 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 full-size slide

  16. 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 full-size slide

  17. 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 full-size slide

  18. Scanning on Android - Filters
    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
    builder.setServiceUuid(SERVICE_UUID);
    builder.setServiceData(SERVICE_UUID, serviceData);
    ScanFilter scanFilter = builder.build();
    bluetoothLeScanner.startScan(Collections.singletonList(scanFilter),
    scanSettings, callback);
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

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

    View full-size slide

  20. Doing Beacons?
    Use a third-party service!
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. 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 full-size slide

  24. private void connectToPeripheral(BluetoothDevice device) {
    MyGattCallback gattCallback = new MyGattCallback();
    // Learn what auto-connect parameter means!
    device.connectGatt(context, true, gattCallback);
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

  25. Pro tip nr 3:
    Understand auto-connect
    Sort of light-weight disocvery, but not really...
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. Android BLE and Threads 1/3
    public class MyGattCallback extends BluetoothGattCallback implements Handler.Callback {
    public static final int MSG_CONNECT = 10;
    public static final int MSG_DISCOVER_SERVICES = 20;
    private Handler bleHandler;
    private Context context;
    public MyGattCallback(Context context) {
    this.context = context;
    HandlerThread handlerThread = new HandlerThread("BLE-Worker");
    handlerThread.start();
    bleHandler = new Handler(handlerThread.getLooper(), this);
    }
    public void connect(String address) {
    bleHandler.obtainMessage(MSG_CONNECT, address)
    .sendToTarget();
    }
    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 full-size slide

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

    View full-size slide

  30. Android BLE and Threads 3/3
    @Override
    public boolean handleMessage(Message msg) {
    switch (msg.what) {
    case MSG_CONNECT:
    BluetoothManager manager = (BluetoothManager) context
    .getSystemService(BLUETOOTH_SERVICE);
    BluetoothAdapter adapter = manager.getAdapter();
    BluetoothDevice device = adapter.getRemoteDevice(address);
    device.connectGatt(context, true, gattCallback);
    break;
    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 full-size slide

  31. 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_CONNECT:
    ...
    case MSG_DISCOVER_SERVICES:
    ...
    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 full-size slide

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

    View full-size slide

  33. Reading data is asynchronous
    private 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 full-size slide

  34. 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);
    desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    gatt.writeDescriptor(desc);
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  37. 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 full-size slide

  38. 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 full-size slide

  39. 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 full-size slide

  40. Event-driven BLE (1)
    public abstract class GattEvent {
    public final Status status;
    public final GattPeripheral peripheral;
    public GattEvent(GattPeripheral peripheral, Status status) {
    this.peripheral = peripheral;
    this.status = status;
    }
    public enum Status {
    Success, Failed
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

  41. Event-driven BLE (2)
    public class ConnectionChangedEvent extends GattEvent {
    public enum State {
    Connected, Disconnected, Connecting, Disconnecting;
    }
    public final State newState;
    public ConnectionChangedEvent(GattPeripheral peripheral,
    Status status, State newState) {
    super(peripheral, status);
    this.newState = newState;
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

  42. Event-driven BLE (3)
    public class GattPeripheral {
    private final GattCallback gattCallback;
    private BluetoothGatt bluetoothGatt;
    private Handler leHandler;
    public GattPeripheral(BluetoothGatt bluetoothGatt, Handler leHandler) {
    this.bluetoothGatt = bluetoothGatt;
    this.leHandler = leHandler;
    gattCallback = new GattCallback();
    }
    private class GattCallback extends BluetoothGattCallback {
    // callback implementation here...
    }
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

  43. Event-driven BLE (4)
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    super.onConnectionStateChange(gatt, status, newState);
    ConnectionChangedEvent.State state = null;
    switch (newState) {
    ...
    }
    GattEvent.Status gattStatus = ...
    ConnectionChangedEvent event
    = new ConnectionChangedEvent(GattPeripheral.this, gattStatus, state);
    // TODO: Send event somewhere!
    }
    Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se

    View full-size slide

  44. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  47. 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 full-size slide

  48. 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 full-size slide

  49. 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 full-size slide

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

    View full-size slide

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

    View full-size slide