Application of Bluetooth Low Energy (BLE) in Android APP

Application of Bluetooth Low Energy (BLE) in Android APP

foreword

Recently, the company took over a new project. Users can connect their musical instruments with Phone or Pad. When playing the musical instrument, the performance status will be synchronously fed back to the device, which is convenient for users to practice. It is similar to the one played before. A game called [Guitar Hero] . But this time there is no need to plug in the wire, just connect directly to the Bluetooth wireless connection.
Then the problem comes, because the data transmission is always going on when playing, but if you want to keep turning on the bluetooth, it will consume a lot of power. Maybe the power of the device will be consumed after a few songs, which is of course unacceptable. of. So is there any good solution?
Fortunately, Android has added BLE technology after 4.3, which can greatly save power consumption of the device. So what is BLE? What is the difference between it and classic Bluetooth? What problems should we pay attention to during development?

What is BLE?

BLE is the abbreviation of Bluetooth Low Energy Bluetooth, as the name suggests, its power consumption is low.

The difference with classic bluetooth

We know that most of the Bluetooth used on mobile devices is 4.0, and Bluetooth 4.0 has two branches, classic 4.0 and BLE4.0, classic 4.0 is an upgrade of the traditional 3.0 Bluetooth, and is backward compatible. And BLE 4.0 is a new branch, not backward compatible.
Compared with classic Bluetooth, the advantages of BLE are fast search, fast connection, ultra-low power consumption to maintain connection and transmit data. The weakness is that the data transmission rate is low, the physical bandwidth is only 1M, and the actual transmission speed is between 1 and 6KB.

Issues to be paid attention to in BLE development

Key Terms and Concepts

  • Generic Attribute Profile (GATT) - A GATT profile is a generic specification for sending and receiving blocks of data called "attributes" over a BLE link. All current BLE applications are based on GATT. The Bluetooth SIG specifies profiles for many low energy devices. A profile is a specification of how a device works in a particular application. Note that a device can implement multiple profiles. For example, a device might include a heart rate monitor and a battery check.

  • Attribute Protocol (ATT)—GATT is established on the basis of the ATT protocol, also known as GATT/ATT. ATT is optimized to run on BLE devices, and to do so, it uses as few bytes as possible. Each attribute is identified by a unique uniform identifier (UUID), and each String type UUID uses a 128-bit standard format. Attributes are formatted as characteristics and services via ATT.

  • Characteristic A characteristic includes a single variable and 0-n descriptors used to describe the characteristic variable. The characteristic can be considered as a type, similar to a class.

  • Descriptor Descriptor is used to describe the properties of characteristic variables. For example, a descriptor can specify a human-readable description, or an acceptable range for a characteristic variable, or a specific unit of measurement for a characteristic variable.

  • Service service is a collection of characteristics. For example, you may have a service called "Heart Rate Monitor (heart rate monitor)", which includes many characteristics, such as "heart rate measurement (heart rate measurement)" and so on. You can find a list of currently supported GATT-based profiles and services at bluetooth.org.

If you don't understand the above list of technical terms, look at the picture below:

Terms and concepts included in Bluetooth Low Energy

How about it, is it much clearer after reading it~

Roles and Responsibilities

There are two groups of roles for Android devices to interact with BLE devices:

  • Central device and peripheral device (Central vs. peripheral):
    The concept of central device and peripheral device is aimed at the BLE connection itself. The Central role is responsible for scan advertisement. The peripheral role is responsible for make advertisement.

  • GATT server vs. GATT client:
    These two roles depend on how the two devices communicate after a successful BLE connection.

Example:
There is an activity tracking BLE device and an Android device that supports BLE. Android devices support the Central role, while BLE devices support the peripheral role. Creating a BLE connection requires the presence of both roles, and the connection cannot be established if both only support the Central role or both only support the peripheral role.

When the connection is established, GATT data needs to be transmitted between them. Who will be the server and who will be the client depends on the specific data transmission situation. For example, if the activity tracking BLE device needs to transmit sensor data to the Android device, the activity tracker will naturally become the server side; and if the activity tracker needs to obtain update information from the Android device, the Android device may be more suitable as the server side.

The process of establishing a connection

1.使用BluetoothAdapter.startLeScan来扫描BLE设备;
2.在扫描到设备的回调函数onLeScan中会得到BluetoothDevice对象;
3.使用BluetoothDevice.connectGatt(参数之一BluetoothGattCallback用于传递一些连接状态及结果)
来获取到BluetoothGatt对象,BluetoothGatt.connect建立连接;
4.上面的BluetoothGattCallback参数为BluetoothGattCallback的匿名内部类,     
通过重写一些方法来对客户端进行操作,例如读取BluetoothGattService与BluetoothGattCharacteristic。

