BLE basic theory/Android BLE development example

Reference: https://blog.csdn.net/qq_36075612/article/details/127739150?spm=1001.2014.3001.5502
Reference: https://blog.csdn.net/qq_36075612/article/details/122772966?spm=1001.2014.3001 .5502

Classification of Bluetooth

traditional bluetooth


Traditional Bluetooth is developed and improved based on the previous 1.0.1.2, 2.0+EDR, 2.1+EDR, 3.0+EDR, etc.

  • Traditional Bluetooth can be used for transmission with relatively large amounts of data, such as voice, music, and transmission of relatively large data amounts;
  • Low-power Bluetooth is used in products with high real-time requirements but low data rates, such as remote controls, such as mice, keyboards, remote control mice (Air Mouse), and data transmission of sensing devices, such as heartbeat belts and blood pressure meter, temperature sensor, etc.

Traditional Bluetooth has 3 power levels, Class1, Class2, and Class3, which support transmission distances of 100m, 10m, and 1m respectively. However, low-power Bluetooth has no power level. Generally, the transmission power is 7dBm. Generally, in the open distance, it should not reach 20m . questionable.

Due to Apple's restrictions on the classic Bluetooth data transmission interface (which requires MFI certification) and high power consumption, it is slowly
being eliminated in current mobile Internet applications.

Bluetooth Low Energy

Bluetooth low energy is developed based on Nokia's Wibree standard.

Bluetooth Low Energy (BLE) technology is a low-cost, short-range, interoperable and robust wireless technology that operates in the license-free 2.4GHz ISM radio frequency band. It was designed from the ground up as ultra-low power (ULP) wireless technology. It utilizes many smart tricks to minimize power consumption.

The Bluetooth low energy architecture consists of two chips: single-mode chip and dual-mode chip.

  • Single-mode chips can communicate with other single-mode chips and dual-mode chips. At this time, the latter need to use the Bluetooth low-power technology part of its own architecture to send and receive data.
  • The dual-mode chip can also communicate with standard Bluetooth technology and other dual-mode chips using traditional Bluetooth architecture.

Bluetooth professional vocabulary (original text)

SAY

The Bluetooth Special Interest Group is a trade association composed of leading players in the telecommunications, computer, automotive manufacturing, industrial automation and networking industries. The group is dedicated to advancing Bluetooth wireless technology, developing and bringing to market low-cost wireless specifications for connecting mobile devices over short distances.

Profile

A very important feature of Bluetooth is that all Bluetooth products must implement all Bluetooth specifications. In order to make it easier to maintain compatibility between Bluetooth devices, Profile is defined in the Bluetooth specification. Profile defines how the device implements a connection or application. You can understand Profile as the connection layer or application layer.Protocol specifications.
The Bluetooth organization stipulates some standard profiles, such as HID OVER GATT, anti-loss device, heart rate meter, etc. Each profile will contain multiple services, and each service represents a capability of the slave machine.

service

service can be understood as a service. In the BLE slave, there are multiple services, such as power information service, system information service, etc., and each service contains multiple characteristic values. Each specific characteristic value is the subject of BLE communication. For example, the current power is 80%, so the characteristic value of the power will be stored in the profile of the slave, so that the master can read the data of 80% through this characteristic.

characteristic

Characteristic characteristic value. The communication between BLE master and slave is realized through characteristic, which can be understood as a label and an attribute. Through this label, the desired content can be obtained or written.

decriptor

Descriptor Descriptor. Descriptors describe Characteristics. Descriptors have read and write attributes , and descriptors can also be read and written.

UUID

UUID, unified identification code. The service and characteristic we just mentioned require a unique UUID to identify them.

The UUID defined by SIG shares a basic UUID: 0x0000xxxx-0000-1000-8000-00805F9B34FB,
with a total of 128 bits. To further simplify the base UUID, each SIG-defined attribute has a unique 16-bit UUID that replaces the 'x' part of the base UUID above. Using 16-bit UUID is convenient for memory and operation. For example, SIG defines the 16-bit UUID of "Device Information" as 0x180A.

Several Profiles of Bluetooth (original text)

Among all Profiles, there are four basic Profiles, which will be used by other Profiles, including GAP/SDAP/SPP/GOEP Profile.

GAP Profile

GAP Profile: Generic Access Profile, this Profile ensures that different Bluetooth products canDiscover each other and connect. GAP specifies some general operating tasks. Therefore, it is mandatory and serves as the basis for all other Bluetooth application specifications.

SDAP Profile

SDAP Profile: Service Discovery Application Profile. Through this Profile, a Bluetooth device can find the information provided by other Bluetooth devices.Serve, and query related information.

SPP Profile

The full name is Serial Port Profile, which defines how to establish a virtual serial port and connect two BT devices.
This connection can be established, for example, between two computers or Labtops.

GOEP Profile

GOEP Profile: Generic Object Exchange Profile, general object exchange. The name of this Profile is somewhat puzzling. It defines the transmission of data, including synchronization, file transfer, or pushing other data. It can be understood as a content-independent transport layer protocol that can be used by any application to transmit its own defined data objects.

A2DP Profile

The full name of A2DP Profile is Advanced Audio Distribution Profile Bluetooth audio transmission model protocol.

DUN Profile

DUN Profile, the full name of Dial-up Networking (DUN) Profile, enables one Bluetooth device to share the Internet through another Bluetooth device with wireless function.

VRCP Profile

AVRCP (Audio/Video Remote Control Profile), which is the audio/video remote control profile.

HID Profile

HID stands for Human Interface Device Profile, which is Human Interface Device Profile.

Bluetooth Low Energy

Now Bluetooth Low Energy (BLE) connections are based on the GATT (Generic Attribute Profile) protocol. GATT is a general specification for sending and receiving short data segments over a Bluetooth connection. These short data
segments are called attributes.

