Introduction to Low power Bluetooth

Android 4.3 (API Level 18) introduces Bluetooth Low Energy (BLE) core features and provides corresponding apis. Through these apis, applications can scan Bluetooth devices, query services, and characteristics of read and write devices.

The Bluetooth protocol used by Android BLE is the GATT protocol. For details about this protocol, see the Official Bluetooth documentation. Here I use a chart from the official website to outline some Bluetooth Low Energy terms that you need to know in Android development.





Bluetooth protocol diagram

Service

A low-power Bluetooth device can define many services, which can be understood as a collection of functions. Each different Service on the device has a 128-bit UUID as a separate identifier for the Service. The Bluetooth core specification specifies two different UUID types, a base UUID and a 16-bit UUID that replaces the base UUID. All bluetooth technology consortium defined UUids share one basic UUID: 0x0000XXXX-0000-1000-8000-00805F9B34Fb To further simplify the base UUID, each attribute defined by the Bluetooth Technology Consortium has a unique 16-bit UUID in place of the ‘x’ part of the base UUID above. For example, the heart rate measurement feature uses 0X2A37 as its 16-bit UUID, so its full 128-bit UUID is 0x00002a37-0000-1000-8000-00805f9b34Fb

Characteristic

Under services, there are many unique data items called Characteristic. Likewise, each Characteristic has a unique UUID as its identifier. In Android development, after establishing bluetooth connection, we say that sending data to peripheral devices through Bluetooth is to write data to the Value field in these Characteristic; The peripheral sends data to the phone to listen for changes in the Value field in these Charateristic, and if there are changes, the phone’s BLE API will receive a callback to listen.

For more details, see GATT Profile and its Services. GATT Specification GATT Services Overview of the Bluetooth protocol


Introduction to the Android BLE API

BluetoothAdapter BluetoothAdapter has basic Bluetooth operations such as enabling Bluetooth scanning, Instantiate a BluetoothDevice using a known MAC address (BluetoothAdapter#getRemoteDevice) for the operation of connecting bluetooth devices, etc.

The BluetoothDevice represents a remote BluetoothDevice. This class allows you to connect to the bluetooth device you represent or get information about it, such as its name, address, and binding status.

BluetoothGatt this class provides the basic functionality of Bluetooth GATT. For example, reconnect bluetooth device, discover Service of Bluetooth device, and so on.

BluetoothGattService is a class obtained by BluetoothGattService #getService, which returns null if the current service is not visible. This class corresponds to the Service mentioned above. We can further getCharacteristic(UUID UUID) of this class to achieve bidirectional transmission of bluetooth data.

BluetoothGattCharacteristic this class corresponding to the above mentioned Characteristic. This class defines the data that needs to be written to and read from the peripheral.


Android Bluetooth development example

Step 1: Declare the permissions you need

<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> Use scan and set bluetooth permission (to declare this permission must declare the previous permission)Copy the code

Prior to Android5.0, GPS hardware functionality was applied by default. After Android 5.0, you need to declare the use of GPS hardware module functionality in the manifest.

    <! -- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
    <uses-feature android:name="android.hardware.location.gps" />Copy the code

On Android 6.0 and above, you also need to turn on location permissions. If the app does not have location permissions, Bluetooth scanning will not work (other Bluetooth operations such as connecting to bluetooth devices and writing data will not be affected).

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>Copy the code

Step 2: Initialization before bluetooth connection

Before establishing a Bluetooth connection, you need to confirm that the device supports BLE. If yes, check whether Bluetooth is enabled. If Bluetooth is not enabled, you can use the BLuetoothAdapter class to enable Bluetooth.

  1. Get BluetoothAdapter

     private BluetoothAdapter mBluetoothAdapter;
    
     // Initializes Bluetooth adapter.
     final BluetoothManager bluetoothManager =
         (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
     mBluetoothAdapter = bluetoothManager.getAdapter();Copy the code
  2. If it detects that Bluetooth is not enabled, try enabling Bluetooth
     // Ensures Bluetooth is available on the device and it is enabled. If not.// displays a dialog requesting user permission to enable Bluetooth.
     if (mBluetoothAdapter == null| |! mBluetoothAdapter.isEnabled()) { Intent enableBtIntent =new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
             startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
     }Copy the code

Step 3: Scan your Bluetooth device

When bluetooth is enabled on a peripheral device, it will broadcast a lot of data information about the device, such as MAC address, UUID and so on. Using this data, we can screen out the equipment we need.

In BluetoothAdapter we can see that there are two methods for scanning Bluetooth. The first method can specify that only Bluetooth devices with a particular UUID Service are scanned, while the second method scans all Bluetooth devices.

boolean    startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)Copy the code
boolean    startLeScan(BluetoothAdapter.LeScanCallback callback)Copy the code

Enable Bluetooth scanning

final BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
        bluetoothDeviceArrayList.add(device);
        Log.d(TAG, "run: scanning..."); }}; mBluetoothAdapter.startLeScan(callback);Copy the code

