android藍牙BLE(二) —— 通訊

android BLE系列:

android藍牙BLE(一) —— 掃描android

android藍牙BLE(二) —— 通訊緩存

android藍牙BLE(三) —— 廣播bash

android藍牙BLE(四) —— 實戰網絡

1、藍牙基礎協議

    想了解藍牙通訊以前,須要先了解藍牙兩個最基本的協議:GAP 和 GATT。ide

一、GAP(Generic Access Profile)簡介

    GAP是通用訪問配置文件的首字母縮寫,主要控制藍牙鏈接和廣播。GAP使藍牙設備對外界可見,並決定設備是否能夠或者怎樣與其餘設備進行交互。
oop

    GAP定義了多種角色,但主要的兩個是:中心設備 和 外圍設備。
post

        中心設備:能夠掃描並鏈接多個外圍設備,從外設中獲取信息。
ui

        外圍設備:小型,低功耗,資源有限的設備。能夠鏈接到功能更強大的中心設備,併爲其提供數據。spa

二、GAP廣播數據

    GAP 中外圍設備經過兩種方式向外廣播數據:廣播數據 和 掃描回覆。 每種數據最長能夠包含 31 byte。
線程

    廣播數據是必需的,由於外設必需不停的向外廣播,讓中心設備知道它的存在。

    掃描回覆是可選的,中心設備能夠向外設請求掃描回覆,這裏包含一些設備額外的信息。

    外圍設備會設定一個廣播間隔。每一個廣播間隔中,它會從新發送本身的廣播數據。廣播間隔越長,越省電,同時也不太容易掃描到。

3. 廣播的網絡拓撲結構

    外設經過廣播本身讓中心設備發現本身,並創建 GATT 鏈接,從而進行更多的數據交換。但有些狀況是不須要鏈接的,只要外設廣播本身的數據便可。目的是讓外圍設備,把本身的信息發送給多箇中心設備。由於基於 GATT 鏈接的方式的,只能是一個外設鏈接一箇中心設備。

4. GATT(Generic Attribute Profile)簡介

    GATT配置文件是一個通用規範,用於在BLE鏈路上發送和接收被稱爲「屬性」的數據塊。目前全部的BLE應用都基於GATT。

    BLE設備經過叫作 ServiceCharacteristic 的東西進行通訊

    GATT使用了 ATT(Attribute Protocol)協議,ATT 協議把 Service, Characteristic對應的數據保存在一個查詢表中,次查找表使用 16 bit ID 做爲每一項的索引。

    GATT 鏈接是獨佔的。也就是一個 BLE 外設同時只能被一箇中心設備鏈接。一旦外設被鏈接,它就會立刻中止廣播,這樣它就對其餘設備不可見了。當外設與中心設備斷開,外設又開始廣播,讓其餘中心設備感知該外設的存在。而中心設備可同時與多個外設進行鏈接。

5.GATT 通訊

    中心設備和外設須要雙向通訊的話,惟一的方式就是創建 GATT 鏈接。

    GATT 通訊的雙方是 C/S 關係。外設做爲 GATT 服務端(Server),它維持了 ATT 的查找表以及 service 和 characteristic 的定義。中心設備是 GATT 客戶端(Client),它向 外設(Server) 發起請求來獲取數據。

6.GATT 結構

Profile:並非實際存在於 BLE 外設上的,它只是一個被 Bluetooth SIG 或者外設設計者預先定義的 Service 的集合。例如心率Profile(Heart Rate Profile)就是結合了 Heart Rate Service 和 Device Information Service。

Service:包含一個或者多個 Characteristic。每一個 Service 有一個 UUID 惟一標識。

Characteristic: 是最小的邏輯數據單元。一個Characteristic包括一個單一value變量和0-n個用來描述characteristic變量的Descriptor。與 Service 相似,每一個 Characteristic 用 16 bit 或者 128 bit 的 UUID 惟一標識。

    實際開發中,和 BLE 外設打交道,主要是經過 Characteristic。能夠從 Characteristic 讀取數據,也能夠往 Characteristic 寫數據,從而實現雙向的通訊。

     UUID 有 16 bit 、32bit 和 128 bit 的。16 bit 的 UUID 是官方經過認證的,須要花錢購買。 Bluetooth_Base_UUID定義爲 00000000-0000-1000-8000-00805F9B34FB

    若16 bit UUID爲xxxx,轉換爲128 bit UUID爲0000xxxx-0000-1000-8000-00805F9B34FB
    若32 bit UUID爲xxxxxxxx,轉換爲128 bit UUID爲xxxxxxxx-0000-1000-8000-00805F9B34FB

2、中心設備與外設通信

