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

Tech Talk: BLE from Android side

Tech Talk: BLE from Android side

When great technology lacks clear explanation and suffers from bugs and fragmentation then suffer we as developers.
Tech Talk starts from the basics of BLE in the context of Android and touches more abstract topics, e.g. BLE protocols and overall stack architecture.

Slava Petrochenko

September 24, 2018
Tweet

More Decks by Slava Petrochenko

Other Decks in Technology

Transcript

  1. > < next previous Typical use of BLE ‣ Sports

    and fitness devices ‣ Home automation ‣ Remote controls ‣ Medical reporting and monitoring devices ‣ PC peripherals (wireless mouse, keyboard etc)
  2. ‣ Supports Android 4.3+ ‣ Ugly API ‣ Tons of

    bugs ‣ Scattered (sometimes misleading) resources ‣ Great technology with no actual alternative > < next previous Android & BLE - key points
  3. Peripheral device (usually a server) Actors > < next previous

    ‣ Low-energy device ‣ Acts as a database, serving data to the clients ‣ Can be connected to multiple central devices (Bluetooth 4.1+)
  4. Central device (usually a client) Actors > < next previous

    ‣ High-powered device ‣ Requests information from device acting as a server ‣ Can be connected to multiple peripheral devices
  5. Roles > < next previous ‣ Central/peripheral relates to connection

    ‣ Client/server relates to data exchange ‣ Completely independent (e.g. peripheral can play a role of server and client at the same time)
  6. > < next previous GATT attributes, service ‣ Identified by

    128 (16 for adopted) bits UUID ‣ Logically groups data (characteristics) ‣ Can include other services (reusability)
  7. > < next previous GATT attributes, characteristic ‣ Also identified

    by 128 (16 for adopted) bits UUID ‣ Single value field ‣ Multiple (if necessary) descriptors ‣ Properties (read/write/notify/indicate/broadcast etc)
  8. > < next previous GATT service, example Service name: Distance

    Service Characteristic: Steps Count: Value: number of steps (bytes) Descriptors: ‣ value format ‣ value range ‣ value description Properties: Read/write/notify Characteristic: Running Distance: Value: number of miles (bytes) Descriptors: ‣ … (the same) Properties: … ( the same) ANY REFERENCE TO REAL SERVICES IS PURELY COINCIDENTAL
  9. > < next previous Android (central) device steps ‣ Permissions

    and checks ‣ Devices discovery (scanning) ‣ Connection establishment ‣ Services discovery ‣ Performing operations ‣ Connection teardown
  10. > < next previous Permissions and checks ‣ “android.permission.BLUETOOTH" (normal)

    ‣ “android.permission.BLUETOOTH_ADMIN" (normal) ‣ “android.permission.ACCESS_COARSE{FINE}_LOCATION” (dangerous) 
 (location must be ON, otherwise scan won’t work) ‣ <uses-feature android:name=“android.hardware.bluetooth_le" android:required=“true”/> 
 (if optional, runtime check could be used)
  11. > < next previous Devices discovery (scanning) val filters =

    arrayListOf<ScanFilter>() val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .build() val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner bluetoothLeScanner.startScan(filters, settings, scanCallback)
  12. > < next previous Devices discovery (scanning) val filters =

    arrayListOf<ScanFilter>() val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .build() val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner bluetoothLeScanner.startScan(filters, settings, scanCallback)
  13. > < next previous Devices discovery (scanning) val filters =

    arrayListOf<ScanFilter>() val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .build() val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner bluetoothLeScanner.startScan(filters, settings, scanCallback)
  14. > < next previous Devices discovery (scanning) val filters =

    arrayListOf<ScanFilter>() val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .build() val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner bluetoothLeScanner.startScan(filters, settings, scanCallback)
  15. > < next previous Devices discovery (scanning) val filters =

    arrayListOf<ScanFilter>() val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .build() val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner bluetoothLeScanner.startScan(filters, settings, scanCallback)
  16. > < next previous Devices discovery (scanning) val scanCallback =

    object : ScanCallback() { override fun onScanResult(callbackType: Int, scanResult: ScanResult) {A super.onScanResult(callbackType, scanResult) }A override fun onBatchScanResults(results: List<ScanResult>) { super.onBatchScanResults(results) } override fun onScanFailed(errorCode: Int) { super.onScanFailed(errorCode) } }
  17. > < next previous Connection establishment val scanCallback = object

    : ScanCallback() { override fun onScanResult(callbackType: Int, scanResult: ScanResult) {A super.onScanResult(callbackType, scanResult) val bluetoothDevice = scanResult.device bluetoothDevice.connectGatt( applicationContext, /** autoConnect flag */ false, gattClientCallback ) }A override fun onBatchScanResults(results: List<ScanResult>) { super.onBatchScanResults(results) } override fun onScanFailed(errorCode: Int) { super.onScanFailed(errorCode) } }
  18. > < next previous Connection establishment, mysterious autoConnect flag when

    (autoConnect) { true -> {A \ • Connection attempt without timeout (but usually a bit slower) }A false -> {A }A }A
  19. > < next previous Connection establishment, mysterious autoConnect flag when

    (autoConnect) { true -> {A\ • Connection attempt without timeout (but usually a bit slower) • Automatic reconnection happens (unless connection wasn’t closed manually) }A false -> {A }A }A
  20. > < next previous Connection establishment, mysterious autoConnect flag when

    (autoConnect) { true -> {A\ • Connection attempt without timeout (but usually a bit slower) • Automatic reconnection happens (unless connection wasn’t closed manually) • Connection attempt is added to a white list }A false -> {A }A }A
  21. > < next previous Connection establishment, mysterious autoConnect flag when

    (autoConnect) { true -> {A\ • Connection attempt without timeout (but usually a bit slower) • Automatic reconnection happens (unless connection wasn’t closed manually) • Connection attempt is added to a white list }A false -> {A • Direct connection attempt with timeout (about 30s) }A }A
  22. > < next previous Connection establishment, mysterious autoConnect flag when

    (autoConnect) { true -> {A\ • Connection attempt without timeout (but usually a bit slower) • Automatic reconnection happens (unless connection wasn’t closed manually) • Connection attempt is added to a white list }A false -> {A • Direct connection attempt with timeout (about 30s) • No reconnection attempt }A }A
  23. > < next previous Connection establishment, mysterious autoConnect flag when

    (autoConnect) { true -> {A\ • Connection attempt without timeout (but usually a bit slower) • Automatic reconnection happens (unless connection wasn’t closed manually) • Connection attempt is added to a white list }A false -> {A • Direct connection attempt with timeout (about 30s) • No reconnection attempt • Queued up and waits until previous direct connection attempts finish }A }A
  24. > < next previous Connection establishment, mysterious autoConnect flag when

    (autoConnect) { true -> {A\ • Connection attempt without timeout (but usually a bit slower) • Automatic reconnection happens (unless connection wasn’t closed manually) • Connection attempt is added to a white list and queued up }A false -> {A • Direct connection attempt with timeout (about 30s) • No reconnection attempt • Queued up and waits until previous direct connection attempts finish }A }A Practically unusable on pre-Lollipop devices
  25. > < next previous Connection establishment override fun onScanResult(callbackType: Int,

    result: ScanResult) { super.onScanResult(callbackType, result) val bluetoothDevice = result.device bluetoothDevice.connectGatt( applicationContext, /** autoConnect flag */ false, gattClientCallback )A }A
  26. > < next previous Connection establishment BluetoothGattCallback val gattClientCallback =

    object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { … } override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { … } override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic?, status: Int) { … } override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic?, status: Int) { … } override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic?) { … } override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor?, status: Int) { … } override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor?, status: Int) { … } override fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int, status: Int) { … } … … … }
  27. > < next previous Connection establishment override fun onConnectionStateChange(gatt: BluetoothGatt,

    status: Int, newState: Int) { super.onConnectionStateChange(gatt, status, newState) if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { gatt.discoverServices() } }
  28. > < next previous Services discovery override fun onServicesDiscovered(gatt: BluetoothGatt,

    status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) }AA
  29. > < next previous Services discovery override fun onServicesDiscovered(gatt: BluetoothGatt,

    status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) }AA
  30. > < next previous Services discovery override fun onServicesDiscovered(gatt: BluetoothGatt,

    status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) }AA
  31. > < next previous Performing operations, read override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.readCharacteristic(characteristic) }A
  32. > < next previous Performing operations, read override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.readCharacteristic(characteristic) }A override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: …, status: Int) {A super.onCharacteristicRead(gatt, characteristic, status) parseCharacteristic(characteristic) }A
  33. > < next previous Performing operations, write override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) characteristic.value = ByteArray(2) { it.toByte() } gatt.writeCharacteristic(characteristic) }A
  34. > < next previous Performing operations, write override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) characteristic.value = ByteArray(2) { it.toByte() } gatt.writeCharacteristic(characteristic) }A override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: …, status: Int) {A super.onCharacteristicWrite(gatt, characteristic, status) parseWriteResponse(characteristic) }A
  35. > < next previous Performing operations, order override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.writeCharacteristic(characteristic) gatt.readCharacteristic(characteristic) }A
  36. > < next previous Performing operations, order override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.writeCharacteristic(characteristic) // write operation will be silently overwritten by read gatt.readCharacteristic(characteristic) }A ‣ Implement custom queue (or use library e.g. RxAndroidBle)
  37. > < next previous Performing operations, notifications override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.setCharacteristicNotification(characteristic, true) /** local configuration */ }AA
  38. > < next previous Performing operations, notifications override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.setCharacteristicNotification(characteristic, true) /** local configuration */ /** remote (peripheral) configuration */ val descriptor = characteristic.getDescriptor(CCCD_UUID) descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) }AA
  39. > < next previous Performing operations, notifications override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.setCharacteristicNotification(characteristic, true) /** local configuration */ /** remote (peripheral) configuration */ val descriptor = characteristic.getDescriptor(CCCD_UUID) descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) }AA override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {A super.onDescriptorWrite(gatt, descriptor, status) /** will be invoked after writeDescriptor procedure */ }A
  40. > < next previous Performing operations, notifications override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.setCharacteristicNotification(characteristic, true) /** local configuration */ /** remote (peripheral) configuration */ val descriptor = characteristic.getDescriptor(CCCD_UUID) descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) }AA override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {A super.onDescriptorWrite(gatt, descriptor, status) /** will be invoked after writeDescriptor procedure */ }A override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {A super.onCharacteristicChanged(gatt, characteristic) /** receive characteristics updates */ }AB
  41. > < next previous Performing operations, indications override fun onServicesDiscovered(gatt:

    BluetoothGatt, status: Int) { super.onServicesDiscovered(gatt, status) val service = gatt.getService(SERVICE_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) gatt.setCharacteristicNotification(characteristic, true) /** local configuration */ /** remote (peripheral) configuration */ val descriptor = characteristic.getDescriptor(CCCD_UUID) descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE gatt.writeDescriptor(descriptor) }A override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { super.onDescriptorWrite(gatt, descriptor, status) /** will be invoked after writeDescriptor procedure */ }C override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { super.onCharacteristicChanged(gatt, characteristic) /** receive characteristics updates */ }B
  42. > < next previous Connection teardown ‣ Call disconnect on

    GATT before close ‣ Call close on GATT when disconnected ‣ No disconnected callback for at least several seconds -> close
  43. > < next previous RxAndroidBle ‣ RxJava 1/2 support ‣

    Fixes some issues of BLE stack ‣ Automatic operations queueing ‣ Open-source ‣ Not designed for persistent connections
  44. > < next previous RxAndroidBle, example val rxBleClient = RxBleClient.create(applicationContext)

    val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() val disposable = rxBleClient.scanBleDevices(scanSettings) .filter { it.bleDevice.macAddress == macAddr } .firstElement() .flatMapObservable { it.bleDevice.establishConnection(false) } .flatMapSingle { Single.zip( it.readCharacteristic(FIRST_CHARACTERISTIC_UUID), it.readCharacteristic(SECOND_CHARACTERISTIC_UUID), BiFunction(::composeValues)) }.subscribe(Consumer(::showResult), Consumer(::showError))
  45. > < next previous RxAndroidBle, example val rxBleClient = RxBleClient.create(applicationContext)

    val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() val disposable = rxBleClient.scanBleDevices(scanSettings) .filter { it.bleDevice.macAddress == macAddr } .firstElement() .flatMapObservable { it.bleDevice.establishConnection(false) } .flatMapSingle { Single.zip( it.readCharacteristic(FIRST_CHARACTERISTIC_UUID), it.readCharacteristic(SECOND_CHARACTERISTIC_UUID), BiFunction(::composeValues)) }.subscribe(Consumer(::showResult), Consumer(::showError))
  46. > < next previous RxAndroidBle, example val rxBleClient = RxBleClient.create(applicationContext)

    val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() val disposable = rxBleClient.scanBleDevices(scanSettings) .filter { it.bleDevice.macAddress == macAddr } .firstElement() .flatMapObservable { it.bleDevice.establishConnection(false) } .flatMapSingle { Single.zip( it.readCharacteristic(FIRST_CHARACTERISTIC_UUID), it.readCharacteristic(SECOND_CHARACTERISTIC_UUID), BiFunction(::composeValues)) }.subscribe(Consumer(::showResult), Consumer(::showError))
  47. > < next previous RxAndroidBle, example val rxBleClient = RxBleClient.create(applicationContext)

    val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() val disposable = rxBleClient.scanBleDevices(scanSettings) .filter { it.bleDevice.macAddress == macAddr } .firstElement() .flatMapObservable { it.bleDevice.establishConnection(false) } .flatMapSingle { Single.zip( it.readCharacteristic(FIRST_CHARACTERISTIC_UUID), it.readCharacteristic(SECOND_CHARACTERISTIC_UUID), BiFunction(::composeValues)) }.subscribe(Consumer(::showResult), Consumer(::showError))
  48. > < next previous RxAndroidBle, example val rxBleClient = RxBleClient.create(applicationContext)

    val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() val disposable = rxBleClient.scanBleDevices(scanSettings) .filter { it.bleDevice.macAddress == macAddr } .firstElement() .flatMapObservable { it.bleDevice.establishConnection(false) } .flatMapSingle { Single.zip( it.readCharacteristic(FIRST_CHARACTERISTIC_UUID), it.readCharacteristic(SECOND_CHARACTERISTIC_UUID), BiFunction(::composeValues)) }.subscribe(Consumer(::showResult), Consumer(::showError))
  49. > < next previous RxAndroidBle, example val rxBleClient = RxBleClient.create(applicationContext)

    val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() val disposable = rxBleClient.scanBleDevices(scanSettings) .filter { it.bleDevice.macAddress == macAddr } .firstElement() .flatMapObservable { it.bleDevice.establishConnection(false) } .flatMapSingle { Single.zip( it.readCharacteristic(FIRST_CHARACTERISTIC_UUID), it.readCharacteristic(SECOND_CHARACTERISTIC_UUID), BiFunction(::composeValues)) }.subscribe(Consumer(::showResult), Consumer(::showError))
  50. > < next previous RxAndroidBle, example val rxBleClient = RxBleClient.create(applicationContext)

    val scanSettings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build() val disposable = rxBleClient.scanBleDevices(scanSettings) .filter { it.bleDevice.macAddress == macAddr } .firstElement() .flatMapObservable { it.bleDevice.establishConnection(false) } .flatMapSingle { Single.zip( it.readCharacteristic(FIRST_CHARACTERISTIC_UUID), it.readCharacteristic(SECOND_CHARACTERISTIC_UUID), BiFunction(::composeValues)) }.subscribe(Consumer(::showResult), Consumer(::showError))
  51. > < next previous More libraries ‣ Based on Kotlin

    & Coroutines ‣ No RxJava knowledge required (and dependency on thousands of its methods) ‣ Experimental status BleGattCoroutines Android BLE Library (NordicSemiconductor) ‣ Stable and trusted ‣ Unifies and simplifies connection process ‣ Easier from perspective of persistent connection
  52. > < next previous nRF Connect for Mobile “Scan, advertise

    and explore your Bluetooth low energy (BLE) devices and communicate with them”
  53. > < next previous BLE & Android 5.0 ‣ Fixes

    tons of issues introduced by bluetooth stack on Android 4.2 ‣ Introduces peripheral role ‣ Introduces dedicated scanning class
  54. > < next previous Peripheral role on Android ‣ GATT

    Server & Advertisement ‣ Android 5.0+ (hardware support required) ‣ BluetoothAdapter.isMultipleAdvertisementSupported() ‣ API is similar to central role
  55. > < next previous BLE limitations (Bluetooth 4.x) ‣ Package

    size (MTU, 20 bytes) ‣ Distance (theoretically up to 100m, practically ~10m) ‣ Transfer speed (theoretically up to 1Mbps, practically ~10kB/s) ‣ Max 15 notifications subscriptions per BluetoothGatt object (5.0+) ‣ 7 max concurrent connections (4.4+)
  56. > < next previous Bluetooth 5 ‣ Focused on IoT

    ‣ Mobile devices support started in 2017 (Galaxy S8) ‣ Doubled data rate (2 Mbps), at the expense of range ‣ Fourfold the range at the expense of data rate ‣ Eightfold the data broadcasting capacity of transmissions ‣ Android API support added in Oreo (8.0)
  57. > < next previous Connection parameters Connection-interval & Latency ‣

    CI defines how often devices sync (7.5 ms - 4 sec per spec) ‣ Min & max CI varies between Android versions & vendors ‣ Latency tells how many connection events peripheral can skip
  58. > < next previous Broadcasting mode ‣ Connectionless ‣ One-way

    data sending to many devices ‣ No security at all ‣ Beacons as the most popular example
  59. > < next previous Understanding profiles ‣ Define how protocols

    should be used to achieve general or specific goal ‣ Generic profiles ensure interoperability between different BLE devices (GAP & GATT) ‣ Use-case specific profiles are based on top of generic (GATT) profile (e.g. Blood Pressure Profile) ‣ Vendor-specific profiles cover cases not defined by SIG community (e.g. iBeacons)
  60. > < next previous Generic profiles, GAP ‣ Defines peripheral/central

    roles ‣ Devices discovery ‣ Connection establishment ‣ Data broadcasting ‣ Security levels
  61. > < next previous Generic profiles, GATT ‣ Defines client/server

    roles ‣ Data model (services, characteristics) ‣ Procedures for data exchange (read, write, notify etc) ‣ Entry point for the application to interact with BLE stack
  62. > < next previous Host, Attribute Protocol (ATT) ‣ Simple

    client-server stateless protocol ‣ Server contains data organized in the form of attributes ‣ Each attribute has UUID, handle, permissions and value ‣ Defines set of possible operations (read/write, error handling, configurations, notifications/indications etc)
  63. > < next previous Host, Security Manager Protocol ‣ Protocol

    & set of algorithms for keys generations and exchange ‣ Security procedures: pairing, bonding and encryption re-establishment ‣ Security mechanisms: encryption, privacy, signing
  64. > < next previous Host, Logical Link Control & Adaptation

    Protocol (L2CAP) ‣ Protocols multiplexer ‣ Packets fragmentation and recombination ‣ Routes two protocols: Attribute Protocol & Security Manager
  65. > < next previous Controller, Link Layer ‣ Real-time constrained

    part, usually combines software & hardware ‣ Essentially, a state machine with five states ‣ HCI is used to hide complexity & real-time constraints
  66. > < next previous Controller, Physical Layer (PHY) ‣ Analog

    signals modulation/demodulation ‣ 2.4 GHz ISM band is used (divided into 40 channels) ‣ 37 channels used for connection data and 3 for advertisement ‣ Frequency hopping spread spectrum technique is used to minimize effect of radio inference present in the band ‣ Sets modulation rate (1 Mbit/s)
  67. > < next previous Host-Controller Interface (HCI) ‣ Specification with

    a set of commands, events & flow control rules ‣ Allows several possible configurations based on chip ‣ Abstracts complex real-time controller layer
  68. > < next previous Conclusions ‣ Drop pre-Lollipop support ‣

    Use RxAndroidBle or other proven libraries ‣ Assume that each operation could fail and code defensively (delay, retry, reconnection) ‣ Everything that is not GATT.Sucess == Failure ‣ Switch job/project if you can
  69. > < previous Resources ‣ Bluetooth Low Energy: The Developer's

    Handbook ‣ Getting Started with Bluetooth Low Energy (O’Reilly) ‣ developer.android.com/guide/topics/connectivity/bluetooth-le ‣ bignerdranch.com/blog/bluetooth-low-energy-part-1/ ‣ github.com/Polidea/RxAndroidBle ‣ github.com/iDevicesInc/SweetBlue/wiki/Android-BLE-Issues