GAP makes your device visible to other devices and determines if and how your device can interact with contracted devices. For example, the Beacon device only broadcasts to the outside world and does not support connection. The Mi Band waits for the device to connect to the central device.

GAP device role

GAP defines several roles for the device, the main two of which are: peripheral device (Peripheral) and central device
(Central).

  • Peripherals: These are generally very small or simple low-power devices that provide data and connect to a more
    powerful central device. For example, Xiaomi bracelet.
  • Central device: The central device is relatively powerful and is used to connect other peripheral devices. Such as mobile phones and so on.

GAP broadcast data

In GAP, peripheral devices broadcast data to the outside world in two ways: Advertising Data Payload ( broadcast data ) and Scan Response Data Payload ( scan reply ). Each type of data can contain up to 31 bytes.

Broadcast data is necessary here because the peripheral must constantly broadcast to the outside to let the central device know its existence. The scan
reply is optional. The central device can request a scan reply from the peripheral device, which contains some additional information about the device, such as the
name of the device.

GAP broadcast process

The broadcast workflow of GAP is shown in the figure below.

Insert image description here

From the figure we can clearly see how broadcast data and scan reply data work. The peripheral device will set a broadcast interval, and during each broadcast interval, it will resend its broadcast data. The longer the broadcast interval is, the more power is saved and it is less easy to scan.

Broadcast network topology

In most cases, the peripheral device broadcasts itself to let the central device discover itself and establish a GATT connection to exchange more data. In some cases, no connection is required, as long as the peripheral device broadcasts its own data. The main purpose of using this method is to allow peripheral devices to send their own information to multiple central devices. Because based on the GATT connection method, only one peripheral device can be connected to a central device. The most typical application that uses broadcasting is Apple's iBeacon. The network topology diagram in broadcast working mode is as follows:

Insert image description here

GATT connection

The full name of GATT is Generic Attribute Profile (generic attribute protocol).It defines that two BLE devices communicate through something called Service and Characteristic. GATT uses the ATT (Attribute Protocol) protocol. The ATT protocol stores the data corresponding to Service and Characteristic in a lookup table. The lookup table uses 16 bit ID as the index of each item.

Once the two devices establish a connection, GATT comes into effect, which also means that you must complete the previous GAP
protocol. What needs to be explained here is that the GATT connection must first go through the GAP protocol.

In fact, in Android development, weYou can directly use the device's MAC address to initiate a connection without going through the scanning step.. This does not mean that there is no need to go through GAP . In fact, it has been done for you at the chip level. When the Bluetooth chip initiates a connection, it always scans the device first, and then initiates the connection when it is scanned.

Special note about GATT connections: GATT connections are exclusive. That is, a BLE peripheral can only be
connected to one central device at the same time. Once a peripheral is connected, it stops broadcasting so that it is not visible to other devices. When the device disconnects, it starts broadcasting again.

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

GATT connected network topology

A peripheral device can only connect to one central device, and a central device can connect to multiple peripheral devices. Once the connection is established, the communication is two-way. Compared with the previous network topology of GAP broadcast, GAP communication is one-way. If you want two device peripherals to communicate, they can only be relayed through the central device.

Insert image description here

GATT communications matters

The two parties in GATT communication are in a C/S relationship. The peripheral device serves as the GATT server (Server), which maintains the ATT lookup table and the definition of service and characteristic. The central device is the GATT client (Client), which initiates a request to the Server. It should be noted that all communication events are initiated by the client (also called the master device, Master) and receive responses from the server (also called the slave device, Slave).

Once the connection is established, the peripheral device will propose a connection interval (Connection Interval) to the central device , so that the central device will try to reconnect at each connection interval.Check if there is new data. However, this connection interval is only a suggestion. Your central device may not strictly follow this interval . For example, your central device is busy connecting to other peripherals, or the central device resources are too busy.

The following figure shows the data exchange process between a peripheral device (GATT server) and the central device (GATT client). It can be seen that the master device initiates the request every time:

Insert image description here

There is also a slave latency parameter, which was mentioned in the previous blog post.

GATT structure

GATT transactions are built on nested Profiles, Services and Characteristics, as shown in the following figure:

Insert image description here

Profile

It does not actually exist on the BLE peripheral, it is just a collection of services predefined by the Bluetooth SIG or the peripheral designer. For example, Heart Rate Profile combines Heart Rate Service and Device Information Service. A list of all officially adopted GATT Profiles can be found here.

Service

It divides the data into independent logical items, which contains one or more Characteristic. Each Service has a UUID unique identifier. UUID has 16 bit or 128 bit. The 6-bit UUID is officially certified and needs to be purchased at a cost. The 128-bit UUID is customizable and can be set at will.

Some standard services have been officially adopted. The complete list is here (the link is invalid). Taking the Heart Rate Service as an example, you can see that its official 16-bit UUID is 0x180D, which contains 3 Characteristics: Heart Rate Measurement, Body Sensor Location, and Heart Rate Control Point. point), and defines that only the first one is required, and the others are optional.

Characteristic

The lowest level in the GATT transaction is Characteristic, which is the smallest logical data unit. Of course, it may contain a group of associated data, such as the X/Y/Z three-axis value of the accelerometer.

Similar to Service, each Characteristic is uniquely identified by a 16-bit or 128-bit UUID. You can
use the standard Characteristic officially defined by Bluetooth SIG for free. Using the officially defined standard can ensure that BLE software and hardware can understand each other. Of course, you can customize the Characteristic so that only your own software and peripherals can understand each other.

For example, Heart Rate Measurement Characteristic, which is the Characteristic that the Heart Rate Service mentioned above must implement, and its UUID is 0x2A37.
Its data structure is that the first 8 bits define the heart rate data format (is it UINT8 or UINT16?), followed by the actual heart rate data in the corresponding format.

