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

Avatar for Erik Hellman

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
  2. 1994 - Bluetooth is invented Practical Bluetooth Low Energy on

    Android - @ErikHellman / www.hellso!.se
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. Pro tip 5: Services must be discovered! discoverServices() must be

    called before communicating Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
  19. 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
  20. 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
  21. 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
  22. Pro tip 6: If possible, enable notifications Practical Bluetooth Low

    Energy on Android - @ErikHellman / www.hellso!.se
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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