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
  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