In fact, dealing with BLE peripherals is mainly through Characteristic. You can read data from Characteristic and write data to Characteristic . This achieves two-way communication.

So you can implement a Sevice similar to a serial port (UART) by yourself. This Service contains two Characteristics, one is configured as a read-only channel (RX), and the other is configured as a write-only channel (TX).

more content

Bluetooth SIG official documentation, if you want to learn more about it, you can read it carefully.
 Bluetooth Core Protocol Documentation
 Bluetooth Developer Portal
 Official BLE Profile
 Official BLE Service
 Official BLE Characteristic

 Recommend a book to everyone, "The Definitive Guide to Bluetooth Low Energy"
 Tutorial source code address: https://github.com/HX-IoT/

BLE Bluetooth parameter settings: Bluetooth scan list number/scan package/scan response package settings, etc.

https://blog.csdn.net/WHMTBYY/article/details/125089358
https://blog.csdn.net/WHMTBYY/article/details/125861715

Android BLE rapid development example

Use of FastBle

Declare permissions

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  • android.permission.BLUETOOTH : This permission allows the program to connect to the paired Bluetooth device. Requesting connection/receiving connection/transmitting data needs to change the permission, which is mainly used to operate after pairing;
  • android.permission.BLUETOOTH_ADMIN : This permission allows the program to discover and pair Bluetooth devices. This permission is used to manage Bluetooth devices. With this permission, the application can use the local Bluetooth device, mainly for operations before pairing;
  • android.permission.ACCESS_COARSE_LOCATION and android.permission.ACCESS_FINE_LOCATION: After Android 6.0, these two permissions are required. Bluetooth scans the surrounding devices to obtain vague location information. These two permissions belong to the same group of dangerous permissions. After being declared in the manifest file, they need to be obtained dynamically at runtime.

Initialization and configuration

@Override
protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    BleManager.getInstance().init(getApplication());
    BleManager.getInstance()
            .enableLog(true)
            .setReConnectCount(1, 5000)
            .setOperateTimeout(5000);
}

Before use, you need to call the initialization init (Application app) method in advance. In addition, some customized configurations can be made, such as whether to display the internal log of the framework, the number of reconnections and the reconnection interval, and the operation timeout.

Scan for peripherals

As a central device, the APP wants to establish Bluetooth communication with peripheral hardware devices by first obtaining the device object through scanning. Before calling the scan method, you should first handle the following preparations.

  • Determine whether the current Android device supports BLE.
    The Bluetooth BLE function has been added to the system since Android 4.3.
 BleManager.getInstance().isSupportBle();
  • Determine whether the Bluetooth of the current Android device is turned on.
    You can directly call the following judgment method to judge whether the machine has turned on Bluetooth. If not, a prompt will be thrown to the user.
BleManager.getInstance().isBlueEnable();
  • Actively turn on Bluetooth.
    In addition to judging whether Bluetooth is turned on and prompting the user, we can also directly help the user turn on the Bluetooth switch through the program. There are several ways to turn on the Bluetooth:
    Method 1: Turn on Bluetooth directly through the Bluetooth adapter.
BleManager.getInstance().enableBluetooth();

Method 2: Guide the user to turn on Bluetooth through the startActivityForResult guidance interface.

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0x01);

It should be noted that the first method is asynchronous and it takes a while to turn on Bluetooth. After calling this method, Bluetooth will not be turned on immediately. If you need to scan immediately after using this method, it is recommended to maintain a blocking thread, internally query whether Bluetooth is on at regular intervals, and externally display the waiting UI to guide the user to wait until it is successfully turned on. Using the second method, the user will be guided to open it through a system pop-up box, and finally a callback notification in the form of onActivityResult will be used to notify whether the opening is successful.

  • Models 6.0 and above dynamically obtain location permissions.
    After Bluetooth is turned on, before scanning, you need to determine whether the current device is 6.0 or above. If so, you need to dynamically obtain the location permissions previously declared in the Manifest.

  • Configure scanning rules
    You can configure one or more scanning rules, or you can use the default (scan for 10 seconds) without configuring. During scanning, the scanned devices will be filtered according to the configured filtering options, and the filtered devices will be returned as a result. If the scan time is configured to be less than or equal to 0, infinite scanning will be implemented until BleManger.getInstance().cancelScan() is called to terminate the scan.

  BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
          .setServiceUuids(serviceUuids)      // 只扫描指定的服务的设备,可选
          .setDeviceName(true, names)         // 只扫描指定广播名的设备,可选
          .setDeviceMac(mac)                  // 只扫描指定mac的设备,可选
          .setAutoConnect(isAutoConnect)      // 连接时的autoConnect参数,可选,默认false
          .setScanTimeOut(10000)              // 扫描超时时间,可选,默认10秒
          .build();
  BleManager.getInstance().initScanRule(scanRuleConfig);

After the above preparations are completed, you can start scanning.

  BleManager.getInstance().scan(new BleScanCallback() {
    
    
      @Override
      public void onScanStarted(boolean success) {
    
    
      }
 
      @Override
      public void onLeScan(BleDevice bleDevice) {
    
    
      }
 
      @Override
      public void onScanning(BleDevice bleDevice) {
    
    
      }
 
      @Override
      public void onScanFinished(List<BleDevice> scanResultList) {
    
    
      }
  });
  • onScanStarted(boolean success): will return to the main thread, and the parameter indicates whether the scanning action is started successfully. Because Bluetooth is not turned on, the last scan has not ended, etc., the scan may fail to start.
  • onLeScan(BleDevice bleDevice): Callback for all scanned results during the scanning process. Since the scanning and filtering process is in the working thread, this method is also in the working thread. The same device will carry its own different status (such as signal strength, etc.) at different times and appear in this callback method. The number of occurrences depends on the number of surrounding devices and the broadcast interval of peripheral devices.
  • onScanning(BleDevice bleDevice): Callback for all filtered results during the scanning process. The difference from onLeScan is that it will return to the main thread; the same device will only appear once; the device that appears is the device filtered by the scan filtering rules.
  • onScanFinished(List scanResultList): A collection of all scanned and filtered devices during this scanning period. It will return to the main thread, which is equivalent to the sum of onScanning devices.