簡單介紹BLE開發當中各類主要類和其做用:

        BluetoothDeivce:藍牙設備,表明一個具體的藍牙外設。
        BluetoothGatt:通用屬性協議,定義了BLE通信的基本規則和操做
        BluetoothGattCallback:GATT通訊回調類,用於回調的各類狀態和結果。
        BluetoothGattService:服務,由零或多個特徵組構成。
        BluetoothGattCharacteristic:特徵,裏面包含了一組或多組數據,是GATT通訊中的最小數據單元。
        BluetoothGattDescriptor:特徵描述符,對特徵的額外描述,包括但不只限於特徵的單位,屬性等。

一、鏈接

    對掃描到的藍牙能夠用集合形式進行緩存,也可只保存其mac地址,存儲到字符集合中,用於後續的鏈接。

1.一、根據mac地址獲取到BluetoothDeivce用於鏈接

BluetoothManager bluetoothmanager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
 mBluetoothAdapter = bluetoothmanager.getAdapter();
 //獲取藍牙設備對象進行鏈接
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(macAddressStr)
複製代碼

1.二、藍牙gatt回調

    實現BluetoothGattCallBack類,監聽藍牙鏈接過程當中各類回調的監聽。

    藍牙Gatt回調方法中都不能夠進行耗時操做,須要將其方法內進行的操做丟進另外一個線程,儘快返回。

//定義子線程handle,用於在BluetoothGattCallback中回調方法中的操做拋到該線程工做。
private Handler mHandler;
//定義handler工做的子線程
private HandlerThread mHandlerThread;

初始化handler
mHandlerThread = new HandlerThread("daqi");
mHandlerThread.start();
//將handler綁定到子線程中
mHandler = new Handler(mHandlerThread.getLooper());


//定義藍牙Gatt回調類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{

        //鏈接狀態回調
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            // status 用於返回操做是否成功,會返回異常碼。
            // newState 返回鏈接狀態,如BluetoothProfile#STATE_DISCONNECTED、BluetoothProfile#STATE_CONNECTED
            
            //操做成功的狀況下
            if (status == BluetoothGatt.GATT_SUCCESS){
                //判斷是否鏈接碼
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                
                }else if(newState == BluetoothProfile.STATE_DISCONNECTED){
                    //判斷是否斷開鏈接碼
                    
                }
            }else{
                //異常碼
                
            }
        }
        
        //服務發現回調
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
        }

        //特徵寫入回調
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
        }
        
        //外設特徵值改變回調
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
        }
        
        //描述寫入回調
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
        }
    }
複製代碼

1.三、鏈接設備

    調用BluetoothDevice#connectGatt()進行ble鏈接,第二個參數默認選擇false,不自動鏈接。並定義BluetoothGatt變量,存儲BluetoothDevice#connectGatt()返回的對象。

//定義Gatt實現類
private BluetoothGatt mBluetoothGatt; 

//建立Gatt回調
private BluetoothGattCallback mGattCallback = new daqiBluetoothGattCallback();
//鏈接設備
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    mBluetoothGatt = mBluetoothDevice.connectGatt(mContext,
            false, mGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
    mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback);
}
複製代碼

1.四、鏈接異常處理
    藍牙鏈接時,不必定百分百鏈接成功。鏈接出錯時,會返回異常碼進行錯誤描述。
    對於大多數異常碼,能夠經過重連來達到鏈接成功的目的。

錯誤代碼:
    133 :鏈接超時或未找到設備。
    8 : 設備超出範圍
    22 :表示本地設備終止了鏈接

//定義重連次數
private int reConnectionNum = 0;
//最多重連次數
private int maxConnectionNum = 3;

public class daqiBluetoothGattCallback extends BluetoothGattCallback{

    //鏈接狀態回調
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        // status 用於返回操做是否成功,會返回異常碼。
        //操做成功的狀況下
        if (status == BluetoothGatt.GATT_SUCCESS){
            
        }else{
            //重連次數不大於最大重連次數
            if(reConnectionNum < maxConnectionNum){
                //重連次數自增
                reConnectionNum++
                //鏈接設備
                if (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{
                //斷開鏈接,返回鏈接失敗回調
                
            }
        }
    }
    
    //其餘回調方法
}
複製代碼

二、發現服務

鏈接成功後,觸發BluetoothGattCallback#onConnectionStateChange()方法。

2.一、發現服務

public class daqiBluetoothGattCallback extends BluetoothGattCallback{

    //鏈接狀態回調
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        // status 用於返回操做是否成功,會返回異常碼。
        //操做成功的狀況下
        if (status == BluetoothGatt.GATT_SUCCESS){
            //判斷是否鏈接碼
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    //可延遲發現服務,也可不延遲
                    mHandler.post(() ->
                        //發現服務
                        mBluetoothGatt.discoverServices();
                    );
                }else if(newState == BluetoothProfile.STATE_DISCONNECTED){
                    //判斷是否斷開鏈接碼
                    
                }
        }
    }
    
    //其餘回調方法
}
複製代碼

