1. Bluetooth basic protocol

To understand Bluetooth communication, you need to understand the two most basic bluetooth protocols: GAP and GATT.

Overview of the Generic Access Profile (GAP)

GAP is an acronym for Universal Access Profile, which controls Bluetooth connectivity and broadcasting. GAP makes a Bluetooth device visible to the outside world and determines if and how the device can interact with other devices.

GAP defines a variety of roles, but the main two are: central devices and peripherals.

  • Central device: Can scan and connect multiple peripherals to get information from peripherals.

  • Peripherals: Small, low-power, resource-limited devices. You can connect to and feed data to more powerful central devices.

GAP broadcast data

Peripheral devices in GAP broadcast data in two ways: broadcast data and scan reply (each data can contain up to 31 bytes). .

Broadcast data is necessary because peripherals must continuously broadcast outwards to let central devices know of their presence. Scan reply is optional. The central device can request scan reply from peripherals, which contains some additional information about the device.

The broadcast network topology

The peripheral broadcasts itself to the central device to discover itself and establish a GATT connection for more data exchange. However, in some cases the connection is not required, as long as the peripheral broadcasts its own data. The idea is for the peripheral device to send its own information to multiple central devices. Because of the GATT connection, only one peripheral can connect to one central device.

This section describes the Generic Attribute Profile (GATT)

The GATT profile is a general specification for sending and receiving blocks of data called “properties” over a BLE link. All BLE applications are currently based on GATT.

BLE devices communicate with things called services and Characteristic

GATT uses the Attribute Protocol (ATT). The ATT Protocol stores the data corresponding to services and Characteristic in a query table. The secondary lookup table uses 16 bit ids as the index of each item.

GATT connections are exclusive. A BLE peripheral can only be connected to one central device at a time. As soon as the peripheral is connected, it stops broadcasting, making it invisible to other devices. When the peripheral is disconnected from the central device, the peripheral broadcasts again to let other central devices sense the presence of the peripheral. The central device can connect to multiple peripherals at the same time.

GATT communication

If the central device and peripherals need two-way communication, the only way is to establish a GATT connection.

The two sides of GATT communication are C/S relationship. As a GATT Server, peripherals maintain ATT lookup tables and the definitions of services and characteristic. The central device is the GATT Client, which makes requests to peripherals to obtain data.

GATT structure

  • Profile: Does not actually exist on BLE peripherals, but is simply a collection of services predefined by the Bluetooth SIG or the peripherals designer. For example, Heart Rate Profile is a combination of Heart Rate Service and Device Information Service.

  • Service: contains one or more Characteristic. Each Service has a unique UUID identifier.

  • Characteristic: Is the smallest logical data unit. A Characteristic variable consists of a single value variable and 0-n Characteristic variable descriptors. Just like services, each Characteristic is uniquely identified with a 16 bit or 128 bit UUID.

In practical development, dealing with BLE peripherals is mainly throughCharacteristic. Available from theCharacteristicYou can read data, or you can go toCharacteristicWrite data, so as to achieve two-way communication.

Uuids are available in 16 -, 32 -, and 128 - bit formats. The 16-bit UUID is officially certified and needs to be purchased.Copy the code

Bluetooth_Base_UUID is defined as 00000000-0000-1000-8000-00805f9b34FB

  • If the 16-bit UUID is XXXX, convert it to the 128-bit UUID0000xxxx-0000-1000-8000-00805F9B34FB
  • If the 32-bit UUID is XXXXXXXX, convert it to the 128-bit UUIDxxxxxxxx-0000-1000-8000-00805F9B34FB

2. Communication between central equipment and peripherals

A brief introduction to the major classes in BLE development and their roles:

  • BluetoothDeivce: Bluetooth device that stands for a specific Bluetooth peripheral.
  • BluetoothGatt: Generic attribute protocol that defines the basic rules and operations of BLE communication
  • BluetoothGattCallback: GATT communication callback class for callbacks to various states and results.
  • BluetoothGattService: a service consisting of zero or more characteristic groups.
  • BluetoothGattCharacteristic: characteristics, which contains one or more sets of data, is the smallest in GATT communication data unit. BluetoothGattDescriptor: Feature descriptors, additional descriptions of features, including but not limited to units of features, attributes, etc.

Gets the Bluetooth device object

The scanned Bluetooth can be cached in the form of a set, or only its MAC address can be saved and stored in a character set for subsequent connection.

BluetoothDeivce is obtained according to the MAC address for connection