Device Information

The scanned BLE peripheral device will be in the form of a BleDevice object as the smallest unit object for subsequent operations. It itself contains this information:

  • String getName(): Bluetooth broadcast name
  • String getMac(): Bluetooth Mac address
  • byte[] getScanRecord(): Broadcast data carried when scanned
  • int getRssi(): signal strength when scanned

This object will be used in subsequent device connection, disconnection, device status determination, read and write operations, etc. It can be understood as the carrier of peripheral Bluetooth devices. All operations on peripheral Bluetooth devices are conducted through this object.

Connect, disconnect, and monitor connection status

After getting the device object, you can perform connection operations.

    BleManager.getInstance().connect(bleDevice, new BleGattCallback() {
    
    
        @Override
        public void onStartConnect() {
    
    
        }
 
        @Override
        public void onConnectFail(BleException exception) {
    
    
        }
 
        @Override
        public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
    
    
        }
 
        @Override
        public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
    
    
        }
    });
  • onStartConnect(): Start connecting.
  • onConnectFail(BleException exception): The connection was unsuccessful.
  • onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status): The connection is successful and the service is discovered.
  • onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status): The connection is disconnected, especially when it is disconnected after being connected. Here you can monitor the connection status of the device. Once the connection is disconnected, you can consider reconnecting the BleDevice object according to your own situation. It should be noted that it is best to leave a period of time between disconnection and reconnection, otherwise you may be unable to connect for a long time. In addition, if the disconnect(BleDevice bleDevice) method is called, the result of actively disconnecting the Bluetooth connection will also be called back in this method. At this time, isActiveDisConnected will be true.

GATT agreement

BLE connections are based on the GATT (Generic Attribute Profile) protocol. GATT is a general specification for sending and receiving short data segments over Bluetooth connections. These short data segments are called attributes. It defines two BLE devices to communicate through Service and Characteristic. GATT uses the ATT (Attribute Protocol) protocol. The ATT protocol stores Service, Characteristic and corresponding data in a lookup table. The secondary lookup table uses 16 bit ID as the index of each item.

This part of GATT will be explained in detail below. In short, if the central device and peripheral devices need two-way communication, the only way is to establish a GATT connection. When the connection is successful, a GATT connection is established between the peripheral device and the central device.

The connect(BleDevice bleDevice, BleGattCallback bleGattCallback) method mentioned above actually has a return value, and this return value is BluetoothGatt. Of course, there are other ways to obtain the BluetoothGatt object. After the connection is successful, call:

BluetoothGatt gatt = BleManager.getInstance().getBluetoothGatt(BleDevice bleDevice)

Through the BluetoothGatt object as a connection bridge, the central device can obtain a lot of information about the peripheral device, as well as two-way communication.

First, you can obtain the Service and Characteristic owned by this Bluetooth device. Each attribute can be defined for different purposes, and protocol communication can be carried out through them. The following method is to find all Service and Characteristic UUIDs through BluetoothGatt :

    List<BluetoothGattService> serviceList = bluetoothGatt.getServices();
    for (BluetoothGattService service : serviceList) {
    
    
        UUID uuid_service = service.getUuid();
 
        List<BluetoothGattCharacteristic> characteristicList= service.getCharacteristics();
        for(BluetoothGattCharacteristic characteristic : characteristicList) {
    
    
            UUID uuid_chara = characteristic.getUuid();
        }
    }

Protocol communication

After the APP has established a connection with the device and knows the Service and Characteristic (which need to be communicated and confirmed with the hardware protocol), we can communicate through the BLE protocol. The bridge of communication is mainly through standard or customized Characteristic, which we call "characteristic" in Chinese. We can read and write data from Characteristic. This achieves two-way communication. From the perspective of APP as a central device, there are three main communication methods commonly used for data interaction: receiving notifications, writing, and reading. In addition, there are communication operations such as setting the maximum transmission unit and obtaining real-time signal strength.

  • Receiving notifications
    There are two ways to receive notifications, indicate and notify.
    The difference between indicate and notify is that, indicate will definitely receive data, while notify may lose data. The bottom layer of indicate encapsulates the response mechanism. If no response is received from the central device, it will be sent again until it succeeds; while notify will not have a response from the central device for receiving the data, and may not be able to guarantee the accuracy of data arrival. The advantage is that it is fast. Normally, when a peripheral device needs to continuously send data to the APP, such as the pressure change of the blood pressure monitor during the measurement process, or the real-time data transmission of the fetal heart rate monitor during the monitoring process, in such frequent cases, notify is given priority. form. When only a small and very important piece of data needs to be sent to the APP, give priority to the indicate form. Of course, from the perspective of Android development, if the hardware has already considered mature protocols and sending methods, all we need to do is to make the corresponding connection according to its configured data sending method.
    open notify
  BleManager.getInstance().notify(
          bleDevice,
          uuid_service,
          uuid_characteristic_notify,
          new BleNotifyCallback() {
    
    
              @Override
              public void onNotifySuccess() {
    
    
                  // 打开通知操作成功
              }
 
              @Override
              public void onNotifyFailure(BleException exception) {
    
    
                  // 打开通知操作失败
              }
 
              @Override
              public void onCharacteristicChanged(byte[] data) {
    
    
                  // 打开通知后,设备发过来的数据将在这里出现
              }
          });