In the LeScanCallback callback method, the first parameter is the class representing the Bluetooth device, through which you can establish a Bluetooth connection to obtain a series of detailed parameters about the device, such as name, MAC address, etc. The second parameter is the signal strength index of Bluetooth. Through the signal index of Bluetooth, we can roughly calculate the distance between bluetooth device and mobile phone. The calculation formula is: d = 10^((ABS (RSSI) -a)/(10 * n)); The third parameter is the advertising data broadcast by Bluetooth. Once a bluetooth device is found after executing the above code, LeScanCallback is called back until stopLeScan is invoked. Devices that appear in the callback are repeated, so if we need to manually filter out the discovered peripherals by taking out the address of the peripherals via BluetoothDevice.

Stop Bluetooth scanning

void    stopLeScan(BluetoothAdapter.LeScanCallback callback)Copy the code

Bluetooth scanning in progress can be stopped by calling BluetoothAdapter#stopLeScan. It is important to note that the callback passed in must be the callback passed in when Bluetooth scanning is enabled, otherwise bluetooth scanning will not stop.

The operation of Bluetooth scan consumes the energy of the phone. So we can’t leave Bluetooth on all the time, we have to set it to turn off Bluetooth scanning after a certain period of time. Example code is as follows:

private void scanLeDevice(final boolean enable) {
    if (enable) {
        // Stops scanning after a pre-defined scan period.
        // Pre-define the time to stop Bluetooth scanning (because bluetooth scanning requires a lot of power consumption)
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run(a) {
                mScanning = false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        }, SCAN_PERIOD);
        mScanning = true;

        // Define a callback interface for scanning end processing
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    } else {
        mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); }}Copy the code

Step 4: Connect a Bluetooth device

Bluetooth devices can be connected via the BluetoothDevice#ConnectGatt method or reconnected via the BluetoothGatt#connect method. Here are the official instructions for the two methods:

BluetoothDevice#connectGatt
BluetoothGatt    connectGatt(Context context.boolean autoConnect, BluetoothGattCallback callback)Copy the code

The second parameter indicates whether automatic connection is required. If this parameter is set to true, it means that if the device is disconnected, it will continuously try to connect automatically. Setting this to false means that only one connection attempt is made. The third parameter is the callback for the sequence of operations that took place after the connection, such as connect and disconnect callbacks, service discovery callbacks, successful write, successful read, and so on.

BluetoothGatt#connect
boolean    connect(a)Copy the code

Calling this method is equivalent to calling BluetoothDevice#connectGatt with the second parameter autoConnect set to true.

BluetoothGattCalbackl#onConnectionStateChange is called asynchronously when the bluetooth connection method is called, and if the connection is successful, the BluetoothGattCalbackl#onConnectionStateChange method is called back. The thread on which this method runs is a Binder thread, so it is not recommended to process time-consuming tasks directly in this thread, as this may cause bluetooth-related threads to block.

void    onConnectionStateChange(BluetoothGatt gatt, int status, int newState)Copy the code

This method takes three parameters, the first of which is the Gatt service connection class for the Bluetooth device. The second parameter indicates whether the connection operation was successfully executed. Bluetoothgatt. GATT_SUCCESS indicates that the connection operation was successfully executed, and the third parameter is valid. Otherwise, the connection attempt was not successful. Sometimes, we get status == 133. According to most people on the web, this is because Android supports a maximum of six or seven Bluetooth devices, and if you go beyond that, you can’t connect anymore. So when we disconnect a bluetooth device, we also have to call the BluetoothGatt#close method to release the connection resources. Otherwise, after many attempts to connect to a Bluetooth device, this limit will soon be exceeded, resulting in this error and no longer being able to connect to a Bluetooth device. The third parameter represents the current connection status of the device. If newState == bluetoothprofile.state_connected, the device is connected and ready for the next step (Found bluetooth Service, also known as Service). This method is also called back when a Bluetooth device is disconnected, where newState == bluetoothprofile.state_disconnected.

Step 5: Discover the service

This step can only be done after a successful connection to the bluetooth device, that is, after the BluetoothGattCalbackl#onConnectionStateChang method has been successfully called back indicating a successful connection. After this method is invoked, the system asynchronously discovers services and BluetoothGattCallback#onServicesDiscovered does not establish a communication connection between the mobile device and bluetooth device until BluetoothGattCallback#onServicesDiscovered is called back.

At this stage, we have successfully established a communication connection with the Bluetooth device, and then we can perform the corresponding Bluetooth communication operations, such as writing data, reading data from the Bluetooth device, and so on.

Read the data

And once we found the service we could simply get the BluetoothGattService out of BluetoothGattService, Then through BluetoothGattService# BluetoothGattCharactristic getCharactristic. Through BluetoothGattCharactristic# readCharacteristic method can be read notification system to specific data. If the system reads the data sent by the bluetooth device, the BluetoothGattCallback#onCharacteristicRead method is called. Through BluetoothGattCharacteristic# getValue can read data to a bluetooth device. Here is an example of code:

