Slide 1

Slide 1 text

@ErikHellman - DroidConBerlin 2021 Bluetooth Low Energy for Modern Android Development Or how to not go MAD when doing BLE… Erik Hellman, Head of Development - Iteam Solutions

Slide 2

Slide 2 text

@ErikHellman - DroidConBerlin 2021 MAD at Bluetooth

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Standards are hard

Slide 5

Slide 5 text

GATT GAP PHY Peripheral Central Advertising Profile Characteristic Service Descriptor

Slide 6

Slide 6 text

Bluetooth Low Energy - Basics

Slide 7

Slide 7 text

Generic Access Profile - GAP

Slide 8

Slide 8 text

Broadcasting/Advertising

Slide 9

Slide 9 text

Generic Attribute Profile - GATT

Slide 10

Slide 10 text

Client/Server Communications

Slide 11

Slide 11 text

GATT - Generic Attribute Profile Pro fi le Service Characteristic Characteristic Descriptor Descriptor Descriptor Descriptor Service Characteristic Descriptor Descriptor Only a logical grouping Doesn’t exist in real life! The actual communication point Information and con fi guration of a characteristics

Slide 12

Slide 12 text

Services • Identi fi ed by a UUID • Must be discovered after each successful connection - every time! • Contains a collection of Characteristics

Slide 13

Slide 13 text

Characteristics • Identi fi ed with a UUID • Stores a value (byte array) • Default maximum size of 21 bytes • Contains a collection of Descriptors • Can not handle concurrent read & writes - use two separate characteristics!

Slide 14

Slide 14 text

Descriptors • Identi fi ed with a UUID • Information about a characteristics (name, type, etc.) • Enable/Disable noti fi cations

Slide 15

Slide 15 text

Bluetoth SIG Base UUID xxxxxxxx-0000-1000-8000-00805F9B34FB

Slide 16

Slide 16 text

Bluetooth 16-bit UUIDs

Slide 17

Slide 17 text

Using 16-bit UUIDs Media Player Name Characteristic UUID:
 00002B93-0000-1000-8000-00805F9B34FB

Slide 18

Slide 18 text

Most custom devices use custom UUIDs!

Slide 19

Slide 19 text

Android and Bluetooth LE

Slide 20

Slide 20 text

Android versions and Bluetooth Low Energy •API level 16 - 😭 •API level 21 - 😟 •API level 26 - 🙂 •API level 31 - 😃

Slide 21

Slide 21 text

Peripheral Discovery & Pairing

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

Pairing a device with CompanionDeviceManager val request = AssociationRequest.Builder() .addDeviceFilter( BluetoothLeDeviceFilter.Builder() .setNamePattern(NAME_PATTERN) .build() ) .setSingleDevice(true) .build()

Slide 25

Slide 25 text

