Practical Bluetooth Low Energy on Android

Practical Bluetooth Low Energy
on Android
Erik Hellman
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
History - Harald "Bluetooth" Gormsson
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
~ 1000 years later...
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
History - Bluetooth is invented
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Mr Bluetooth, Jim Kardach, Intel
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
The Logo
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Versions
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Beacon vs. Peripheral
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Beacon Advertisements
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
BLE Advertisement Packets
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
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
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
Scanning on Android - Basic
private void startScanningForDevices(BluetoothAdapter adapter) {
bluetoothLeScanner = adapter.getBluetoothLeScanner();
callback = new MyScanCallback(this, new MyGattCallback());
bluetoothLeScanner.startScan(callback);
}
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
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
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
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
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();
device.connectGatt(context, false, gattCallback);
scanner.stopScan(this);
}
}
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
ScanResult - Manufacturer data
private boolean shouldWeConnect(ScanResult result) {
ScanRecord scanRecord = result.getScanRecord();
byte[] mfrData = scanRecord.getManufacturerSpecificData(MFR_ID);
List<ParcelUuid> serviceUuids = scanRecord.getServiceUuids()
Map<ParcelUuid, byte[]> serviceData = scanRecord.getServiceDate();
}
return // some custom filtering...;
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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(this, false, gattCallback);
}
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Peripheral Structure
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
GATT simplified
BluetoothGatt
The device
BluetoothService
Collection of I/O endpoints
BluetoothCharacteristic
I/O endpoint
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
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 DO THIS!
gatt.discoverServices();
}
}
Log.d(TAG, "Callback on thread: " + Thread.currentThread().getName());
}
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Pro-tip nr 3: BLE and Threads
All callbacks happen on a binder Thread!
Never block the BluetoothGattCallback!
Use a HandlerThread for all GATT operations!
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
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
Android BLE and Threads 3/3
@Override
public boolean handleMessage(Message msg) {
BluetoothGatt gatt = (BluetoothGatt) msg.obj;
switch (msg.what) {
case MSG_DISCOVER_SERVICES:
gatt.discoverServices();
break;
}
return true;
}
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
Pro-tip 4: Services must be
discovered!
discoverServices() must be
called before communicating
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
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
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
Pro-tip 5: If possible, enable
notifications
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
Pro-tip 6: 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
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
Errors!
Sony Mobile owners will see this...
03-06
03-06
03-06
03-06
13:00:11.994:
13:00:11.994:
13:00:12.004:
13:00:42.004:
D/BluetoothGatt(26771):
D/BluetoothGatt(26771):
D/BluetoothGatt(26771):
D/BluetoothGatt(26771):
registerApp()
registerApp() - UUID='...'
onClientRegistered() - status=0 clientIf=5
onClientConnectionState() - status=133 clientIf=5 device='...'
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Pro-tip 7: Most error codes are
undocumented!
Treat everything but GATT_SUCCESS as error
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Pro-tip 8: When an error occurs;
disconnect, close and
reconnect.
Most BLE errors are "unrecoverable"
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
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
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se
Thank you for listening!
Practical Bluetooth Low Energy on Android - @ErikHellman / www.hellso!.se