一、藍(lán)牙基礎(chǔ)協(xié)議
想了解藍(lán)牙通信之前县踢,需要先了解藍(lán)牙兩個(gè)最基本的協(xié)議:GAP 和 GATT。
GAP(Generic Access Profile)簡(jiǎn)介
? GAP是通用訪問配置文件的首字母縮寫疏之,主要控制藍(lán)牙連接和廣播殿雪。GAP使藍(lán)牙設(shè)備對(duì)外界可見,并決定設(shè)備是否可以或者怎樣與其他設(shè)備進(jìn)行交互锋爪。
GAP定義了多種角色丙曙,但主要的兩個(gè)是:中心設(shè)備 和 外圍設(shè)備。
中心設(shè)備:可以掃描并連接多個(gè)外圍設(shè)備,從外設(shè)中獲取信息其骄。
外圍設(shè)備:小型亏镰,低功耗,資源有限的設(shè)備拯爽∷髯ィ可以連接到功能更強(qiáng)大的中心設(shè)備,并為其提供數(shù)據(jù)毯炮。
GAP廣播數(shù)據(jù)
GAP 中外圍設(shè)備通過兩種方式向外廣播數(shù)據(jù):廣播數(shù)據(jù) 和 掃描回復(fù)( 每種數(shù)據(jù)最長(zhǎng)可以包含 31 byte逼肯。)。
? 廣播數(shù)據(jù)是必需的桃煎,因?yàn)橥庠O(shè)必需不停的向外廣播篮幢,讓中心設(shè)備知道它的存在。而掃描回復(fù)是可選的为迈,中心設(shè)備可以向外設(shè)請(qǐng)求掃描回復(fù)三椿,這里包含一些設(shè)備額外的信息。
外圍設(shè)備會(huì)設(shè)定一個(gè)廣播間隔葫辐。每個(gè)廣播間隔中搜锰,它會(huì)重新發(fā)送自己的廣播數(shù)據(jù)。廣播間隔越長(zhǎng)耿战,越省電蛋叼,同時(shí)也不太容易掃描到。
廣播的網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)
? 外設(shè)通過廣播自己讓中心設(shè)備發(fā)現(xiàn)自己剂陡,并建立 GATT 連接鸦列,從而進(jìn)行更多的數(shù)據(jù)交換租冠。但有些情況是不需要連接的,只要外設(shè)廣播自己的數(shù)據(jù)即可薯嗤。目的是讓外圍設(shè)備顽爹,把自己的信息發(fā)送給多個(gè)中心設(shè)備。因?yàn)榛?GATT 連接的方式的骆姐,只能是一個(gè)外設(shè)連接一個(gè)中心設(shè)備镜粤。
GATT(Generic Attribute Profile)簡(jiǎn)介
? GATT配置文件是一個(gè)通用規(guī)范,用于在BLE鏈路上發(fā)送和接收被稱為“屬性”的數(shù)據(jù)塊玻褪。目前所有的BLE應(yīng)用都基于GATT肉渴。
BLE設(shè)備通過叫做 Service 和 Characteristic 的東西進(jìn)行通信
? GATT使用了 ATT(Attribute Protocol)協(xié)議,ATT 協(xié)議把 Service, Characteristic
對(duì)應(yīng)的數(shù)據(jù)保存在一個(gè)查詢表中带射,次查找表使用 16 bit ID 作為每一項(xiàng)的索引同规。
? GATT 連接是獨(dú)占的。也就是一個(gè) BLE 外設(shè)同時(shí)只能被一個(gè)中心設(shè)備連接窟社。一旦外設(shè)被連接券勺,它就會(huì)馬上停止廣播,這樣它就對(duì)其他設(shè)備不可見了灿里。當(dāng)外設(shè)與中心設(shè)備斷開关炼,外設(shè)又開始廣播,讓其他中心設(shè)備感知該外設(shè)的存在匣吊。而中心設(shè)備可同時(shí)與多個(gè)外設(shè)進(jìn)行連接儒拂。
GATT 通信
中心設(shè)備和外設(shè)需要雙向通信的話,唯一的方式就是建立 GATT 連接色鸳。
? GATT 通信的雙方是 C/S 關(guān)系社痛。外設(shè)作為 GATT 服務(wù)端(Server),它維持了 ATT 的查找表以及 service 和 characteristic
的定義命雀。中心設(shè)備是 GATT 客戶端(Client)蒜哀,它向 外設(shè)(Server) 發(fā)起請(qǐng)求來獲取數(shù)據(jù)。
GATT 結(jié)構(gòu)
Profile:并不是實(shí)際存在于 BLE 外設(shè)上的咏雌,它只是一個(gè)被 Bluetooth SIG 或者外設(shè)設(shè)計(jì)者預(yù)先定義的
Service
的集合。例如心率Profile
(Heart Rate Profile
)就是結(jié)合了Heart Rate Service
和Device Information Service
校焦。Service:包含一個(gè)或者多個(gè)
Characteristic
赊抖。每個(gè)Service
有一個(gè)UUID
唯一標(biāo)識(shí)。Characteristic: 是最小的邏輯數(shù)據(jù)單元寨典。一個(gè)
Characteristic
包括一個(gè)單一value變量和0-n個(gè)用來描述characteristic
變量的Descriptor
氛雪。與Service
類似,每個(gè)Characteristic
用 16 bit 或者 128 bit 的UUID
唯一標(biāo)識(shí)耸成。
實(shí)際開發(fā)中报亩,和 BLE 外設(shè)打交道浴鸿,主要是通過 Characteristic
∠易罚可以從 Characteristic
讀取數(shù)據(jù)岳链,也可以往 Characteristic
寫數(shù)據(jù),從而實(shí)現(xiàn)雙向的通信劲件。
UUID 有 16 bit 掸哑、32bit 和 128 bit 的。16 bit 的 UUID 是官方通過認(rèn)證的零远,需要花錢購(gòu)買苗分。
Bluetooth_Base_UUID定義為 00000000-0000-1000-8000-00805F9B34FB
- 若16 bit UUID為xxxx,轉(zhuǎn)換為128 bit UUID為
0000xxxx-0000-1000-8000-00805F9B34FB
- 若32 bit UUID為xxxxxxxx牵辣,轉(zhuǎn)換為128 bit UUID為
xxxxxxxx-0000-1000-8000-00805F9B34FB
二摔癣、中心設(shè)備與外設(shè)通訊
簡(jiǎn)單介紹BLE開發(fā)當(dāng)中各種主要類和其作用:
- BluetoothDeivce:藍(lán)牙設(shè)備,代表一個(gè)具體的藍(lán)牙外設(shè)纬向。
- BluetoothGatt:通用屬性協(xié)議择浊,定義了BLE通訊的基本規(guī)則和操作
- BluetoothGattCallback:GATT通信回調(diào)類,用于回調(diào)的各種狀態(tài)和結(jié)果罢猪。
- BluetoothGattService:服務(wù)近她,由零或多個(gè)特征組構(gòu)成。
-
BluetoothGattCharacteristic:特征膳帕,里面包含了一組或多組數(shù)據(jù)粘捎,是GATT通信中的最小數(shù)據(jù)單元。
BluetoothGattDescriptor:特征描述符危彩,對(duì)特征的額外描述攒磨,包括但不僅限于特征的單位,屬性等汤徽。
獲取藍(lán)牙設(shè)備對(duì)象
對(duì)掃描到的藍(lán)牙可以用集合形式進(jìn)行緩存娩缰,也可只保存其mac地址,存儲(chǔ)到字符集合中谒府,用于后續(xù)的連接拼坎。
根據(jù)mac地址獲取到BluetoothDeivce用于連接
BluetoothManager bluetoothmanager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothmanager.getAdapter();
//獲取藍(lán)牙設(shè)備對(duì)象進(jìn)行連接
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(macAddressStr)
藍(lán)牙gatt回調(diào)
實(shí)現(xiàn)BluetoothGattCallBack
類,監(jiān)聽藍(lán)牙連接過程中各種回調(diào)的監(jiān)聽完疫。藍(lán)牙Gatt回調(diào)方法中都不應(yīng)該進(jìn)行耗時(shí)操作泰鸡,需要將其方法內(nèi)進(jìn)行的操作丟進(jìn)另一個(gè)線程,盡快返回壳鹤。
//定義子線程handle盛龄,用于在BluetoothGattCallback中回調(diào)方法中的操作拋到該線程工作。
private Handler mHandler;
//定義handler工作的子線程
private HandlerThread mHandlerThread;
初始化handler
mHandlerThread = new HandlerThread("daqi");
mHandlerThread.start();
//將handler綁定到子線程中
mHandler = new Handler(mHandlerThread.getLooper());
//定義藍(lán)牙Gatt回調(diào)類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//連接狀態(tài)回調(diào)
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// status 用于返回操作是否成功,會(huì)返回異常碼。
// newState 返回連接狀態(tài)余舶,如BluetoothProfile#STATE_DISCONNECTED啊鸭、BluetoothProfile#STATE_CONNECTED
//操作成功的情況下
if (status == BluetoothGatt.GATT_SUCCESS){
//判斷是否連接碼
if (newState == BluetoothProfile.STATE_CONNECTED) {
}else if(newState == BluetoothProfile.STATE_DISCONNECTED){
//判斷是否斷開連接碼
}
}else{
//異常碼
}
}
//服務(wù)發(fā)現(xiàn)回調(diào)
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
}
//特征寫入回調(diào)
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
//外設(shè)特征值改變回調(diào)
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
//描述寫入回調(diào)
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
}
連接設(shè)備
? 調(diào)用BluetoothDevice#connectGatt()
進(jìn)行ble連接,第二個(gè)參數(shù)默認(rèn)選擇false,不自動(dòng)連接匿值。并定義BluetoothGatt
變量赠制,存儲(chǔ)BluetoothDevice#connectGatt()
返回的對(duì)象。
//定義Gatt實(shí)現(xiàn)類
private BluetoothGatt mBluetoothGatt;
//創(chuàng)建Gatt回調(diào)
private BluetoothGattCallback mGattCallback = new daqiBluetoothGattCallback();
//連接設(shè)備
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);
}
連接異常處理
? 藍(lán)牙連接時(shí)千扔,不一定百分百連接成功憎妙。連接出錯(cuò)時(shí),會(huì)返回異常碼進(jìn)行錯(cuò)誤描述曲楚。對(duì)于大多數(shù)異常碼厘唾,可以通過重連來達(dá)到連接成功的目的。
錯(cuò)誤代碼:
- 133 :連接超時(shí)或未找到設(shè)備龙誊。
- 8 : 設(shè)備超出范圍
- 22 :表示本地設(shè)備終止了連接
//定義重連次數(shù)
private int reConnectionNum = 0;
//最多重連次數(shù)
private int maxConnectionNum = 3;
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//連接狀態(tài)回調(diào)
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// status 用于返回操作是否成功,會(huì)返回異常碼抚垃。
//操作成功的情況下
if (status == BluetoothGatt.GATT_SUCCESS){
}else{
//重連次數(shù)不大于最大重連次數(shù)
if(reConnectionNum < maxConnectionNum){
//重連次數(shù)自增
reConnectionNum++
//連接設(shè)備
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{
//斷開連接,返回連接失敗回調(diào)
}
}
}
//其他回調(diào)方法
}
發(fā)現(xiàn)服務(wù)
連接成功后趟大,觸發(fā)BluetoothGattCallback#onConnectionStateChange()
方法鹤树。
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//連接狀態(tài)回調(diào)
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// status 用于返回操作是否成功,會(huì)返回異常碼。
//操作成功的情況下
if (status == BluetoothGatt.GATT_SUCCESS){
//判斷是否連接碼
if (newState == BluetoothProfile.STATE_CONNECTED) {
//可延遲發(fā)現(xiàn)服務(wù)逊朽,也可不延遲
mHandler.post(() ->
//發(fā)現(xiàn)服務(wù)
mBluetoothGatt.discoverServices();
);
}else if(newState == BluetoothProfile.STATE_DISCONNECTED){
//判斷是否斷開連接碼
}
}
}
//其他回調(diào)方法
}
當(dāng)發(fā)現(xiàn)服務(wù)成功后罕伯,會(huì)觸發(fā)BluetoothGattCallback#onServicesDiscovered()
回調(diào):
//定義需要進(jìn)行通信的ServiceUUID
private UUID mServiceUUID = UUID.fromString("0000xxxx-0000-1000-8000-00805f9b34fb");
//定義藍(lán)牙Gatt回調(diào)類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//服務(wù)發(fā)現(xiàn)回調(diào)
@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);
//獲取到特定的服務(wù)不為空
if(gattService != null){
}else{
//獲取特定服務(wù)失敗
}
);
}
}
}
發(fā)現(xiàn)服務(wù)失敗
? 發(fā)現(xiàn)服務(wù)時(shí),會(huì)存在發(fā)現(xiàn)不了特定服務(wù)的情況叽讳∽匪或者說,整個(gè)BluetoothGatt
對(duì)象中的服務(wù)列表為空岛蚤。
BluetoothGatt
類中存在一個(gè)隱藏的方法refresh()
邑狸,用于刷新Gatt的服務(wù)列表。當(dāng)發(fā)現(xiàn)不了服務(wù)時(shí)涤妒,可以通過反射去調(diào)用該方法单雾,再發(fā)現(xiàn)一遍服務(wù)。
讀取和修改特征值
//定義需要進(jìn)行通信的ServiceUUID
private UUID mServiceUUID = UUID.fromString("0000xxxx-0000-1000-8000-00805f9b34fb");
//定義需要進(jìn)行通信的CharacteristicUUID
private UUID mCharacteristicUUID = UUID.fromString("0000yyyy-0000-1000-8000-00805f9b34fb");
//定義藍(lán)牙Gatt回調(diào)類
public class daqiBluetoothGattCallback extends BluetoothGattCallback{
//服務(wù)發(fā)現(xiàn)回調(diào)
@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);
//獲取到特定的服務(wù)不為空
if(gattService != null){
//獲取指定uuid的Characteristic
BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(mCharacteristicUUID);
//獲取特定特征成功
if(gattCharacteristic != null){
//寫入你需要傳遞給外設(shè)的特征值(即傳遞給外設(shè)的信息)
gattCharacteristic.setValue(bytes);
//通過GATt實(shí)體類將她紫,特征值寫入到外設(shè)中硅堆。
mBluetoothGatt.writeCharacteristic(gattCharacteristic);
//如果只是需要讀取外設(shè)的特征值:
//通過Gatt對(duì)象讀取特定特征(Characteristic)的特征值
mBluetoothGatt.readCharacteristic(gattCharacteristic);
}
}else{
//獲取特定服務(wù)失敗
}
);
}
}
}
當(dāng)成功讀取特征值時(shí),會(huì)觸發(fā)BluetoothGattCallback#onCharacteristicRead()
回調(diào)贿讹。
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取讀取到的特征值
characteristic.getValue()
}
}
當(dāng)成功寫入特征值到外設(shè)時(shí)渐逃,會(huì)觸發(fā)BluetoothGattCallback#onCharacteristicWrite()
回調(diào)。
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取寫入到外設(shè)的特征值
characteristic.getValue()
}
}
監(jiān)聽外設(shè)特征值改變
? 無論是對(duì)外設(shè)寫入新值围详,還是讀取外設(shè)特定Characteristic
的值朴乖,其實(shí)都只是單方通信。如果需要雙向通信助赞,可以在BluetoothGattCallback#onServicesDiscovered
中對(duì)某個(gè)特征值設(shè)置監(jiān)聽(前提是該Characteristic
具有NOTIFY屬性):
//設(shè)置訂閱notificationGattCharacteristic值改變的通知
mBluetoothGatt.setCharacteristicNotification(notificationGattCharacteristic, true);
//獲取其對(duì)應(yīng)的通知Descriptor
BluetoothGattDescriptor descriptor = notificationGattCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (descriptor != null){
//設(shè)置通知值
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
boolean descriptorResult = mBluetoothGatt.writeDescriptor(descriptor);
}
? 當(dāng)寫入完特征值后买羞,外設(shè)修改自己的特征值進(jìn)行回復(fù)時(shí),手機(jī)端會(huì)觸發(fā)BluetoothGattCallback#onCharacteristicChanged()
方法雹食,獲取到外設(shè)回復(fù)的值畜普,從而實(shí)現(xiàn)雙向通信。
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取外設(shè)修改的特征值
String value = characteristic.getValue()
//對(duì)特征值進(jìn)行解析
}
}
斷開連接
斷開連接的操作分為兩步:
- mBluetoothGatt.disconnect();
- mBluetoothGatt.close();
? 調(diào)用disconnect()
后群叶,會(huì)觸發(fā)手機(jī)會(huì)觸發(fā)BluetoothGattCallback#onConnectionStateChange()
的回調(diào)吃挑,回調(diào)斷開連接信息,newState = BluetoothProfile.STATE_DISCONNECTED
街立。但調(diào)用完disconnect()
緊接著馬上調(diào)用close()
舶衬,會(huì)終止BluetoothGattCallback#onConnectionStateChange()
的回調(diào)∈昀耄可以看情況將兩個(gè)進(jìn)行拆分調(diào)用逛犹,來實(shí)現(xiàn)斷開連接,但必須兩個(gè)方法都調(diào)用梁剔。
例如:
需要在外設(shè)修改特征值觸發(fā)BluetoothGattCallback#onCharacteristicChanged()
時(shí)虽画,斷開連接∪俨。可以先在BluetoothGattCallback#onCharacteristicChanged()
中調(diào)用disconnect()
,并等調(diào)用BluetoothGattCallback#onConnectionStateChange()
回調(diào)码撰,返回?cái)嚅_連接信息后,再調(diào)用close()
對(duì)Gatt資源進(jìn)行關(guān)閉个盆。
當(dāng)和外設(shè)進(jìn)行ble通信時(shí)脖岛,如出現(xiàn)任何意外情況,馬上調(diào)用斷開連接操作砾省。