當發現服務成功後,會觸發BluetoothGattCallback#onServicesDiscovered()回調:

//定義須要進行通訊的ServiceUUID
private UUID mServiceUUID = UUID.fromString("0000xxxx-0000-1000-8000-00805f9b34fb");

//定義藍牙Gatt回調類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{

    //服務發現回調
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
            mHandler.post(() ->
                //獲取指定uuid的service
                BluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID);
                //獲取到特定的服務不爲空
                if(gattService != null){
                    
                }else{
                    //獲取特定服務失敗
                    
                }
            );
        }
    }
}
複製代碼

2.二、發現服務失敗

    發現服務時,會存在發現不了特定服務的狀況。或者說,整個BluetoothGatt對象中的服務列表爲空。
    BluetoothGatt類中存在一個隱藏的方法refresh(),用於刷新Gatt的服務列表。當發現不了服務時,能夠經過反射去調用該方法。

三、修改特徵值並監聽外設特徵值改變

3.一、讀寫特定特徵

//定義須要進行通訊的ServiceUUID
private UUID mServiceUUID = UUID.fromString("0000xxxx-0000-1000-8000-00805f9b34fb");
//定義須要進行通訊的CharacteristicUUID
private UUID mCharacteristicUUID = UUID.fromString("0000yyyy-0000-1000-8000-00805f9b34fb");


//定義藍牙Gatt回調類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{

    //服務發現回調
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
            mHandler.post(() ->
                //獲取指定uuid的service
                BluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID);
                //獲取到特定的服務不爲空
                if(gattService != null){
                    //獲取指定uuid的Characteristic
                    BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(mCharacteristicUUID);
                    //獲取特定特徵成功
                    if(gattCharacteristic != null){
                        //寫入你須要傳遞給外設的特徵值(即傳遞給外設的信息)
                        gattCharacteristic.setValue(bytes);
                        //經過GATt實體類將,特徵值寫入到外設中。
                        mBluetoothGatt.writeCharacteristic(gattCharacteristic);
                        
                        //若是隻是須要讀取外設的特徵值:
                        //經過Gatt對象讀取特定特徵(Characteristic)的特徵值
                        String readValue = mBluetoothGatt.readCharacteristic(gattCharacteristic);
                    }
                }else{
                    //獲取特定服務失敗
                    
                }
            );
        }
    }
}
複製代碼

    當成功讀取特徵值時,會觸發BluetoothGattCallback#onCharacteristicRead()回調。

@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    super.onCharacteristicRead(gatt, characteristic, status);
    if (status == BluetoothGatt.GATT_SUCCESS) {
        //獲取讀取到的特徵值
        characteristic.getValue()
    }
}
複製代碼

    當成功寫入特徵值到外設時,會觸發BluetoothGattCallback#onCharacteristicWrite()回調。

@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    super.onCharacteristicWrite(gatt, characteristic, status);
    if (status == BluetoothGatt.GATT_SUCCESS) {
        //獲取寫入到外設的特徵值
        characteristic.getValue()
    }
}
複製代碼

    當寫入完特徵值後,外設會修改本身的特徵值,手機會觸發BluetoothGattCallback#onCharacteristicChanged()方法,進行雙向通訊

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
     if (status == BluetoothGatt.GATT_SUCCESS) {
        //獲取外設修改的特徵值
        String value = characteristic.getValue()
        //對特徵值進行解析
        
    }
}
複製代碼

四、斷開鏈接

    斷開鏈接的操做分爲兩步:

        一、mBluetoothGatt.disconnect();
        二、mBluetoothGatt.close();

    調用disconnect()後,會觸發手機會觸發BluetoothGattCallback#onConnectionStateChange()的回調,回調斷開鏈接信息,newState = BluetoothProfile.STATE_DISCONNECTED。但接着立刻調用close(),會終止BluetoothGattCallback#onConnectionStateChange()的回調,能夠看狀況將兩個進行拆分調用,但通常都是成對出現。
    例如:
        須要在外設修改特徵值觸發BluetoothGattCallback#onCharacteristicChanged()時,斷開鏈接。能夠先在BluetoothGattCallback#onCharacteristicChanged()中調用disconnect(),並等調用BluetoothGattCallback#onConnectionStateChange()回調,返回斷開鏈接信息後,再調用close()對Gatt資源進行關閉。

    當和外設進行ble通訊時,如出現任何意外狀況,立刻調用斷開鏈接操做。

相關文章
相關標籤/搜索