BluetoothManager bluetoothmanager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothmanager.getAdapter(); / / get the bluetooth device object to connect mBluetoothDevice = mBluetoothAdapter. GetRemoteDevice (macAddressStr)Copy the code

Bluetooth GATT callback

Implementation of BluetoothGattCallBack class, listening to bluetooth connection in the process of various callback listening. There should be no time-consuming operations in any Bluetooth Gatt callback method. You need to dump the operations in its method into another thread and return as soon as possible.

// Define a child thread, handle, to which the operations in the BluetoothGattCallback callback are thrown. private Handler mHandler; Private HandlerThread mHandlerThread; MHandlerThread = new HandlerThread("daqi"); mHandlerThread.start(); MHandler = new Handler(mHandlerThread.getLooper()); / / define bluetooth Gatt callback class public class daqiBluetoothGattCallback extends BluetoothGattCallback {/ / connection status callback @ Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); // status is used to check whether the operation is successful. An exception code is returned. // newState returns connection status, such as BluetoothProfile# STATE_DISCONNECTED, BluetoothProfile# STATE_CONNECTED// If the operation succeedsif(status == bluetoothgatt.gatt_success){// Check whether the connection codeif (newState == BluetoothProfile.STATE_CONNECTED) {
                
                }else if(newState == bluetoothprofile.state_disconnected){// Determine whether to break the connection code}}else@override public void onServicesDiscovered(BluetoothGatt, BluetoothGatt, int status) { super.onServicesDiscovered(gatt, status); } / / write callback feature @ Override public void onCharacteristicWrite (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); } @override public void onCharacteristicChanged(BluetoothGatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); } @override public void onDescriptorWrite(BluetoothGatt, BluetoothGattDescriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); }}Copy the code

Connected devices

BluetoothDevice#connectGatt() is called for a ble connection, the second parameter is set to false by default, not automatically connected. And define the BluetoothGatt variable to store the object returned by BluetoothDevice#connectGatt().

BluetoothGatt mBluetoothGatt; / / create the Gatt callback private BluetoothGattCallback mGattCallback = new daqiBluetoothGattCallback (); // Connect the deviceif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    mBluetoothGatt = mBluetoothDevice.connectGatt(mContext,
            false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
    mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback);
}
Copy the code
Connection Exception Handling

Bluetooth connection may not be 100% successful. If the connection fails, an exception code is returned to describe the error. For most exception codes, the connection can be successfully achieved through reconnection.

Error code:

  • 133: Connection timed out or device not found.
  • 8: The device is out of range
  • 22: indicates that the local device is disconnected
