藍(lán)牙無線技術(shù)是一種全球通用的短距離無線技術(shù),具有耗電量低致燥、成本低登疗、安全性、穩(wěn)定性、易用性等優(yōu)點(diǎn)辐益,尤其在物聯(lián)網(wǎng)設(shè)備上的占有率非常高断傲,因此我們有必要對(duì)藍(lán)牙做深入的了解。本文從藍(lán)牙和Android手機(jī)的一次通信過程為引智政,講解Android藍(lán)牙通信上的一些問題和原理认罩,以及調(diào)用方法。如有描述不當(dāng)?shù)牡胤竭€請(qǐng)指出续捂。
例子
假設(shè)Android手機(jī)A要給藍(lán)牙設(shè)備B發(fā)送一條“hello”的消息垦垂,然后B會(huì)給A回一條“world”。我們應(yīng)該怎么做呢牙瓢?
過程
過程可以大致分為三步:
- 尋找設(shè)備
- 連接
- 通信
尋找設(shè)備
A要怎么找到B呢劫拗,一般是由設(shè)備B按照一定的周期廣播數(shù)據(jù)包,然后A和B指定好協(xié)議矾克,看廣播數(shù)據(jù)里有沒有協(xié)議約定的數(shù)據(jù)页慷,有的話則說明找到了B。
廣播的數(shù)據(jù)包分為四種
- ADV_IND
- ADV_DIRECT_IND
- ADV_NONCONN_IND
- ADV_SCAN_IND
一般發(fā)送的是ADV_IND包(可參考藍(lán)牙協(xié)議分析(5)_BLE廣播通信相關(guān)的技術(shù)分析酒繁。
Android設(shè)備通過系統(tǒng)提供的API開始接受廣播數(shù)據(jù),
//要先停止上一次的scan控妻,不然無法啟動(dòng)新的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,會(huì)幫我們解析廣播的數(shù)據(jù)饼暑,但是5.0以下的話,只是會(huì)把整個(gè)廣播數(shù)據(jù)傳回來洗做。下面大致講解下廣播協(xié)議數(shù)據(jù)弓叛。
廣播協(xié)議分析:
官方文檔:
https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
廣播的數(shù)據(jù),按 len 1字節(jié)诚纸,TYPE1字節(jié), data (len -1)字節(jié)的順序組依次組織撰筷,key的含義在上面的表格中已經(jīng)給出。 下面舉個(gè)栗子
假設(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 //長(zhǎng)度2畦徘,type 1 表示設(shè)備功能毕籽, 6表示110 -> 普通發(fā)現(xiàn)模式,且不支持BR/EDR
05 03 F6 FE F5 FE //長(zhǎng)度5井辆,type 03关筒,表示16位uuid列表, F6FE和F5FE兩組uuid
0E 09 5B 52 4F 41 44 42 49 54 5D 00 00 00 00 長(zhǎng)度 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è)備的快慢宿崭,
有個(gè)軟件叫"nRF Connect",可以很方便的觀看藍(lán)牙設(shè)備的廣播速度。下載地址
軟件截圖:
上圖的Adv. Interval就是廣播的間隔才写,上文講到的所有東西都可以在這個(gè)軟件上觀察到葡兑。
連接過程
藍(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)的時(shí)候铁孵,在接受到ADV_IND包是,會(huì)發(fā)送SCAN_REQ包房资,可以獲得更多的信息蜕劝。
- 發(fā)起連接(Initiating):發(fā)起連接狀態(tài),在ADV_IND或者ADV_DIRECT_IND之后轰异,會(huì)發(fā)送CONNECT_REQ包岖沛,從而建立連接。
- 已連接(Connected):根據(jù)連接時(shí)約定的參數(shù)搭独,發(fā)送CONNECT_EVENT婴削,保持連接不斷開。
具體工作流程如下:
- 可被連接的設(shè)備(Advertiser)牙肝,按照一定的周期廣播ADV_IND或者ADV_DIRECT_IND包(可參考“藍(lán)牙協(xié)議分析(5)_BLE廣播通信相關(guān)的技術(shù)分析”)唉俗。
- 主動(dòng)連接的設(shè)備(Initiator),在收到廣播包之后配椭,會(huì)回應(yīng)一個(gè)CONNECT_REQ請(qǐng)求虫溜,該請(qǐng)求攜帶了可決定后續(xù)“通信時(shí)序”的參數(shù),例如雙方在哪一個(gè)時(shí)間點(diǎn)股缸、哪一個(gè)Physical Channel收發(fā)數(shù)據(jù)衡楞,等等。
- Initiator在發(fā)出CONNECT_REQ數(shù)據(jù)包之后敦姻,自動(dòng)轉(zhuǎn)變?yōu)镃onnection狀態(tài)瘾境,成為Master角色(注意:這是“自動(dòng)”的,不需要等待另一方的回應(yīng))镰惦。同樣迷守,Advertiser在收到CONNECT_REQ請(qǐng)求之后,也自動(dòng)轉(zhuǎn)變?yōu)镃onnection狀態(tài)陨献,成為Slave角色盒犹。
- 此后,雙方按照CONNECT_REQ參數(shù)所給出的約定,定時(shí)到切換到某一個(gè)Physical Channel上急膀,按照Master->Slave然后Slave->Master的順序沮协,收發(fā)數(shù)據(jù),直至連接斷開卓嫂。
在Android手機(jī)中慷暂,連接的代碼非常簡(jiǎn)單,只有一個(gè)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)牙這邊在通信的時(shí)候,會(huì)約定一個(gè)隨機(jī)的頻率(也不是完全隨機(jī)帮非,有一個(gè)范圍)氧吐。雙方在這個(gè)頻率上通信,這樣通信更穩(wěn)定末盔。所以Ble在掃描和連接兩個(gè)步驟中筑舅,可能耗時(shí)比較長(zhǎng),但是開始通信之后陨舱,速度和穩(wěn)定性都會(huì)加快不少翠拣。
而跳頻的參數(shù),是在連接命令里發(fā)過去的游盲。
藍(lán)牙設(shè)備误墓,都會(huì)注冊(cè)service和characteristic,并且用uuid標(biāo)識(shí)益缎。
service 可以理解為一個(gè)服務(wù)优烧,在BLE從機(jī)中有多個(gè)服務(wù),電量信息链峭,系統(tǒng)服務(wù)信息等,每一個(gè)service中包含了多個(gè)characteristic特征值又沾,每一個(gè)具體的characteristic特征值才是BLE通信的主題弊仪。
characteristic特征值:BLE主機(jī)從機(jī)通信均是通過characteristic進(jìn)行,可以將其理解為一個(gè)標(biāo)簽杖刷,通過該標(biāo)簽可以讀取或?qū)懭胂嚓P(guān)信息励饵。
大家可以理解為service就是java里的class,characteristic是class里的public方法滑燃。所以我們應(yīng)該先找到class役听,再調(diào)用其方法。
再往下想,寫和讀肯定是兩個(gè)方法典予,所以也會(huì)是兩個(gè)characteristic甜滨。
service和characteristic的uuid都是通信之前雙方約定好的。android端在上文說的discoverService之后瘤袖,查看有沒有對(duì)應(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的返回。這兩個(gè)操作保持有序進(jìn)行逆济,就可以通信了酌予。
過程總結(jié)
針對(duì)上文說的例子, 畫個(gè)圖來總結(jié)一下
藍(lán)牙采坑總結(jié)
scan過程
據(jù)測(cè)試纹腌,Android5.0以下的手機(jī)霎终,是不支持128位的uuid的,只支持16位的升薯。如果過濾的uuid填入128位的話莱褒,是搜索不到設(shè)備的。所以要分別判斷涎劈。
部分手機(jī)(三星遇到過)广凸,在藍(lán)牙關(guān)閉情況下,仍然可以使用ble功能蛛枚,但是經(jīng)常會(huì)有問題谅海,所以請(qǐng)務(wù)必打開藍(lán)牙再開始通信。
部分手機(jī)蹦浦,如果1次scan不到扭吁,后續(xù)都不會(huì)scan到了,這時(shí)要停止scan盲镶,重新啟動(dòng)一次侥袜。
連接錯(cuò)誤
國(guó)內(nèi)Android手機(jī)的藍(lán)牙不知道為何,非常不穩(wěn)定溉贿。在連接的過程中枫吧,會(huì)有各種各樣的錯(cuò)誤,我用了github上總結(jié)的錯(cuò)誤宇色,
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
這些錯(cuò)誤會(huì)頻繁的遇到九杂,所以為了成功率更高颁湖,需要在連接的時(shí)候增加重試機(jī)制。
遇到這些錯(cuò)誤例隆,立刻斷開連接甥捺,等待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)前連接鹿响,否則會(huì)出現(xiàn)各種問題羡微。
但是重試隨之而來的問題是老的gatt對(duì)象可能仍然會(huì)回調(diào)(系統(tǒng)回調(diào)不知道什么時(shí)候回來),所以在接收回調(diào)的時(shí)候惶我,最好判斷一下當(dāng)前的gatt對(duì)象是否是最新的妈倔。
然后還有就是通信問題,通信相對(duì)于連接來說绸贡,是穩(wěn)定很多的盯蝴,但是仍然會(huì)有一定的幾率,收不到消息听怕。所以硬件設(shè)備和手機(jī)的藍(lán)牙代碼捧挺,最好有一方有重試機(jī)制,一般來說手機(jī)重試就足夠了尿瞭。
參考文獻(xiàn)
- 蝸窩科技 http://www.wowotech.net
- BluetoothKit作者博文 BluetoothKit
- 簡(jiǎn)書
后續(xù)會(huì)對(duì)android連接的源碼分析一波