Android藍(lán)牙通信過程詳解

藍(lán)牙無線技術(shù)是一種全球通用的短距離無線技術(shù),具有耗電量低、成本低、安全性、穩(wěn)定性、易用性等優(yōu)點,尤其在物聯(lián)網(wǎng)設(shè)備上的占有率非常高,因此我們有必要對藍(lán)牙做深入的了解。本文從藍(lán)牙和Android手機(jī)的一次通信過程為引,講解Android藍(lán)牙通信上的一些問題和原理,以及調(diào)用方法。如有描述不當(dāng)?shù)牡胤竭€請指出。

例子

假設(shè)Android手機(jī)A要給藍(lán)牙設(shè)備B發(fā)送一條“hello”的消息,然后B會給A回一條“world”。我們應(yīng)該怎么做呢?

過程

過程可以大致分為三步:

  • 尋找設(shè)備
  • 連接
  • 通信

尋找設(shè)備

A要怎么找到B呢,一般是由設(shè)備B按照一定的周期廣播數(shù)據(jù)包,然后A和B指定好協(xié)議,看廣播數(shù)據(jù)里有沒有協(xié)議約定的數(shù)據(jù),有的話則說明找到了B。
廣播的數(shù)據(jù)包分為四種

Android設(shè)備通過系統(tǒng)提供的API開始接受廣播數(shù)據(jù),

//要先停止上一次的scan,不然無法啟動新的scan
bluetoothAdapter.getBluetoothLeScanner().stopScan(bleCallback);
bluetoothAdapter.getBluetoothLeScanner().startScan(getFilters(), getSettings(), bleCallback);

然后在callback里獲取廣播數(shù)據(jù)

  private ScanCallback bleCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, final ScanResult result) {
           
        }
}

ScanResult就是當(dāng)前接收到的廣播里數(shù)據(jù),在5.0以及5.0以上的api,會幫我們解析廣播的數(shù)據(jù),但是5.0以下的話,只是會把整個廣播數(shù)據(jù)傳回來。下面大致講解下廣播協(xié)議數(shù)據(jù)。

廣播協(xié)議分析:

image.png

官方文檔:
https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
廣播的數(shù)據(jù),按 len 1字節(jié),TYPE1字節(jié), data (len -1)字節(jié)的順序組依次組織,key的含義在上面的表格中已經(jīng)給出。 下面舉個栗子
假設(shè)下面是設(shè)備B的廣播數(shù)據(jù)

02 01 06 05 03 F6 FE F5 FE 0E 09 5B 52 4F 41 44 42 49 54 5D 00 00 00 00 0D FF AA 00 66 EE 06 66 1F FF FF 25 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

解析數(shù)據(jù)

02 01 06           //長度2,type 1 表示設(shè)備功能, 6表示110 -> 普通發(fā)現(xiàn)模式,且不支持BR/EDR
05 03 F6 FE F5 FE  //長度5,type 03,表示16位uuid列表, F6FE和F5FE兩組uuid
0E 09 5B 52 4F 41 44 42 49 54 5D 00 00 00 00  長度 0E,即14, type 9,即設(shè)備名稱,后面試試字符串轉(zhuǎn)16進(jìn)制
0D FF AA 00 66 EE 06 66 1F FF FF 25 04 00 , FF表示自定義數(shù)據(jù),即藍(lán)牙設(shè)備的自定義的數(shù)據(jù)

另外,設(shè)備的廣播頻率是可以自己定義的,從幾ms到幾百ms不等,廣播的頻率決定了手機(jī)發(fā)現(xiàn)設(shè)備的快慢,
有個軟件叫"nRF Connect",可以很方便的觀看藍(lán)牙設(shè)備的廣播速度。下載地址
軟件截圖:

221526440023_.pic.jpg

上圖的Adv. Interval就是廣播的間隔,上文講到的所有東西都可以在這個軟件上觀察到。

連接過程

藍(lán)牙分為5種工作狀態(tài),

  • 準(zhǔn)備(standby):就緒狀態(tài),準(zhǔn)備轉(zhuǎn)變?yōu)槠渌麪顟B(tài)
  • 廣播(advertising):向外發(fā)送數(shù)據(jù)的狀態(tài)
  • 監(jiān)聽掃描(Scanning): 掃描狀態(tài)的時候,在接受到ADV_IND包是,會發(fā)送SCAN_REQ包,可以獲得更多的信息。
  • 發(fā)起連接(Initiating):發(fā)起連接狀態(tài),在ADV_IND或者ADV_DIRECT_IND之后,會發(fā)送CONNECT_REQ包,從而建立連接。
  • 已連接(Connected):根據(jù)連接時約定的參數(shù),發(fā)送CONNECT_EVENT,保持連接不斷開。