close notify

  BleManager.getInstance().stopNotify(uuid_service, uuid_characteristic_notify);

open indicate

  BleManager.getInstance().indicate(
          bleDevice,
          uuid_service,
          uuid_characteristic_indicate,
          new BleIndicateCallback() {
    
    
              @Override
              public void onIndicateSuccess() {
    
    
                  // 打开通知操作成功
              }
 
              @Override
              public void onIndicateFailure(BleException exception) {
    
    
                  // 打开通知操作失败
              }
 
              @Override
              public void onCharacteristicChanged(byte[] data) {
    
    
                  // 打开通知后,设备发过来的数据将在这里出现
              }
          });

Close indicate

  BleManager.getInstance().stopIndicate(uuid_service, uuid_characteristic_indicate);

The notification operation here uses two key parameters, uuid_service and uuid_characteristic_notify (or uuid_characteristic_indicate), which are the Service and Characteristic mentioned above, which are represented here in the form of strings and are not case-sensitive.

  • read and write
  BleManager.getInstance().read(
          bleDevice,
          uuid_service,
          uuid_characteristic_read,
          new BleReadCallback() {
    
    
              @Override
              public void onReadSuccess(byte[] data) {
    
    
                  // 读特征值数据成功
              }
 
              @Override
              public void onReadFailure(BleException exception) {
    
    
                  // 读特征值数据失败
              }
          });
 
  BleManager.getInstance().write(
          bleDevice,
          uuid_service,
          uuid_characteristic_write,
          data,
          new BleWriteCallback() {
    
    
              @Override
              public void onWriteSuccess(int current, int total, byte[] justWrite) {
    
    
                  // 发送数据到设备成功(分包发送的情况下,可以通过方法中返回的参数可以查看发送进度)
              }
 
              @Override
              public void onWriteFailure(BleException exception) {
    
    
                  // 发送数据到设备失败
              }
          });

When sending BLE data to each other, up to 20 bytes can be sent at a time. If the data that needs to be sent exceeds 20 bytes, there are two methods. One is to actively try to widen the MTU, and the other is to use packetized transmission. The write method in the framework defaults to subpackaging when data exceeds 20 bytes.

  • Set the maximum transmission unit MTU
  BleManager.getInstance().setMtu(bleDevice, mtu, new BleMtuChangedCallback() {
    
    
      @Override
      public void onSetMTUFailure(BleException exception) {
    
    
          // 设置MTU失败
      }
 
      @Override
      public void onMtuChanged(int mtu) {
    
    
          // 设置MTU成功,并获得当前设备传输支持的MTU值
      }
  });
  • Get the device’s real-time signal strength Rssi
  BleManager.getInstance().readRssi(
          bleDevice,
          new BleRssiCallback() {
    
    
 
              @Override
              public void onRssiFailure(BleException exception) {
    
    
                  // 读取设备的信号强度失败
              }
 
              @Override
              public void onRssiSuccess(int rssi) {
    
    
                  // 读取设备的信号强度成功
              }
          });

During the communication process of BLE devices, I would like to share some experiences with you:

  • It is best to leave a short period of time between two operations, such as 100ms (the specific time can be extended or shortened based on your actual Bluetooth peripherals). For example, after onConnectSuccess, delay 100ms before notify, and then delay 100ms before writing.
  • During the connection and post-connection processes, always pay attention to the onDisConnected method and then handle it.
  • If you need to reconnect after disconnection, please delay for a while, otherwise it will cause congestion.

BLE development basics

Before decomposing the FastBle source code, I first introduce some theoretical knowledge of BLE communication.

Introduction to Bluetooth

Bluetooth is a short-range wireless communication technology. Its characteristic is short-range communication. The typical distance is within 10 meters. The transmission speed can reach up to 24 Mbps. It supports multiple connections and has high security. It is very suitable for use on smart devices.

Version evolution of Bluetooth technology

  • Version 1.0 was released in 1999 and is now rarely seen on the market;
  • Version 1.1 was released in 2002 and is now rarely seen on the market;
  • Version 2.0 was released in 2004 and is now rarely seen on the market;
  • Version 2.1, released in 2007, was the most widely used before and is what we call classic Bluetooth.
  • In 2009, Bluetooth version 3.0 was launched, which is the so-called high-speed Bluetooth, and the transmission rate can theoretically be as high as 24 Mbit/s;
  • Bluetooth version 4.0 was launched in 2010. It is a culmination of previous versions and includes classic Bluetooth, high-speed Bluetooth and Bluetooth low energy protocols. Classic Bluetooth includes the old Bluetooth protocol, high-speed Bluetooth is based on Wi-Fi, and low-power Bluetooth is BLE.
  • In 2016, the Bluetooth Technology Alliance proposed a new Bluetooth technology standard, Bluetooth version 5.0. Bluetooth 5.0 has corresponding improvements and optimizations for the speed of low-power devices. It is combined with WiFi to assist indoor positioning, improves transmission speed, and increases effective working distance. It is mainly aimed at improvements in the direction of the Internet of Things.

Gradual evolution of BLE functionality on Android

In the Android development process, version fragmentation has always been an issue that needs to be considered. In addition, manufacturer customization and Bluetooth itself are also in the development process like Android, so we need to know what functions each version supports.

  • Starting from Android 4.3, the BLE function is supported, but only Central Mode is supported.
  • Starting from Android 5.0, Peripheral Mode is supported

What do central mode and peripheral mode mean?

  • Central Mode: The Android side serves as the central device and connects other peripheral devices.
  • Peripheral Mode: The Android terminal serves as a peripheral device and is connected to other central devices. After Android 5.0 supports peripheral mode, two Android phones can communicate with each other through BLE.

Bluetooth broadcast and scan

The following content is partially referenced from BLE Introduction.