Pairing a device with CompanionDeviceManager companionDeviceManager.associate(request, object : CompanionDeviceManager.Callback() { override fun onDeviceFound(sender: IntentSender?) { activity.startIntentSenderForResult( sender, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0 ) } override fun onFailure(error: CharSequence?) { // Handle the failure ... } }, null )

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

Communication

Slide 28

Slide 28 text

GATT events val manager = context.getSystemService() val device = manager ?. adapter ?. getRemoteDevice(address) val gatt = device ?. connectGatt( context, true, // Try to connect until we succeed gattCallback, // Callbacks BluetoothDevice.TRANSPORT_LE, // Use BLE, not classic BluetoothDevice.PHY_OPTION_NO_PREFERRED // Only works if autoConnect is false )

Slide 29

Slide 29 text

GATT events sealed class GattEvent data class CharacteristicChanged( val characteristic: BluetoothGattCharacteristic ) : GattEvent() data class CharacteristicRead( val characteristic: BluetoothGattCharacteristic, val status: Int ) : GattEvent() data class CharacteristicWritten( val characteristic: BluetoothGattCharacteristic, val status: Int ) : GattEvent()

Slide 30

Slide 30 text

BluetoothGattCallback class GattCallbackEvents : BluetoothGattCallback() { private val _events = MutableSharedFlow(extraBufferCapacity = 10) val events: SharedFlow = _events override fun onConnectionStateChange( gatt: BluetoothGatt?, status: Int, newState: Int ) { _events.tryEmit(ConnectionStateChanged(status, newState)) } override fun onCharacteristicChanged( gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic? ) { if (characteristic ! = null) { _events.tryEmit(CharacteristicChanged(characteristic)) } }

Slide 31

Slide 31 text

GattDevice - Our BLE API facade class GattDevice(callbacks: GattCallbackEvents) { private val events = callbacks.events 
 private var bluetoothGatt: BluetoothGatt? = null private fun requireGatt(): BluetoothGatt = bluetoothGatt ?: throw IllegalStateException("BluetoothGatt is null") …wrapper code goes here… }

Slide 32

Slide 32 text

Call & wait suspend fun writeCharacteristic( characteristic: BluetoothGattCharacteristic ): CharacteristicWritten = events .onSubscription { requireGatt().writeCharacteristic(characteristic) } .firstOrNull { it is CharacteristicRead & & it.characteristic.uuid == characteristic.uuid } as CharacteristicWritten? ?: CharacteristicWritten(characteristic, BluetoothGatt.GATT_FAILURE)

Slide 33

Slide 33 text

Only one ongoing call at a time! private suspend fun Mutex.queueWithTimeout( timeout: Long = DEFAULT_GATT_TIMEOUT, block: suspend CoroutineScope.() - > T ): T { withLock { withTimeout(timeMillis = timeout, block = block) } }

Slide 34

Slide 34 text

Only one ongoing call at a time! private val mutex = Mutex() 
 
 suspend fun writeCharacteristic( characteristic: BluetoothGattCharacteristic ): CharacteristicWritten = mutex.queueWithTimeout { events .onSubscription { requireGatt().writeCharacteristic(characteristic)) } .firstOrNull { it is CharacteristicRead && it.characteristic.uuid == characteristic.uuid } as CharacteristicWritten? ? : CharacteristicWritten(characteristic, BluetoothGatt.GATT_FAILURE) }

Slide 35

Slide 35 text

Enabling notifications companion object { private val CCCD = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") } suspend fun registerNotifications(characteristic: BluetoothGattCharacteristic): Boolean = mutex.queueWithTimeout { val result = characteristic.descriptors.find { it.uuid == CCCD } ?. let { descriptor - > descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE events .onSubscription { requireGatt().setCharacteristicNotification(characteristic, true) requireGatt().writeDescriptor(descriptor) } .firstOrNull { it is DescriptorWritten && it.descriptor.characteristic.uuid == descriptor.characteristic.uuid && it.descriptor.uuid == descriptor.uuid } as DescriptorWritten? ?: DescriptorWritten(descriptor, BluetoothGatt.GATT_FAILURE) } result ?. status = = BluetoothGatt.GATT_SUCCESS }

Slide 36

Slide 36 text

Enabling notifications // Tell the Android OS to notify our app for this characteristic requireGatt().setCharacteristicNotification(characteristic, true) // Tell the peripheral to notify our phone for this characteristic requireGatt().writeDescriptor(descriptor)

Slide 37

Slide 37 text

PHY Options (Bluetooth 5.x) 100 meters (line of sight)

Slide 38

Slide 38 text

PHY Options (Bluetooth 5.x) 10011010

Slide 39

Slide 39 text

PHY Options (Bluetooth 5.x) Symbols 10011010 Symbols (frequency shifts)

Slide 40

Slide 40 text

PHY Options (Bluetooth 5.x) More symbols - better range, lower throughput 10011010 Symbols (frequency shifts)

Slide 41

Slide 41 text

PHY Options (Bluetooth 5.x) Less symbols - lower range, better throughput 10011010 Symbols (frequency shifts)

Slide 42

Slide 42 text

PHY Options (Bluetooth 5.x) 1 MBit/s, 100 meter range LE 1M PHY 2 MBit/s, 80 meter range LE 2M PHY 125 Kbit/s, 400 meter range LE Coded PHY

Slide 43

Slide 43 text

PHY Options (Bluetooth 5.x) BluetoothDevice.PHY_LE_1M_MASK // Default range and throughput BluetoothDevice.PHY_LE_2M_MASK // Lower range and higher throughput BluetoothDevice.PHY_LE_CODED_MASK // Longer range and lower throughput BluetoothDevice.PHY_OPTION_S2 // S=2 Coding for LE Coded PHY (default for LE Coded) BluetoothDevice.PHY_OPTION_S8 // S=8 Coding for LE Coded PHY (longest possible range) // Best possible range, but lowest throughput bluetoothGatt.setPreferredPhy( BluetoothDevice.PHY_LE_CODED_MASK, BluetoothDevice.PHY_LE_CODED_MASK, BluetoothDevice.PHY_OPTION_S8 )

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

Key take-aways • API level 26 • CompanionDeviceManager • Characteristics should never be read AND write! • Only one GATT operation at a time!

Slide 46

Slide 46 text

References • Bluetooth PHY – How it Works and How to Leverage it
 https://punchthrough.com/crash-course-in-2m-bluetooth-low-energy-phy/ • Exploring Bluetooth 5 – Going the Distance
 https://www.bluetooth.com/blog/exploring-bluetooth-5-going-the-distance/ • Modern Android Bluetooth LE demo project
 https://github.com/ErikHellman/ModernAndroidBluetoothLE

Slide 47

Slide 47 text

Thank you for listening! @ErikHellman