具體工作流程如下:

  1. 可被連接的設(shè)備(Advertiser),按照一定的周期廣播ADV_IND或者ADV_DIRECT_IND包(可參考“藍(lán)牙協(xié)議分析(5)_BLE廣播通信相關(guān)的技術(shù)分析”)。
  2. 主動連接的設(shè)備(Initiator),在收到廣播包之后,會回應(yīng)一個CONNECT_REQ請求,該請求攜帶了可決定后續(xù)“通信時序”的參數(shù),例如雙方在哪一個時間點、哪一個Physical Channel收發(fā)數(shù)據(jù),等等。
  3. Initiator在發(fā)出CONNECT_REQ數(shù)據(jù)包之后,自動轉(zhuǎn)變?yōu)镃onnection狀態(tài),成為Master角色(注意:這是“自動”的,不需要等待另一方的回應(yīng))。同樣,Advertiser在收到CONNECT_REQ請求之后,也自動轉(zhuǎn)變?yōu)镃onnection狀態(tài),成為Slave角色。
  4. 此后,雙方按照CONNECT_REQ參數(shù)所給出的約定,定時到切換到某一個Physical Channel上,按照Master->Slave然后Slave->Master的順序,收發(fā)數(shù)據(jù),直至連接斷開。

在Android手機(jī)中,連接的代碼非常簡單,只有一個api可以調(diào)用,如下:

private BluetoothGatt connectGattCompat(BluetoothGattCallback bluetoothGattCallback, BluetoothDevice device, boolean autoConnect) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return device.connectGatt(context, autoConnect, bluetoothGattCallback, TRANSPORT_LE);
        } else {
            return device.connectGatt(context, autoConnect, bluetoothGattCallback);
        }
    }

之后在bluetoothGattCallback的onConnectionChange的回調(diào)里,就可以知道連接的結(jié)果。

通信

說道通信,就要講到跳頻。跳頻的原理是

藍(lán)牙這邊在通信的時候,會約定一個隨機(jī)的頻率(也不是完全隨機(jī),有一個范圍)。雙方在這個頻率上通信,這樣通信更穩(wěn)定。所以Ble在掃描和連接兩個步驟中,可能耗時比較長,但是開始通信之后,速度和穩(wěn)定性都會加快不少。
而跳頻的參數(shù),是在連接命令里發(fā)過去的。

藍(lán)牙設(shè)備,都會注冊service和characteristic,并且用uuid標(biāo)識。

  • service 可以理解為一個服務(wù),在BLE從機(jī)中有多個服務(wù),電量信息,系統(tǒng)服務(wù)信息等,每一個service中包含了多個characteristic特征值,每一個具體的characteristic特征值才是BLE通信的主題。

  • characteristic特征值:BLE主機(jī)從機(jī)通信均是通過characteristic進(jìn)行,可以將其理解為一個標(biāo)簽,通過該標(biāo)簽可以讀取或?qū)懭胂嚓P(guān)信息。

大家可以理解為service就是java里的class,characteristic是class里的public方法。所以我們應(yīng)該先找到class,再調(diào)用其方法。

再往下想,寫和讀肯定是兩個方法,所以也會是兩個characteristic。
service和characteristic的uuid都是通信之前雙方約定好的。android端在上文說的discoverService之后,查看有沒有對應(yīng)的uuid,如果有,就可以發(fā)起通信了。

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            listener.onServicesDiscovered(status);
        }
         @Override
         public void onServicesDiscovered(final int status) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                service = getGatt().getService(ServerUUID);
                if (null != service) {
                    BluetoothGattCharacteristic read_characteristic = service.getCharacteristic(readDataUUID);
                    if (null != read_characteristic) {
                        int properties = read_characteristic.getProperties();
                        if ((properties | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                            getGatt().setCharacteristicNotification(read_characteristic, true);
                            }
                      }
                   }
             }
        }