Regarding this part, a concept needs to be introduced, GAP (Generic Access Profile), which is used to control device connection and broadcast. GAP makes your device visible to other devices and determines whether or how your device can interact with the device. For example, the Beacon device only sends out broadcasts and does not support connections; the Xiaomi bracelet can establish a connection with the central device.

In GAP, Bluetooth devices can broadcast data packets to the outside world. The broadcast packets are divided into two parts: Advertising Data Payload (broadcast data) and Scan Response Data Payload (scan reply).Each type of data can contain up to 31 bytes. Broadcast data is necessary here because the peripheral must constantly broadcast to the outside to let the central device know its existence. The scan reply is optional. The central device can request a scan reply from the peripheral device, which contains some additional information about the device, such as the name of the device. In Android, the system will splice these two data together and return a 62-byte array. These broadcast data can be parsed manually by yourself. In Android 5.0, ScanRecord is also provided to help you parse. You can directly obtain meaningful data through this class. What data types can be included in broadcasts? Device connection attributes identify the BLE mode supported by the device. This is required. Device name, key GATT service contained in the device, or Service data, manufacturer-defined data, etc.

Insert image description here

The peripheral device will set a broadcast interval, and during each broadcast interval, it will resend its broadcast data. The longer the broadcast interval is, the more power is saved and it is less easy to scan.

As just mentioned, GAP determines how your device interacts with other devices. The answer is there are 2 ways:

  • Completely based on broadcasting,
    there are also cases where no connection is required, as long as the peripheral device broadcasts its own data. The main purpose of using this method is to allow peripheral devices to send their own information to multiple central devices. The most typical application that uses broadcasting is Apple's iBeacon. This is a function defined by Apple based on BLE broadcast, which can realize advertising push and indoor positioning. This also shows that APP uses BLE and needs positioning permission.

  • Based on non-connection, this application relies on BLE broadcast, also called Beacon. There are two roles here. The party that sends the broadcast is called Broadcaster, and the party that listens to the broadcast is called Observer.

  • GATT connection-based method
    In most cases, the peripheral device broadcasts itself to let the central device discover itself and establish a GATT connection to exchange more data. There are only two roles here. The party initiating the connection is called the central device, and the connected device is called the peripheral device.
    Peripheral devices: These are generally very small or simple low-power devices that provide data and are connected to a more relatively powerful central device, such as a Xiaomi bracelet.
    Central device: The central device is relatively powerful and is used to connect other peripheral devices, such as mobile phones.
    GATT connections require special attention:GATT connections are exclusive. That is, a BLE peripheral can only be connected to one central device at the same time.(Multiple connections are time-sliced). Once a peripheral is connected, it stops broadcasting so that it is not visible to other devices. When the device disconnects, it starts broadcasting again. If the central device and peripheral devices need two-way communication, the only way is to establish a GATT connection.

The two parties in GATT communication are in a C/S relationship. The peripheral device serves as the GATT server (Server), which maintains the ATT lookup table and the definition of service and characteristic. The central device is the GATT client (Client), which initiates a request to the Server. It should be noted that all communication events are initiated by the client and receive the response from the server.

BLE communication basics

There are two important concepts at the basis of BLE communication, ATT and GATT.

  • ATT's
    full name is attribute protocol, and its Chinese name is "attribute protocol". It is the basis of BLE communication. ATT encapsulates data and exposes it as "attributes". The server provides the "attributes" and the client obtains the "attributes". ATT is specially designed for low-power Bluetooth, with a very simple structure and short data length.

  • The full name of GATT
    is Generic Attribute Profile, and its Chinese name is "generic attribute profile". It is a further logical encapsulation of ATT based on ATT, defining the interaction mode and meaning of data. GATT is a concept that we directly come into contact with when developing BLE.

  • GATT level
    GATT defines four concepts according to the level: configuration file (Profile), service (Service), feature (Characteristic) and description (Descriptor). Their relationship is as follows: Profile defines a practical application scenario, a Profile contains several Services, a Service contains several Characteristics, and a Characteristic can contain several Descriptors.

Insert image description here

  • Profile
    Profile does not actually exist on the BLE peripheral, it is just a collection of Services predefined by Bluetooth SIG or peripheral designers. For example, Heart Rate Profile combines Heart Rate Service and Device Information Service. A list of all officially adopted GATT Profiles can be found here.

  • Service
    Service divides data into independent logical items, which contains one or more Characteristics. Each Service has a UUID unique identifier. UUID has 16 bit or 128 bit. The 16-bit UUID is officially certified and needs to be purchased at a cost. The 128-bit UUID is customizable and can be set at will. Some standard services have been officially adopted. The complete list is here. Taking the Heart Rate Service as an example, you can see that its official 16-bit UUID is 0x180D, which contains 3 Characteristics: Heart Rate Measurement, Body Sensor Location and Heart Rate Control Point, and it is defined that only the first one is required. is optional.

  • Characteristic
    needs to focus on Characteristic, which defines values ​​and operations, including a Characteristic declaration, Characteristic attributes, values, and value descriptions (Optional). Usually the BLE communication we talk about is actually reading and writing Characteristic or subscribing to notifications. For example, during actual operation, when I read a certain Characteristic, I get the value of the Characteristic.

  • UUID
    Service, Characteristic and Descriptor are all uniquely identified by UUID.
    UUID is a globally unique identifier. It is a 128-bit value. In order to facilitate identification and reading, it is generally marked in hexadecimal notation of "8 digits - 4 digits - 4 digits - 4 digits - 12 digits", such as "12345678-abcd-1000- 8000-123456000000".
    However, the 128-bit UUID is too long. Considering that the data length is very limited in low-power Bluetooth, Bluetooth uses the so-called 16-bit or 32-bit UUID, in the following form: "0000XXXX-0000-1000-8000 -00805F9B34FB". Except for the digits "XXXX", the others are fixed, so in fact, the 16-bit UUID corresponds to a 128-bit UUID. As a result, UUIDs are greatly reduced. For example, there are only a limited number of 65536 (16 to the fourth power) 16-bit UUIDs. At the same time, because the number is limited, 16-bit UUID cannot be used casually. The Bluetooth SIG has predefined some UUIDs that we can use directly. For example, "00001011-0000-1000-8000-00805F9B34FB" is a UUID commonly seen in BLE devices. Of course, you can also spend money to customize a custom UUID.