Let's talk about the specific process in combination with Google's official Sample: BluetoothLeGatt .

BLE permissions

Like classic Bluetooth, the app uses BLE and needs to declare the BLUETOOTH permission. Use this permission to perform Bluetooth communications, such as requesting connections, accepting connections, and transmitting data.
If you want your app to initiate device discovery or manipulate Bluetooth settings, it must declare the BLUETOOTH_ADMIN permission. NOTE: If you use the BLUETOOTH_ADMIN authority, you must also declare the BLUETOOTH authority.
Declare the Bluetooth permission in your app manifest file:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

If you want to declare that your app is only available for devices with BLE, include in the manifest file:

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

But if you want your app to provide devices that do not support BLE, you need to include the above code in the manifest and set required="false", and then you can determine the availability of BLE at runtime by using PackageManager.hasSystemFeature():

// 使用此检查确定BLE是否支持在设备上,然后你可以有选择性禁用BLE相关的功能
  if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}   

start bluetooth

Before your app communicates with BLE, you need to confirm whether the device supports BLE, and if so, make sure it is enabled. Note that <uses-feature.../>this check is only required if set to false.
If BLE is not supported, then you should disable some BLE functions appropriately. If BLE is supported but disabled, you can ask the user to enable Bluetooth without leaving the app. This setup is done in two steps using the BluetoothAdapter.

1. Get the BluetoothAdapter
The BluetoothAdapter is required for all Bluetooth operations in the Android system. It corresponds to the Bluetooth module of the local Android device. The BluetoothAdapter is a singleton in the entire system. After you get its sample, you can perform related Bluetooth operations. The code snippet below shows how to get the adapter. Note that this method uses getSystemService() to return the BluetoothManager, which is then used to obtain an instance of the adapter. Android 4.3 (API 18) introduces BluetoothManager.

// 初始化蓝牙适配器
 final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();

2. Turn on Bluetooth
Next, you need to confirm whether Bluetooth is turned on. Call isEnabled() to check whether Bluetooth is currently enabled. If the method returns false, Bluetooth is disabled. The following code checks whether Bluetooth is enabled, if not, an error will be displayed to prompt the user to enable Bluetooth.

// 确保蓝牙在设备上可以开启
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
   Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
   startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

Search for BLE devices

Search for BLE devices by calling startLeScan() of the BluetoothAdapter. The BluetoothAdapter.LeScanCallback parameter needs to be passed in when calling this method.
Therefore, you need to implement the BluetoothAdapter.LeScanCallback interface, and the search results of BLE devices will be returned through this callback.
Because scanning BLE devices is a power-intensive operation and wastes power, the following principles must be ensured:
1. Stop scanning immediately after finding the corresponding device;
2. Do not search for devices in a loop, and set a suitable time limit for each search. Avoid continuous scanning and power consumption when the device is not in the available range.

private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
  // Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;

private void scanLeDevice(final boolean enable) {
        if (enable) {
            // 经过预定扫描期后停止扫描
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);
            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }

If you only want to scan for a specific type of peripheral, you can call instead startLeScan(UUID[], BluetoothAdapter.LeScanCallback)), providing an array of UUID objects for the GATT services your app supports.
As the interface of the BLE scan result, the following is the implementation of BluetoothAdapter.LeScanCallback.

// Device scan callback.
    private BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, int rssi,
                                     byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mLeDeviceListAdapter.addDevice(device);
                            mLeDeviceListAdapter.notifyDataSetChanged();
                        }
                    });
                }
            };

Personal test, using this method to intelligently search for BLE devices, which cannot be found by classic Bluetooth devices.

Connect to GATT server

The first step in interacting with a BLE device is to connect to it—more specifically, to a GATT server on the BLE device. In order to connect to a GATT server on a BLE device, use the connectGatt( ) method. This method requires three parameters: a Context object, autoConnect (boolean value, indicating whether to automatically connect to the BLE device as soon as it is available), and the BluetoothGattCallback call.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
When connecting to the GATT server, the BLE device acts as the host and returns a BluetoothGatt instance, and then you can use this instance to perform GATT client operations. The supplicant (Android app) is the GATT client. BluetoothGattCallback is used to communicate results to the user, such as connection status, and any further GATT client operations.

    // Implements callback methods for GATT events that the app cares about.  For example,
    // connection change and services discovered.
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            String intentAction;
            Log.i(TAG, "oldStatus=" + status + " NewStates=" + newState);
            if(status == BluetoothGatt.GATT_SUCCESS)
            {

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;

                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                // Attempts to discover services after successful connection.
                Log.i(TAG, "Attempting to start service discovery:" +
                mBluetoothGatt.discoverServices());
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mBluetoothGatt.close();
                mBluetoothGatt = null;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.w(TAG, "onServicesDiscovered received: " + status);
                findService(gatt.getServices());
            } else {
                if(mBluetoothGatt.getDevice().getUuids() == null)
                Log.w(TAG, "onServicesDiscovered received: " + status);

            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            Log.i(TAG, "onCharacteristicRead: ");
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
                Log.i(TAG, "onCharacteristicRead: ");
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            Log.e(TAG, "OnCharacteristicChanged");
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            Log.e(TAG, "OnCharacteristicChanged");
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
                                        int status)
        {
            Log.e(TAG, "OnCharacteristicWrite");
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt,
                                         BluetoothGattDescriptor bd,
                                         int status) {
            Log.e(TAG, "onDescriptorRead");
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt,
                                         BluetoothGattDescriptor bd,
                                         int status) {
            Log.e(TAG, "onDescriptorWrite");
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int a, int b)
        {
            Log.e(TAG, "onReadRemoteRssi");
        }

        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int a)
        {
            Log.e(TAG, "onReliableWriteCompleted");
        }

    };