@Override
public void onCharacteristicRead(final BluetoothGatt gatt,
                                    final BluetoothGattCharacteristic characteristic,
                                    final int status) {

    Log.d(TAG, "callback characteristic read status " + status
            + " in thread " + Thread.currentThread());
    if (status == BluetoothGatt.GATT_SUCCESS) {
        Log.d(TAG, "read value: "+ characteristic.getValue()); }}// Read data
BluetoothGattService service = gattt.getService(SERVICE_UUID);
BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
gatt.readCharacteristic();Copy the code

Write data

Like reading data, need to get to the BluetoothGattCharactristic before write data. Then perform the following steps:

  1. Call BluetoothGattCharactristic# setValue to need to write the data (single 1 most bluetooth support 20 bytes of data transmission, if need to transport the data is greater than this one byte requires the subcontract transport).
  2. Asynchronous notification system call BluetoothGattCharactristic# writeCharacteristic method to write data equipment.
  3. The system calls the BluetoothGattCallback#onCharacteristicWrite method to inform you that data has been written. At this point, we need to perform BluetoothGattCharactristic# getValue method to check the write data whether we need to send data, if not in accordance with the project need to decide whether you need to resend. Here is the sample code:
@Override
public void onCharacteristicWrite(final BluetoothGatt gatt,
                                    final BluetoothGattCharacteristic characteristic,
                                    final int status) {
    Log.d(TAG, "callback characteristic write in thread " + Thread.currentThread());
    if(! characteristic.getValue().equal(sendValue)) {// Execute the retransmission policygatt.writeCharacteristic(characteristic); }}// Write data to bluetooth data channel
BluetoothGattService service = gattt.getService(SERVICE_UUID);
BluetoothGattCharacteristic characteristic = gatt.getCharacteristic(CHARACTER_UUID);
characteristic.setValue(sendValue);
gatt.writeCharacteristic(characteristic);Copy the code

Register and monitor bluetooth device to realize real-time reading of bluetooth device data

BLE apps usually need to get notifications of characteristic changes in the device. The following code shows how to set a listener for a Characteristic.

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);Copy the code

It is important to note that in addition to BluetoothGatt# setCharacteristicNotification open Android side receives the notice of the switch, You also need to write data switches that enable notifications to the Characteristic Descriptor property so that when the hardware data changes, it actively sends data to the phone.

Final step: Disconnect

When we connect the Bluetooth device to complete a series of Bluetooth operations, we can disconnect the Bluetooth device. BluetoothGatt#disconnect disconnects the bluetooth device being connected. When this method is called, the system asynchronously calls the BluetoothGattCallback#onConnectionStateChange method. The method’s newState parameter is used to determine whether the callback was successfully connected or disconnected. Due to the limited resources of Android bluetooth connected devices, BluetoothGatt#close method must be executed after we disconnected bluetooth operation to release resources. Note that BluetoothGattCallback#onConnectionStateChange will not receive any callbacks as it is also possible to disconnect bluetooth using the BluetoothGattCallback# close method. If the BluetoothGatt#connect method is executed at this point, a null pointer to the bluetooth API is raised. So, what we recommend is that once bluetooth has been connected, BluetoothGatt#disconnect, This is followed by executing the BluetoothGatt#close method in BluetoothGattCallback#onConnectionStateChange to release the resource. Here is an example of code:

    @Override
    public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
                                    final int newState) {
        Log.d(TAG, "onConnectionStateChange: thread "
                + Thread.currentThread() + " status " + newState);

        if(status ! = BluetoothGatt.GATT_SUCCESS) {String err = "Cannot connect device with error status: " + status;
      // Calling the Disconnect method when an attempt fails does not cause the method callback, so here
                // Just call it back.
            gatt.close();
            Log.e(TAG, err);
            return;
        }

        if (newState == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverService();
        } else if (newState== BluetoothProfile.STATE_DISCONNECTED) { gatt.close(); }}Copy the code

Precautions for Bluetooth operation

  1. For Bluetooth writes (including writes of Descriptor), read operations must be serialized. Write data and read data cannot be performed at the same time. If the write method is called and the write or read method is immediately called, the second call will immediately return false, indicating that the operation cannot be performed. For details, see Bluetooth read-write operation return false. Why do multiple read-write operations only have one callback?
  2. Android has a limited number of peripherals to connect to, and when bluetooth devices are not needed, BluetoothGatt#close must be called to release resources. A detailed reference can be seen here for Android BLE bluetooth development of various pits
  3. The timeout period of bluetooth API connection to Bluetooth device is about 20s, depending on the system implementation. Sometimes the bluetooth connection on some devices can take a long time, more than ten seconds. If you manually set the connection timeout (for example, BluetoothGattCallback#onConnectionStateChange after 5s set by Handler#postDelay BluetoothGattCallback#onConnectionStateChange) on some devices could cause the following attempts to connect to return state == 133. In addition, we can refer to the solution of “BluetoothGatt Status 133” in the Android BLE connection
  4. All Bluetooth operations are fixed to one thread using a Handler, which saves a lot of trouble when threads are out of sync

Finally, attached I wrote a bluetooth operation library using RxJava encapsulation, you can refer to my class library source code according to the needs of their own project secondary development. Github.com/Belolme/RxB…