FastBle source code analysis

Through the basic theory of BLE above, we can analyze that BLE communication actually involves the client initiating a connection with the server, and then finding its Characteristic through the server for data interaction between the two.

In the FastBle source code, first look at the connect() method in BleManager:

public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) {
    
    
    if (bleGattCallback == null) {
    
    
        throw new IllegalArgumentException("BleGattCallback can not be Null!");
    }
 
    if (!isBlueEnable()) {
    
    
        BleLog.e("Bluetooth not enable!");
        bleGattCallback.onConnectFail(new OtherException("Bluetooth not enable!"));
        return null;
    }
 
    if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) {
    
    
        BleLog.w("Be careful: currentThread is not MainThread!");
    }
 
    if (bleDevice == null || bleDevice.getDevice() == null) {
    
    
        bleGattCallback.onConnectFail(new OtherException("Not Found Device Exception Occurred!"));
    } else {
    
    
        BleBluetooth bleBluetooth = new BleBluetooth(bleDevice);
        boolean autoConnect = bleScanRuleConfig.isAutoConnect();
        return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback);
    }
 
    return null;
}

This method passes in the scanned peripheral device object, and after judging some necessary conditions, calls bleBluetooth.connect() to connect. Let's take a look at the BleBluetooth class:

public BleBluetooth(BleDevice bleDevice) {
    
    
    this.bleDevice = bleDevice;
}

The above BleBluetooth construction method is to pass in a Bluetooth device object. It can be seen that a BleBluetooth may represent the entire interaction process between your Android and this peripheral device, from the beginning of connection, to intermediate data interaction, to the entire process of disconnection. In the case of multiple connections, there are as many BleBluetooth objects maintained in the device pool as there are peripheral devices.

MultipleBluetoothController controls multiple device connections. It has methods to add and remove devices, such as addBleBluetooth and removeBleBluetooth as shown in the figure below. The parameters passed in are BleBluetooth objects, which verifies the above statement.

public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) {
    
    
    if (bleBluetooth == null) {
    
    
        return;
    }
    if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
    
    
        bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth);
    }
}
 
public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) {
    
    
    if (bleBluetooth == null) {
    
    
        return;
    }
    if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) {
    
    
        bleLruHashMap.remove(bleBluetooth.getDeviceKey());
    }
}

Back to BleBlutooth's connect method:

public synchronized BluetoothGatt connect(BleDevice bleDevice,
                                          boolean autoConnect,
                                          BleGattCallback callback) {
    
    
    addConnectGattCallback(callback);
    isMainThread = Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper();
    BluetoothGatt gatt;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    
    
        gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
                autoConnect, coreGattCallback, TRANSPORT_LE);
    } else {
    
    
        gatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(),
                autoConnect, coreGattCallback);
    }
    if (gatt != null) {
    
    
        if (bleGattCallback != null)
            bleGattCallback.onStartConnect();
        connectState = BleConnectState.CONNECT_CONNECTING;
    }
    return gatt;
}

It can be seen that the connectGatt() method of BluetoothDevice in the native API is ultimately called. As mentioned in the Bluetooth principle analysis, a BluetoothGattCallback should be created during the connection process to be used as a callback. This class is very important, and all callbacks for GATT operations are here. The coreGattCallback here should play this role. It is the implementation class object of BluetoothGattCallback, which encapsulates and distributes the operation callback results.