Private int reConnectionNum = 0; Private int maxConnectionNum = 3; Public class daqiBluetoothGattCallback extends BluetoothGattCallback {/ / connection status callback @ Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); // status is used to check whether the operation is successful. An exception code is returned. // If the operation succeedsif (status == BluetoothGatt.GATT_SUCCESS){
            
        }else{// The number of reconnections cannot exceed the maximum number of reconnectionsif(reConnectionNum < maxConnectionNum){// The number of reconnections increases automatically. ReConnectionNum ++ // Connects the deviceif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    mBluetoothGatt = mBluetoothDevice.connectGatt(mContext,
                            false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
                } else {
                    mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback); }}else{// Disconnect, return connection failure callback}}} // Other callback methods}Copy the code

The discovery service

After a successful connection, the BluetoothGattCallback#onConnectionStateChange() method is triggered.

Public class daqiBluetoothGattCallback extends BluetoothGattCallback {/ / connection status callback @ Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); // status is used to check whether the operation is successful. An exception code is returned. // If the operation succeedsif(status == bluetoothgatt.gatt_success){// Check whether the connection codeif(newState == bluetoothprofile.state_connected) {// The discovery service can be delayed, Also don't delay mHandler. Post (() - > / / discovery service mBluetoothGatt discoverServices ();) ; }else if(newState == bluetoothprofile.state_disconnected){// Determine whether to break the connection code}}} // other callback methods}Copy the code

When a service is discovered successfully, the BluetoothGattCallback#onServicesDiscovered() callback is triggered:

Private UUID mServiceUUID = UUID. FromString ("0000xxxx-0000-1000-8000-00805f9b34fb"); / / define bluetooth Gatt callback class public class daqiBluetoothGattCallback extends BluetoothGattCallback {/ / service discovery callback @ Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status);if(status == bluetoothgatt.gatt_success) {mhandler.post (() -> // BluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID); // The specified service is not nullif(gattService ! = null){ }else{// failed to get a specific service}); }}}Copy the code
Service Discovery failure

During service discovery, certain services may fail to be discovered. In other words, the entire BluetoothGatt object has an empty list of services. There is a hidden method refresh () in the BluetoothGatt class that refreshes the list of Gatt services. When the service cannot be found, the method can be invoked through reflection to discover the service again.

Read and modify eigenvalues

Private UUID mServiceUUID = UUID. FromString ("0000xxxx-0000-1000-8000-00805f9b34fb"); // Define the type of CharacteristicUUID that needs to be processed private UUID mCharacteristicUUID = uuid.fromString ("0000yyyy-0000-1000-8000-00805f9b34fb"); / / define bluetooth Gatt callback class public class daqiBluetoothGattCallback extends BluetoothGattCallback {/ / service discovery callback @ Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status);if(status == bluetoothgatt.gatt_success) {mhandler.post (() -> // BluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID); // The specified service is not nullif(gattService ! = null) {/ / access to specify uuid Characteristic BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(mCharacteristicUUID); // The specific feature was obtained successfullyif(gattCharacteristic! = null) {/ / writer you need to pass to the eigenvalues of the peripherals. (that is, information is passed to the peripherals) gattCharacteristic setValue (bytes); // Write eigenvalues to peripherals via the GATt entity class. mBluetoothGatt.writeCharacteristic(gattCharacteristic); / / if only need the eigenvalues of the read peripherals: / / reading specific features from the Gatt object (Characteristic) eigenvalues of mBluetoothGatt. ReadCharacteristic (gattCharacteristic); }}else{// failed to get a specific service}); }}}Copy the code

The BluetoothGattCallback#onCharacteristicRead() callback is triggered when the characteristic value is successfully read.

@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    super.onCharacteristicRead(gatt, characteristic, status);
    if(status == bluetoothgatt.gatt_success) {// Get the read characteristic.getValue()}}Copy the code

The BluetoothGattCallback#onCharacteristicWrite() callback is triggered when the characteristic value is successfully written to the peripheral.

@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    super.onCharacteristicWrite(gatt, characteristic, status);
    if(status == bluetoothgatt.gatt_success) {// Get the characteristic values written to the peripherals.getValue()}}Copy the code

Listen for peripheral eigenvalues to change

Either writing new values to the peripheral or reading values of a particular Characteristic of the peripheral, it is only one-way communication. If two-way communication is needed, BluetoothGattCallback#onServicesDiscovered sets listening on a Characteristic value (if the Characteristic has the NOTIFY property) :

/ / set the subscription notificationGattCharacteristic values change notification
mBluetoothGatt.setCharacteristicNotification(notificationGattCharacteristic, true);
// Get the corresponding notification Descriptor
BluetoothGattDescriptor descriptor = notificationGattCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if(descriptor ! =null) {// Set the notification value
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
	boolean descriptorResult = mBluetoothGatt.writeDescriptor(descriptor);
}
Copy the code

If the peripheral modifies its characteristic value to reply after writing the characteristic value, the mobile phone will trigger the BluetoothGattCallback#onCharacteristicChanged() method to obtain the value returned by the peripheral, thus realizing the two-way communication.

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
     ifBluetoothgatt.gatt_success (status == bluetoothgatt.gatt_success) {String value = characteristic.getValue()}Copy the code

disconnect

The disconnection operation is divided into two steps:

  • mBluetoothGatt.disconnect();
  • mBluetoothGatt.close();

If you call disconnect(), it will trigger the phone will trigger the BluetoothGattCallback#onConnectionStateChange() callback, which calls the disconnect message, NewState because = BluetoothProfile STATE_DISCONNECTED. But calling close() immediately after disconnect() terminates the BluetoothGattCallback#onConnectionStateChange() callback. Disconnection can be achieved by splitting the two calls as necessary, but both methods must be called.

For example, the need to disconnect if the peripheral changes the characteristic value triggering bluetoothattCallback #onCharacteristicChanged(). You can call disconnect() in BluetoothGattCallback#onCharacteristicChanged() and wait for the BluetoothGattCallback#onConnectionStateChange() callback , returns the disconnection information, and then calls close() to close the Gatt resource.

When ble communication with peripherals occurs, the disconnection operation is called immediately if any unexpected situation occurs.

Android BLE series:

Android Bluetooth BLE (I) — Scan

Android Bluetooth BLE (2) — Communication

Android Bluetooth BLE (3) — Broadcast

Android Bluetooth BLE (4) — Practical