Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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