private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {
    
    
 
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    
    
        super.onConnectionStateChange(gatt, status, newState);
 
        if (newState == BluetoothGatt.STATE_CONNECTED) {
    
    
            gatt.discoverServices();
 
        } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
    
    
            closeBluetoothGatt();
            BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this);
 
            if (connectState == BleConnectState.CONNECT_CONNECTING) {
    
    
                connectState = BleConnectState.CONNECT_FAILURE;
 
                if (isMainThread) {
    
    
                    Message message = handler.obtainMessage();
                    message.what = BleMsg.MSG_CONNECT_FAIL;
                    message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
                    handler.sendMessage(message);
                } else {
    
    
                    if (bleGattCallback != null)
                        bleGattCallback.onConnectFail(new ConnectException(gatt, status));
                }
 
            } else if (connectState == BleConnectState.CONNECT_CONNECTED) {
    
    
                connectState = BleConnectState.CONNECT_DISCONNECT;
 
                if (isMainThread) {
    
    
                    Message message = handler.obtainMessage();
                    message.what = BleMsg.MSG_DISCONNECTED;
                    BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
                    para.setAcitive(isActiveDisconnect);
                    para.setBleDevice(getDevice());
                    message.obj = para;
                    handler.sendMessage(message);
                } else {
    
    
                    if (bleGattCallback != null)
                        bleGattCallback.onDisConnected(isActiveDisconnect, bleDevice, gatt, status);
                }
            }
        }
    }
 
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    
    
        super.onServicesDiscovered(gatt, status);
        BleLog.i("BluetoothGattCallback:onServicesDiscovered "
                + '\n' + "status: " + status
                + '\n' + "currentThread: " + Thread.currentThread().getId());
 
        if (status == BluetoothGatt.GATT_SUCCESS) {
    
    
            bluetoothGatt = gatt;
            connectState = BleConnectState.CONNECT_CONNECTED;
            isActiveDisconnect = false;
            BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(BleBluetooth.this);
 
            if (isMainThread) {
    
    
                Message message = handler.obtainMessage();
                message.what = BleMsg.MSG_CONNECT_SUCCESS;
                BleConnectStateParameter para = new BleConnectStateParameter(bleGattCallback, gatt, status);
                para.setBleDevice(getDevice());
                message.obj = para;
                handler.sendMessage(message);
            } else {
    
    
                if (bleGattCallback != null)
                    bleGattCallback.onConnectSuccess(getDevice(), gatt, status);
            }
        } else {
    
    
            closeBluetoothGatt();
            connectState = BleConnectState.CONNECT_FAILURE;
 
            if (isMainThread) {
    
    
                Message message = handler.obtainMessage();
                message.what = BleMsg.MSG_CONNECT_FAIL;
                message.obj = new BleConnectStateParameter(bleGattCallback, gatt, status);
                handler.sendMessage(message);
            } else {
    
    
                if (bleGattCallback != null)
                    bleGattCallback.onConnectFail(new ConnectException(gatt, status));
            }
        }
    }
 
    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
    
    
        super.onCharacteristicChanged(gatt, characteristic);
 
        Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleNotifyCallback) {
    
    
                BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
    
    
                    Handler handler = bleNotifyCallback.getHandler();
                    if (handler != null) {
    
    
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE;
                        message.obj = bleNotifyCallback;
                        Bundle bundle = new Bundle();
                        bundle.putByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
 
        iterator = bleIndicateCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleIndicateCallback) {
    
    
                BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
    
    
                    Handler handler = bleIndicateCallback.getHandler();
                    if (handler != null) {
    
    
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE;
                        message.obj = bleIndicateCallback;
                        Bundle bundle = new Bundle();
                        bundle.putByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }
 
    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
    
    
        super.onDescriptorWrite(gatt, descriptor, status);
 
        Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleNotifyCallback) {
    
    
                BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback;
                if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) {
    
    
                    Handler handler = bleNotifyCallback.getHandler();
                    if (handler != null) {
    
    
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_NOTIFY_RESULT;
                        message.obj = bleNotifyCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, status);
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
 
        iterator = bleIndicateCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleIndicateCallback) {
    
    
                BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback;
                if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) {
    
    
                    Handler handler = bleIndicateCallback.getHandler();
                    if (handler != null) {
    
    
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_INDICATE_RESULT;
                        message.obj = bleIndicateCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS, status);
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }
 
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    
    
        super.onCharacteristicWrite(gatt, characteristic, status);
 
        Iterator iterator = bleWriteCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleWriteCallback) {
    
    
                BleWriteCallback bleWriteCallback = (BleWriteCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleWriteCallback.getKey())) {
    
    
                    Handler handler = bleWriteCallback.getHandler();
                    if (handler != null) {
    
    
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_WRITE_RESULT;
                        message.obj = bleWriteCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_WRITE_BUNDLE_STATUS, status);
                        bundle.putByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }
 
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    
    
        super.onCharacteristicRead(gatt, characteristic, status);
 
        Iterator iterator = bleReadCallbackHashMap.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry entry = (Map.Entry) iterator.next();
            Object callback = entry.getValue();
            if (callback instanceof BleReadCallback) {
    
    
                BleReadCallback bleReadCallback = (BleReadCallback) callback;
                if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) {
    
    
                    Handler handler = bleReadCallback.getHandler();
                    if (handler != null) {
    
    
                        Message message = handler.obtainMessage();
                        message.what = BleMsg.MSG_CHA_READ_RESULT;
                        message.obj = bleReadCallback;
                        Bundle bundle = new Bundle();
                        bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status);
                        bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue());
                        message.setData(bundle);
                        handler.sendMessage(message);
                    }
                }
            }
        }
    }
};

After receiving the result callbacks of connection status, reading, writing, notification and other operations, it is handed over to the corresponding Handler for processing through the message queue mechanism. Where is the Handler that processes the message? For example, the write operation Handler handler = bleWriteCallback.getHandler();, the handler object is included in the callback of this write operation.

public abstract class BleBaseCallback {
    
    
 
    private String key;
    private Handler handler;
 
    public String getKey() {
    
    
        return key;
    }
 
    public void setKey(String key) {
    
    
        this.key = key;
    }
 
    public Handler getHandler() {
    
    
        return handler;
    }
 
    public void setHandler(Handler handler) {
    
    
        this.handler = handler;
    }
}

All operation callbacks inherit from the BleBaseCallback abstract class, which has two member variables. A key identifies which Characteristic operation this callback belongs to; another handler is used to pass the operation result sent from the bottom layer, and finally hand over the result to the callback to throw to the caller to complete an interface callback.

private void handleCharacteristicWriteCallback(BleWriteCallback bleWriteCallback,
                                               String uuid_write) {
    
    
    if (bleWriteCallback != null) {
    
    
        writeMsgInit();
        bleWriteCallback.setKey(uuid_write);
        bleWriteCallback.setHandler(mHandler);
        mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback);
        mHandler.sendMessageDelayed(
                mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback),
                BleManager.getInstance().getOperateTimeout());
    }
}

The source code above explains this mechanism. After each write operation, the incoming callback will be uniquely marked, and then used to pass the operation result through the handler. At the same time, this callback will be added to the callback pool of the BleBlutooth object of this device for management. .

In this way, the APP maintains a device connection pool. One device connection pool manages multiple device managers. One device manager manages multiple callback sets of different categories. A callback set contains multiple callbacks of the same type and different characteristics.

Insert image description here

operation result:

Insert image description here
Insert image description here
Insert image description here
Source code link: GitHub

Guess you like

Origin blog.csdn.net/zhuguanlin121/article/details/131996761