setCharacteristicNotification 成功后,就可以在onCharacteristicChanged方法收到回調(diào)了。
藍(lán)牙通信的過程,就是往writeCharacteristic里寫數(shù)據(jù),然后監(jiān)聽readCharacteristic的返回。這兩個操作保持有序進(jìn)行,就可以通信了。

過程總結(jié)

針對上文說的例子, 畫個圖來總結(jié)一下


藍(lán)牙通信完整流程.png

藍(lán)牙采坑總結(jié)

scan過程

  • 據(jù)測試,Android5.0以下的手機(jī),是不支持128位的uuid的,只支持16位的。如果過濾的uuid填入128位的話,是搜索不到設(shè)備的。所以要分別判斷。

  • 部分手機(jī)(三星遇到過),在藍(lán)牙關(guān)閉情況下,仍然可以使用ble功能,但是經(jīng)常會有問題,所以請務(wù)必打開藍(lán)牙再開始通信。

  • 部分手機(jī),如果1次scan不到,后續(xù)都不會scan到了,這時要停止scan,重新啟動一次。

連接錯誤

國內(nèi)Android手機(jī)的藍(lán)牙不知道為何,非常不穩(wěn)定。在連接的過程中,會有各種各樣的錯誤,我用了github上總結(jié)的錯誤,
https://github.com/Twelvelines/AndroidMuseumBleManager/blob/ef5e866c0aa8955320bac7ee9884f60be5bbe1d5/bluetooth-manager-lib/src/main/java/com/blakequ/bluetooth_manager_lib/connect/GattError.java
或者
https://blog.csdn.net/ocean20/article/details/65431478

這些錯誤會頻繁的遇到,所以為了成功率更高,需要在連接的時候增加重試機(jī)制。
遇到這些錯誤,立刻斷開連接,等待1-2秒后再次嘗試連接。
具體見以下代碼:

 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);

            BleLog.d("onConnectionStateChange", "status: " + status + " newState: " + newState);
            if (status != BluetoothGatt.GATT_SUCCESS) {
                BleLog.e(TAG, "連接狀態(tài)異常->" + GattError.parseConnectionError(status));
                disconnectInternal(false);
                onConnectError(status);
               
            }

onConnectError的回調(diào)里,可以直接去重試連接。這樣可以提高成功率。但是重新連接之前一定要close當(dāng)前連接,否則會出現(xiàn)各種問題。

但是重試隨之而來的問題是老的gatt對象可能仍然會回調(diào)(系統(tǒng)回調(diào)不知道什么時候回來),所以在接收回調(diào)的時候,最好判斷一下當(dāng)前的gatt對象是否是最新的。

然后還有就是通信問題,通信相對于連接來說,是穩(wěn)定很多的,但是仍然會有一定的幾率,收不到消息。所以硬件設(shè)備和手機(jī)的藍(lán)牙代碼,最好有一方有重試機(jī)制,一般來說手機(jī)重試就足夠了。

參考文獻(xiàn)

后續(xù)會對android連接的源碼分析一波

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 藍(lán)牙歷史說到藍(lán)牙,就不得不說下藍(lán)牙技術(shù)聯(lián)盟(Bluetooth SIG),它負(fù)責(zé)藍(lán)牙規(guī)范制定和推廣的國際組織...
    徐正峰閱讀 13,038評論 6 33
  • 藍(lán)牙 藍(lán)牙的波段為2400-2483.5MHz(包括防護(hù)頻帶)。這是全球范圍內(nèi)無需取得執(zhí)照(但定不是無管制的)的工...
    蘇永茂閱讀 6,591評論 0 11
  • 初識低功耗藍(lán)牙 Android 4.3(API Level 18)開始引入Bluetooth Low Energy...
    JBD閱讀 113,577評論 46 342
  • BLE簡述 藍(lán)牙是一套非常龐大復(fù)雜的協(xié)議棧,通俗的說就是一組應(yīng)用于無線系統(tǒng)通信的約定,各個廠家根據(jù)這個約定生產(chǎn)出了...
    dxdingdu閱讀 11,586評論 5 37
  • 寄生式發(fā)展 在南美的熱帶雨林里有一種名叫“絞殺榕”的植物,它緊緊纏繞在高大的樹木上,通過奪取對方的陽光和養(yǎng)分而生存...
    陽明心閱讀 650評論 0 0

友情鏈接更多精彩內(nèi)容