來源:https://blog.csdn.net/weixin_35016347/article/details/108011878
整理:技術(shù)讓夢想更偉大 | 李肖遙
藍(lán)牙技術(shù)起源于愛立信在1994年提出的方案,旨在解決移動電話和其他配件之間進行低功耗、低成本的無線通信連接的方法。
下面的分析都是基于V4.1版本,方便入門,可以理解很多核心協(xié)議的設(shè)計思想
藍(lán)牙技術(shù)包含藍(lán)牙發(fā)展過程中的兩套技術(shù),但是這兩套原理和實現(xiàn)都不一樣,也無法實現(xiàn)互通
Basic Rate(BR)/AMP
最初的藍(lán)牙技術(shù),包括可選的EDR(Enhanced Data Rate)技術(shù)和交替使用的MAC層和PHY層擴展 AMP(Alternate MAC and PHY layer extension)【優(yōu)化傳輸速度的過程】
解釋:藍(lán)牙誕生之初使用的BR技術(shù),傳輸速率很低,隨著發(fā)展而變得無法支持,所以引入了EDR,這時還沒有修改軟硬件架構(gòu),但是之后又落伍了,所以直接引入了WiFi的底層協(xié)議,也就是MAC/PHY擴展,但這部分的實現(xiàn)就無法直接更替,所以BR/EDR只能與AMP交替使用
Low Energy(LE)
藍(lán)牙低功耗,則不關(guān)心傳輸速率,而是從降低功耗的角度實現(xiàn)的另一套技術(shù),跟前面的協(xié)議沒有絲毫關(guān)系
藍(lán)牙協(xié)議將藍(lán)牙整體分成了兩層架構(gòu),底層是核心協(xié)議,描述了藍(lán)牙核心技術(shù)的基礎(chǔ)和規(guī)范,應(yīng)用層協(xié)議則基于具體需求,使用核心協(xié)議提供的機制,實現(xiàn)不同的功能策略
核心協(xié)議包含兩部分,Host和Controller,這兩部分在不同的藍(lán)牙協(xié)議版本中略有區(qū)別,但大致上是,Controller完成硬件側(cè)的規(guī)范制訂,包括信號調(diào)制解調(diào),會抽象出用于通信的邏輯鏈路,可能存在一個或多個,如LE Controller、BR/EDR Controller;Host則在邏輯鏈路的基礎(chǔ)上完成更友好的封裝,屏蔽掉技術(shù)細(xì)節(jié),方便應(yīng)用層對數(shù)據(jù)的使用
藍(lán)牙協(xié)議也采用層次結(jié)構(gòu),自下而上依次為物理層、邏輯層、L2CAP層和應(yīng)用層
應(yīng)用層(App Layer)為不同場景定義規(guī)范,提出Profile(一項服務(wù))的概念,實現(xiàn)各種應(yīng)用功能
L2CAP(Logical Link Control and Adaptation Protocol Layer)
邏輯層(Logical Layer)
物理層(Physical Layer)
實現(xiàn)一個BLE應(yīng)用,需要一個支持BLE射頻的芯片,然后基于一個與芯片配套的協(xié)議棧,開發(fā)藍(lán)牙應(yīng)用。
協(xié)議棧的作用就是軟件和硬件之間的橋梁,對應(yīng)用數(shù)據(jù)進行封包然后生成可以通過射頻發(fā)送的空中數(shù)據(jù)包及其逆向過程。
Physical Layer(PHY)
Link Layer(LL)
- Standby:初始狀態(tài),不收發(fā)數(shù)據(jù),接受上層協(xié)議命令與其他狀態(tài)切換
- Advertising:通過廣播發(fā)送數(shù)據(jù)的狀態(tài),建立連接后可進入Connection
- Scanning:接收廣播的數(shù)據(jù)的狀態(tài)
- Initiating:特殊的接收狀態(tài),類似Scanning,接收Advertiser廣播的連接數(shù)據(jù),建立連接后進入Connection
- Connection:建立連接后擁有單獨的通道
12345
Host Controller Interface(HCI)
L2CAP
Attribute Protocol(ATT)
0x0004
Generic Attribute Profile(GATT)
00000000-0000-1000-8000-00805F9B34FB
0000xxxx-0000-1000-8000-00805F9B34FB
xxxxxxxx-0000-1000-8000-00805F9B34FB
Security Manager(SM)
Generic Access Profile(GAP)
使用場景
協(xié)議層次
LL
Type是指PDU的類型,如不同的狀態(tài)下也有不同的消息類型,TxAdd和RxAdd都是地址類型flag,針對不同的type有不同的含義,RFU都是保留字段,Length標(biāo)明payload的長度
Payload內(nèi)容
State | Type | Descriptions | Payload | length | Descriptions |
---|---|---|---|---|---|
Advertising | ADV_IND | 常規(guī)廣播,可連接可掃描 | AdvA | 6 | address of broadcaster |
【后續(xù)建立點對點連接,監(jiān)聽CONNECT_REQ請求】 | AdvData | 0~31 | Broadcast data | ||
ADV_NONCONN_IND | 同ADV_IND,不可連接不可掃描 | AdvA | 6 | address of broadcaster | |
【用于定時傳輸簡單數(shù)據(jù)】 | AdvData | 0~31 | Broadcast data | ||
ADV_SCAN_IND | 同ADV_IND,不可連接可掃描 | AdvA | 6 | address of broadcaster | |
【用于傳輸額外數(shù)據(jù),監(jiān)聽SCAN_REQ請求】 | AdvData | 0~31 | Broadcast data | ||
ADV_DIRECT_IND | 點對點連接,已知雙方藍(lán)牙地址,無廣播數(shù)據(jù),可被指定設(shè)備連接不可掃描 | AdvA | 6 | address of broadcaster | |
【快速建立連接,不關(guān)心廣播數(shù)據(jù),監(jiān)聽CONNECT_REQ請求】 | InitA | 6 | address of receiver/initiater | ||
Scanning | SCAN_REQ | 接收ADV_IND/ADV_SCAN_IND后,請求更多信息 | ScanA | 6 | address of scanne r |
【接收廣播數(shù)據(jù)后請求更多信息】 | AdvA | 6 | address of broadcaster | ||
SCAN_RSP | SCAN_REQ的響應(yīng),返回更多信息 | AdvA | 6 | address of broadcaster | |
ScanRspData | 0~31 | response data | |||
Initiating | CONNECT_REQ | 接收ADV_IND/ADV_DIRECT_IND后,請求建立連接 | InitA | 6 | address of receiver/initiater |
【請求建立連接】 | AdvA | 6 | address of broadcaster | ||
LLData | 22 | parameters of connection |
BLE設(shè)備地址類型
HCI
OCF(Opcode Command Field)表示特定的HCI命令,OGF(Opcode Group Field)表示該HCI命令所屬組別,他們共同組成16位操作碼;Parameter Total Length表示所有參數(shù)總長度
所有BLE相關(guān)的HCI Command的OGF都是0x08
GAP
廣播/掃描應(yīng)答數(shù)據(jù),包含有意義部分和無意義部分(補齊為0),有意義部分是由一個個廣播塊(AD Structure)組成,每個廣播塊包含1字節(jié)長度(指示數(shù)據(jù)部分長度)和剩下的數(shù)據(jù)部分,數(shù)據(jù)部分又分為數(shù)據(jù)類型和數(shù)據(jù)內(nèi)容,數(shù)據(jù)類型會指示真實Data部分的內(nèi)容,例如0x01
表示Data內(nèi)容是描述設(shè)備物理連接狀態(tài),再例如0x08
表示Data內(nèi)容是設(shè)備名稱,更多可以參考generic-access-profile
舉個廣播數(shù)據(jù)的例子
02 01 06 03 03 aa fe 17 16 aa fe 00 -10 00 01 02 03 04 05 06 07 08 09 0a 0b 0e 0f 00 00 00 00
1
02 01 06
是一個AD Structure,數(shù)據(jù)部分長度為2字節(jié),類型是0x01
,描述設(shè)備物理連接狀態(tài),數(shù)據(jù)部分0x06
,1字節(jié)8bit,每bit都是一個標(biāo)志位([預(yù)留]|[預(yù)留]|[預(yù)留]|[同時支持BLE和BR/EDR(Host)]|[同時支持BLE和BR/EDR(Controller)]|[不支持BR/EDR]|[普通發(fā)現(xiàn)模式]|[有限發(fā)現(xiàn)模式]),那么這個廣播就是普通發(fā)現(xiàn)模式,不支持BR/EDR
03 03 aa fe
是第二個AD Structure,數(shù)據(jù)部分長度為3字節(jié),類型是0x03
,表示16-bits的Service UUID
17 16 aa fe 00 -10 00 01 02 03 04 05 06 07 08 09 0a 0b 0e 0f 00 00 00 00
是最后一個AD Structure,數(shù)據(jù)部分長度為0x17
即23字節(jié),類型是0x16
,表示服務(wù)數(shù)據(jù)
AD Type | Description | AD Data |
---|---|---|
0x01 | 設(shè)備物理連接狀態(tài) | 1字節(jié)8bit,每個bit都是一個標(biāo)志位 [預(yù)留]|[預(yù)留]|[預(yù)留]|[同時支持BLE和BR/EDR(Host)]|[同時支持BLE和BR/EDR(Controller)]|[不支持BR/EDR]|[普通發(fā)現(xiàn)模式]|[有限發(fā)現(xiàn)模式] |
0x02 | UUID | 非完整的16-bit UUID |
0x03 | UUID | 完整的16-bit UUID |
0x04 | UUID | 非完整的32-bit UUID |
0x05 | UUID | 完整的32-bit UUID |
0x06 | UUID | 非完整的128-bit UUID |
0x07 | UUID | 完整的128-bit UUID |
0x08 | 設(shè)備名稱 | 縮寫設(shè)備名稱 |
0x09 | 設(shè)備名稱 | 完整設(shè)備名稱 |
0x0a | TX Power Level | TX Power Level |
0xff | 廠商數(shù)據(jù) | [廠商ID]|[廠商自定義數(shù)據(jù)] |
經(jīng)典藍(lán)牙中保持連接非常耗費資源,但是每次連接建立效率又非常低,為了優(yōu)化體驗,BLE簡化了連接過程(毫秒級),極大的降低了面向連接通信的代價
藍(lán)牙通信系統(tǒng)中,對于連接的定義是:在約定的時間段內(nèi),雙方都到一個指定的物理Channel上通信。
LL
面向連接的通信使用特定的PDU,稱為Data channel PDU
LLID指示Data Channel傳輸?shù)腜DU類型,傳輸數(shù)據(jù)是LL Data PDU,傳輸控制信息是LL Control PDU,NESN(Next Expected Sequence Number)和SN(Sequence Number)用于數(shù)據(jù)傳輸過程中的應(yīng)答和流控,MD(More Data)用于關(guān)閉連接,RFU是預(yù)留位,Length指示有效數(shù)據(jù)長度,包括Payload和MIC
LLID | type | Description |
---|---|---|
01b | LL Data PDU | 空包或未傳輸完成的消息(被拆包) |
10b | LL Data PDU | (不需拆包)完整消息或第一個包 |
11b | LL Control PDU | 用于控制、管理LL連接的數(shù)據(jù)包,此時Payload為1字節(jié)Opcode和剩余的控制數(shù)據(jù) |
HCI
GAP
White List
白名單就是一組藍(lán)牙地址列表,通過設(shè)置白名單可以允許掃描、連接特定的藍(lán)牙設(shè)備,以及被掃描、連接
LL Privacy
在白名單的基礎(chǔ)上將設(shè)備地址進行加密,轉(zhuǎn)變成Resolvable Private addresses
LL Encryption
數(shù)據(jù)發(fā)送和接收過程進行加解密
SecurityManager
為BLE設(shè)備提供加密連接相關(guān)的key,包含以下規(guī)范:
SMP規(guī)范中,配對的定義是,Master和Slave通過協(xié)商確定用于加密通信的key的過程,包含三個階段:
具體可以參考開發(fā)的藍(lán)牙測試工具:BLETool
BLE,藍(lán)牙低功耗(極低的運行和待機功耗)
Android 4.3(API 18) 開始引入 BLE ,即藍(lán)牙4.0
Android 4.3 的 BLE 只支持 Central Role(中心設(shè)備,掃描并連接外圍設(shè)備)
Android 5.0 開始同時支持 Central Role 和 Peripheral Role(外圍設(shè)備,向外廣播發(fā)送數(shù)據(jù))
1、權(quán)限
<!--藍(lán)牙權(quán)限-->
<uses-permission android:name='android.permission.BLUETOOTH'/>
<!--藍(lán)牙相關(guān)操作設(shè)置權(quán)限-->
<uses-permission android:name='android.permission.BLUETOOTH_ADMIN'/>
<!--位置權(quán)限,掃描時需要,Android 9-需要模糊定位,Android 10開始需要精確定位-->
<uses-permission android:name='android.permission.ACCESS_COARSE_LOCATION'/>
<uses-permission android:name='android.permission.ACCESS_FINE_LOCATION'/>
<!--聲明使用BLE硬件特性,僅系統(tǒng)支持時可安裝-->
<uses-feature android:name='android.hardware.bluetooth_le' android:required='true' />
2、開啟/關(guān)閉藍(lán)牙
// 判斷支持藍(lán)牙功能
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
// 獲取藍(lán)牙管理服務(wù)
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
// 獲取藍(lán)牙適配器
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter != null) {
// 判斷藍(lán)牙是否開啟
if (bluetoothAdapter.isEnabled()) {
// 關(guān)閉藍(lán)牙
bluetoothAdapter.disable();
} else {
// 1、靜默開啟藍(lán)牙
bluetoothAdapter.enable();
// 2、顯式請求開啟藍(lán)牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, REQUEST_BLUETOOTH_ENABLE);
}
}
}
3、掃描與監(jiān)聽
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
// 獲取藍(lán)牙管理服務(wù)
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
// 獲取藍(lán)牙適配器
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter != null) {
// 判斷藍(lán)牙是否開啟
if (bluetoothAdapter.isEnabled()) {
// 獲取掃描器實例
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
boolean isScanning = false;
if (bluetoothLeScanner != null) {
if (isScanning) {
// 停止掃描
isScanning = false;
bluetoothLeScanner.stopScan(scanCallback);
} else {
// 開始掃描
isScanning = true;
bluetoothLeScanner.startScan(scanCallback);
}
}
}
}
}
Android 8 開始提供一個后臺持續(xù)掃描的API,應(yīng)用殺死后也可以繼續(xù)掃描,直到關(guān)閉藍(lán)牙【待驗證】
public int startScan (List<ScanFilter> filters, ScanSettings settings, PendingIntent callbackIntent);
// 設(shè)置攔截器和掃描選項
bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback);
初始化掃描過濾器
scanFilters = new ArrayList<>();
ScanFilter scanFilter = new ScanFilter.Builder()
.setDeviceName('lalala')
.setServiceUuid(new ParcelUuid(UUID.randomUUID()))
.build();
scanFilters.add(scanFilter);
初始化掃描設(shè)置
scanSettings = new ScanSettings.Builder()
// 設(shè)置掃描模式
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
// 設(shè)置回調(diào)類型
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
// 設(shè)置配對模式
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
// 設(shè)置報告延遲
.setReportDelay(0)
.build();
兩個類都是通過 Builder 構(gòu)造,提供系列函數(shù)用于參數(shù)設(shè)置,如 setDeviceName()
、setScanMode()
、setMatchMode()
等
4、掃描回調(diào)
scanCallback = new ScanCallback {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
}
其中 onBatchScanResults()
是批量返回掃描結(jié)果??赏ㄟ^下面的接口判斷藍(lán)牙芯片是否支持批處理
Bluetoothadapter.isOffloadedScanBatchingSupported();
1
注意 onScanResult()
和 onBatchScanResults()
是互斥的,ScanSettings
中 setReportDelay()
設(shè)置為0(默認(rèn))則通過 onScanResult()
返回掃描結(jié)果,否則開啟批處理掃描模式,并觸發(fā) onBatchScanResults()
回調(diào)。
5、廣播數(shù)據(jù)解析
掃描成功會返回 ScanResult 廣播數(shù)據(jù)類,然后進一步解析
// 返回遠(yuǎn)程設(shè)備類
BluetoothDevice device = scanResult.getDevice();
// 返回掃描記錄,包含廣播和掃描響應(yīng)
ScanRecord scanRecord = scanResult.getScanRecord();
// 返回信號強度,[-127, 126]
int rssi = scanResult.getRssi()
BluetoothDevice 是設(shè)備信息類,常用的方法有
// 獲取硬件地址
String address = device.getAddress();
// 獲取藍(lán)牙名稱
String name = device.getName();
// 獲取設(shè)備類型,如DEVICE_TYPE_CLASSIC、DEVICE_TYPE_LE、DEVICE_TYPE_DUAL、DEVICE_TYPE_UNKNOWN
int type = device.getType();
// 獲取綁定狀態(tài),如BOND_NONE、BOND_BONDING、BOND_BONDED
int state = device.getBondState();
6、連接設(shè)備
掃描返回的廣播消息中可以獲取到遠(yuǎn)程設(shè)備的MAC地址,可用于設(shè)備的連接
if (bluetoothAdapter.isEnabled()) {
// 獲取遠(yuǎn)程設(shè)備對象
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
if (bluetoothDevice != null) {
handler.post(new Runnable() {
@Override
public void run() {
BluetoothGatt bluetoothGatt;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 連接遠(yuǎn)程設(shè)備
bluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
bluetoothGatt = bluetoothDevice.connectGatt(context, false, bluetoothGattCallback);
}
}
});
}
}
bluetoothGatt 是藍(lán)牙通用屬性協(xié)議的封裝,定義了BLE通信的一些基本規(guī)則和連接通信操作
7、連接回調(diào)
bluetoothGattCallback 則是 bluetoothGatt 連接的回調(diào)類,通知客戶端連接狀態(tài)和結(jié)果
bluetoothGattCallback = new BluetoothGattCallback {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, final int status, final int newState) {
super.onConnectionStateChange(gatt, status, newState);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, final int status) {
super.onServicesDiscovered(gatt, status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
super.onDescriptorRead(gatt, descriptor, status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
}
連接成功及其他連接狀態(tài)改變都會調(diào)用 onConnectionStateChange()
方法。status 表示這個操作的狀態(tài),是 BluetoothGatt.GATT_SUCCESS
或者讀寫受限、超過范圍等其他錯誤狀態(tài)。newState 則表示當(dāng)前設(shè)備的連接狀態(tài),連接成功為 BluetoothProfile.STATE_CONNECTED
,連接失敗是BluetoothProfile.STATE_DISCONNECTED
。
8、發(fā)現(xiàn)服務(wù)
連接成功后就可以開始通信,從請求服務(wù)開始(Profile只是一系列具有共同業(yè)務(wù)需求的服務(wù)的抽象集合,服務(wù)才是實體)
bluetoothGatt.discoverServices();
1
發(fā)現(xiàn)服務(wù)后會觸發(fā) onServicesDiscovered()
回調(diào),然后繼續(xù)獲取服務(wù)
// 獲取所有服務(wù)
List<BluetoothGattService> bleServiceList = bluetoothGatt.getServices();
// 通過uuid獲取特定的服務(wù)
BluetoothGattService bleService = bluetoothGatt.getService(serviceUuid);
BluetoothGattService 是藍(lán)牙服務(wù)類,是與某個場景相關(guān)的一系列行為的抽象,具有一個唯一的UUID,然后服務(wù)類型,如SERVICE_TYPE_PRIMARY、SERVICE_TYPE_SECONDARY(主要服務(wù)可以包含二級服務(wù)),包含的特征列表
9、獲取特征
// 獲取所有特征
List<BluetoothGattCharacteristic> bleCharacteristicList = bleService.getCharacteristics();
// 通過uuid獲取特定的特征
BluetoothGattCharacteristic bleCharacteristic = bleService.getCharacteristic(characteristicUuid);
BluetoothGattCharacteristic 是藍(lán)牙特征類,是通信的基本數(shù)據(jù)單位,包含標(biāo)志特征的唯一的UUID,描述特征訪問權(quán)限的特性,如PROPERTY_BROADCAST、PROPERTY_READ、PROPERTY_WRITE等,特征的實際取值,以及特征的描述
10、讀寫特征
// 讀取特征
bluetoothGatt.readCharacteristic(bleCharacteristic);
// 設(shè)置并寫入特征
bleCharacteristic.setValue('XXX');
bluetoothGatt.writeCharacteristic(bleCharacteristic);
這里的讀寫特征函數(shù)都是返回布爾類型表示是否操作成功,如果成功真正的值會在 onCharacteristicRead/onCharacteristicWrite
回調(diào)中讀取/寫入
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
switch (status) {
case GATT_SUCCESS:
String valueStr = BLEUtils.byte2HexString(characteristic.getValue());
break;
case GATT_READ_NOT_PERMITTED:
ToastUtils.showShort(context, 'GATT_READ_NOT_PERMITTED');
break;
default:
ToastUtils.showShort(context, 'CHARACTERISTIC_READ_FAILED');
break;
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
switch (status) {
case GATT_SUCCESS:
String valueStr = BLEUtils.byte2HexString(characteristic.getValue());
break;
default:
ToastUtils.showShort(context, 'CHARACTERISTIC_WRITE_FAILED');
break;
}
}
11、監(jiān)聽特征
真正要實現(xiàn)通信除了單方面讀寫,還需要對數(shù)據(jù)變化進行監(jiān)聽,這樣就可以進行數(shù)據(jù)交換
// 設(shè)置特征監(jiān)聽為true,且要求特征具有NOTIFY屬性
bluetoothGatt.setCharacteristicNotification(characteristic, true);
這樣,自己或?qū)Ψ教卣鞲淖儠r就會回調(diào)函數(shù)從而獲取改變后的特征值
@Override
public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
Log.d('bledemo', 'uuid = ' + characteristic.getUuid().toString());
Log.d('bledemo', 'value = 0x' + BLEUtils.byte2HexString(characteristic.getValue()));
}
12、獲取描述
// 獲取所有描述
List<BluetoothGattDescriptor> bleDescriptorList = bleCharacteristic.getDescriptors();
// 通過uuid獲取特定的描述
BluetoothGattDescriptor bleDescriptor = bleCharacteristic.getDescriptor(descriptorUuid);
BluetoothGattDescriptor 是藍(lán)牙特征描述類,包含對特征的一些額外描述信息
13、讀寫描述
// 讀取描述
bluetoothGatt.readDescriptor(bleDescriptor);
// 設(shè)置并寫入描述
bleDescriptor.setValue('XXX');
bluetoothGatt.writeDescriptor(bleDescriptor);
同樣讀寫成功會觸發(fā)onDescriptorRead/onDescriptorWrite
回調(diào)
@Override
public void onDescriptorRead(BluetoothGattDescriptor descriptor, int status) {
switch (status) {
case GATT_SUCCESS:
String valueStr = BLEUtils.byte2HexString(descriptor.getValue());
break;
case GATT_READ_NOT_PERMITTED:
ToastUtils.showShort(context, 'GATT_READ_NOT_PERMITTED');
break;
default:
ToastUtils.showShort(context, 'DESCRIPTOR_READ_FAILED');
break;
}
}
@Override
public void onDescriptorWrite(BluetoothGattDescriptor descriptor, int status) {
switch (status) {
case GATT_SUCCESS:
String valueStr = BLEUtils.byte2HexString(descriptor.getValue());
break;
default:
ToastUtils.showShort(context, 'DESCRIPTOR_WRITE_FAILED');
break;
}
}
14、斷開連接
// 斷開連接,會觸發(fā)onConnectionStateChange()回調(diào)
bluetoothGatt.disconnect();
// 關(guān)閉連接,不會觸發(fā)回調(diào)
bluetoothGatt.close();
1234
15、開啟/關(guān)閉廣播
if (bluetoothAdapter.isEnabled()) {
// 設(shè)置廣播設(shè)備的名稱,方便搜索
bluetoothAdapter.setName('XXX');
// 獲取廣播類
BluetoothLeAdvertiser bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
if (bluetoothLeAdvertiser != null) {
if (isAdvertising) {
// 關(guān)閉廣播
bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
} else {
// 開始廣播
bluetoothLeAdvertiser.startAdvertising(advertiseSetting, advertiseData, advertiseCallback);
}
}
}
還可以發(fā)送帶響應(yīng)報文的廣播包
bluetoothLeAdvertiser.startAdvertising(advertiseSetting, advertiseData, advertiseResData, advertiseCallback);
1
其中 advertiseSetting 為廣播設(shè)置類對象
advertiseSetting = new AdvertiseSettings.Builder()
// 廣播模式,控制廣播功率和延遲
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
// 廣播發(fā)射功率級別
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
// 廣播超時時間,最大值為 3*60*1000 毫秒,為 0 時禁用超時,默認(rèn)無限廣播
.setTimeout(advertiseTimeout)
// 廣播連接類型
.setConnectable(true)
.build();
12345678910
advertiseData、advertiseResData 為廣播包
advertiseData = new AdvertiseData.Builder()
// 廣播是否包含設(shè)備名稱
.setIncludeDeviceName(true)
// 廣播是否包含發(fā)射功率
.setIncludeTxPowerLevel(true)
// 添加服務(wù)uuid
.addServiceUuid(new ParcelUuid(UUID.randomUUID()))
.build();
advertiseResData = new AdvertiseData.Builder()
// 添加自定義服務(wù)數(shù)據(jù)
.addServiceData(new ParcelUuid(UUID.randomUUID()), new byte[]{1,2,3,4})
// 添加自定義廠商數(shù)據(jù)
.addManufacturerData(0x06, new byte[]{5,6,7,8})
.build();
16、廣播回調(diào)
advertiseCallback 是廣播回調(diào)
advertiseCallback = new AdvertiseCallback {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
}
}
17、啟動GATT服務(wù)
只有廣播仍然不夠,作為外圍角色的設(shè)備還需要啟動GATT服務(wù),等待中心設(shè)備與之建立連接之后就可以通過服務(wù)通信
// 啟動 Gatt 服務(wù)
bluetoothGattServer = bluetoothManager.openGattServer(context, bluetoothGattServerCallback);
12
接下來可以向啟動的GATT服務(wù)中添加Service
// 構(gòu)造服務(wù)
BluetoothGattService bluetoothGattService = new BluetoothGattService(UUID.randomUUID(), BluetoothGattService.SERVICE_TYPE_PRIMARY);
// 構(gòu)造特征
BluetoothGattCharacteristic bluetoothGattCharacteristic = new BluetoothGattCharacteristic(UUID.randomUUID(), BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
bluetoothGattCharacteristic.setValue('character_test_value');
// 構(gòu)造描述
BluetoothGattDescriptor bluetoothGattDescriptor = new BluetoothGattDescriptor(UUID.randomUUID(), BluetoothGattDescriptor.PERMISSION_READ |BluetoothGattDescriptor.PERMISSION_WRITE);
bluetoothGattDescriptor.setValue('descriptor_test_value'.getBytes());
// 添加描述到特征中
bluetoothGattCharacteristic.addDescriptor(bluetoothGattDescriptor);
// 添加特征到服務(wù)中
bluetoothGattService.addCharacteristic(bluetoothGattCharacteristic);
// 添加服務(wù)
bluetoothGattServer.addService(bluetoothGattService);
18、GATT服務(wù)回調(diào)
bluetoothGattServerCallback 是GATT服務(wù)的回調(diào),當(dāng)設(shè)備被連接、通信(讀寫特征)時都會觸發(fā)響應(yīng)的回調(diào)函數(shù)
bluetoothGattServerCallback = new BluetoothGattServerCallback {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
BluetoothGattCharacteristic characteristic,
boolean preparedWrite, boolean responseNeeded,
int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite,
responseNeeded, offset, value);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
BluetoothGattDescriptor descriptor,
boolean preparedWrite, boolean responseNeeded,
int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded,
offset, value);
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
}
聯(lián)系客服