In the above code, there are mainly 4 methods:

onConnectionStateChange(BluetoothGatt gatt, int status, int newState){...};
onServicesDiscovered(BluetoothGatt gatt, int status){...}; 
onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
                                     int status){...};
onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic 
                                     characteristic,int status){...}

After our GATT client connects to the GATT server, due to the change of the connection status, onConnectionStateChange()the method is first triggered. We see that there is a judgment here. If the status is BluetoothProfile.STATE_CONNECTED, the connection is successful. Next there are two actions: broadcastUpdate()
AND BluetoothGatt.discoverServices(). broadcastUpdate(), this is a method of self-encapsulation, because it is necessary to keep the connection and receive data, so write the above into a Service, and the Activity interacts with the user, thus avoiding disconnection when the APP is in the background. broadcastUpdate()Responsible for sending information to the Activity when the connection status changes or receiving data, and notifying the user of the interface status change.

private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        sendBroadcast(intent);
    }

    private void broadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);

        // This is special handling for the Heart Rate Measurement profile.  Data parsing is
        // carried out as per profile specifications:
        // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?  u=org.bluetooth.characteristic.heart_rate_measurement.xml
            // For all other profiles, writes the data formatted in HEX.
        final byte[] data = characteristic.getValue();
        Log.i(TAG, "broadcastUpdate: "+ characteristic.getValue());
        if (data != null && data.length > 0) {
            //final StringBuilder stringBuilder = new StringBuilder(data.length);
            //for(byte byteChar : data)
            //stringBuilder.append(String.format("%02X ", byteChar));
            //intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
            intent.putExtra(EXTRA_DATA, new String(data));
            Log.i(TAG, "broadcastsend: ");
        }
        sendBroadcast(intent);
    }

BluetoothGatt.discoverServices()Search for the service sent by the GATT server, and trigger it after finding it onServicesDiscovered(). We see that there is a self-encapsulated findService()method in it. This method will traverse all the searched services, find the required service according to the UUID, and then traverse all the Characteristics in it, according to the UUID If you find the required Characteristic and read the data, you mainly rely on BluetoothGatt.setCharacteristicNotificationthis method. When the Characteristic state changes, onCharacteristicChanged()this method will be triggered, and we can call it again to broadcastUpdate()notify the user.
Note: After calling BluetoothGatt.setCharacteristicNotification, add such a paragraph:

         //属性时一定要写的,否则会收不到通知
        List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
        for (BluetoothGattDescriptor bgp : descriptors) {
            Log.i(TAG, "setCharacteristicNotification: " + bgp);
            bgp.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            mBluetoothGatt.writeDescriptor(bgp);
        }

Otherwise, I will not receive the notification, and I have been stuck here for a long time...
Then someone asked, onCharacteristicRead()what do you do? BluetoothGatt.readCharacteristic(mNotifyCharacteristic);It will be triggered when we call onCharacteristicRead(), but it is generally not used. Because we have no way of knowing when the data will be transmitted, we still use BluetoothGatt.setCharacteristicNotificationa little more in most cases.
If you want to write data, you need to use it
Characteristic.setValue(strValue.getBytes());
BluetoothGatt.writeCharacteristic(mNotifyCharacteristic);
and then it will be triggered onCharacteristicWrite(), and we can call again broadcastUpdate()to notify the user.

turn off BLE

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

Let's just say so much first, and add more if you find omissions.

Reference article:
http://blog.csdn.net/hellogv/article/details/24267685
http://www.cnblogs.com/savagemorgan/p/3722657.html
http://my.oschina.net/tingzi/blog /215008
http://blog.csdn.net/chaoyue0071/article/details/43450091/

Guess you like

Origin blog.csdn.net/Yaoobs/article/details/51226932