藍牙在我們生活中非常普遍,小到手表,大到電視洗衣機等都有藍牙的身影。
那么,你對藍牙協議了解多少?
Part101 藍牙概述
藍牙技術起源于愛立信在1994年提出的方案,旨在解決移動電話和其他配件之間進行低功耗、低成本的無線通信連接的方法。
藍牙發展歷史
第一代藍牙主要是指90年代的V1.0~V1.2版本,是關于段距離通信的早期探索,此時還存在許多問題,應用不是特別廣泛
第二代藍牙主要是00年中V2.0~V2.1版本,新增了EDR(Enhanced Data Rate)技術提高傳輸速率,以及體驗及安全
第三代藍牙主要是00年末V3.0版本,新增了802.11 WiFi協議,引入了AMP(Generic Alternate MAC/PHY)交替射頻技術,極大的提高了傳輸速率并降低功耗
第四代藍牙是10年以來的V4.0~V4.2版本,主推LE(Low Energy)低功耗,大約僅消耗十分之一,將三種規格,包括經典藍牙、高速藍牙、和藍牙低功耗,集中在一起形成一套綜合協議規范
第五代藍牙是16年開始提出的V5.0版本,主要是為了支持物聯網,在功耗、傳輸速率、有效傳輸距離、數據包容量方面都做了極大的提升
下面的分析都是基于V4.1版本,方便入門,可以理解很多核心協議的設計思想
Part202 藍牙技術分類
藍牙技術包含藍牙發展過程中的兩套技術,但是這兩套原理和實現都不一樣,也無法實現互通
Basic Rate(BR)/AMP
最初的藍牙技術,包括可選的EDR(Enhanced Data Rate)技術和交替使用的MAC層和PHY層擴展 AMP(Alternate MAC and PHY layer extension)【優化傳輸速度的過程】
解釋:藍牙誕生之初使用的BR技術,傳輸速率很低,隨著發展而變得無法支持,所以引入了EDR,這時還沒有修改軟硬件架構,但是之后又落伍了,所以直接引入了WiFi的底層協議,也就是MAC/PHY擴展,但這部分的實現就無法直接更替,所以BR/EDR只能與AMP交替使用
Low Energy(LE)
藍牙低功耗,則不關心傳輸速率,而是從降低功耗的角度實現的另一套技術,跟前面的協議沒有絲毫關系
Part303 藍牙架構
structure
藍牙協議將藍牙整體分成了兩層架構,底層是核心協議,描述了藍牙核心技術的基礎和規范,應用層協議則基于具體需求,使用核心協議提供的機制,實現不同的功能策略
核心協議包含兩部分,Host和Controller,這兩部分在不同的藍牙協議版本中略有區別,但大致上是,Controller完成硬件側的規范制訂,包括信號調制解調,會抽象出用于通信的邏輯鏈路,可能存在一個或多個,如LE Controller、BR/EDR Controller;Host則在邏輯鏈路的基礎上完成更友好的封裝,屏蔽掉技術細節,方便應用層對數據的使用
Part404 藍牙協議
藍牙協議也采用層次結構,自下而上依次為物理層、邏輯層、L2CAP層和應用層
protocol
應用層(App Layer)為不同場景定義規范,提出Profile(一項服務)的概念,實現各種應用功能
L2CAP(Logical Link Control and Adaptation Protocol Layer)
邏輯鏈路控制和適配協議,負責管理邏輯鏈路,使得不同應用可共享一個邏輯鏈路,類似端口的實現
在邏輯鏈路的基礎上,抽象出與具體技術無關的數據傳輸信道,如單/廣播,然后對上以L2CAP channel endpoints的概念,為不同應用程序提供獨立的傳輸通道
邏輯層(Logical Layer)
提供設備對象之間邏輯傳輸,在物理層的基礎上,建立邏輯信道,主要基于傳輸類型來劃分,包括控制類傳輸(負責底層物理鏈路的管理)、用戶類傳輸(負責用戶數據傳輸)和其他特殊類型的傳輸
不同的邏輯信道(Logical Link)會在下層對應Logical Transport,實現流控、應答、重傳等機制
物理層(Physical Layer)
負責提供數據傳輸的物理通道
物理鏈路(Physical Link):對物理信道的進一步封裝
物理信道(Physical channel):三種藍牙技術都使用相同的頻段和頻率范圍,但是具體實現都不一樣
Advertisement Broadcast Channel:用于設備間無連接廣播通信,包括發現/連接操作
Piconet channel:用于連接狀態下通信
Inquiry Scan Physical Channel:用于發現操作,即搜索/被搜索
Page Scan Physical Channel:用于連接操作,即連接/被連接
Basic Piconet Physical Channel:用于連接狀態下通信,使用79個跳頻點
Adapted Piconet Physical Channel:用于連接狀態下通信,使用較少RF跳頻點
Synchronization Scan Channel:用于無連接的廣播通信
BR/EDR頻段分成了79個channel,每個占1M帶寬;采用跳頻技術(Hopping),即物理信道隨機占用某一channel;定義了五種物理信道,每次只能在一種物理信道上通信,采用時分方式
AMP直接使用WIFI的物理層規范,只有一個物理信道,用于已連接設備之間的高速數據通信
LE頻段分成了40個channel,每個占2M帶寬;有兩種物理信道,每次只能在一種物理信道上通信,采用時分方式
Part505 BLE協議棧
實現一個BLE應用,需要一個支持BLE射頻的芯片,然后基于一個與芯片配套的協議棧,開發藍牙應用。
協議棧的作用就是軟件和硬件之間的橋梁,對應用數據進行封包然后生成可以通過射頻發送的空中數據包及其逆向過程。
BLE-protocol
Physical Layer(PHY)
藍牙通信系統的物理層,是免費ISM頻段,整個頻帶分成40份,每份帶寬2MHz;此外還定義了RF收發相關的特性,如發射功率、調制解調方式等
Link Layer(LL)
解決在有限物理信道上傳輸遠多實際信道數量的數據,即信道共享,然后為通信實體創建看似獨享的邏輯信道,以及解決傳輸過程中的校驗、重傳等問題
LL中的信道設計:BLE系統基于通信場景,在40個物理信道中選取三個作為廣播信道,處理數據量小、發送不頻繁、時延不敏感的場景,存在的問題就是不可靠、效率低、不安全;另外的場景則在剩下的37個信道中選取一個為雙方建立單獨信道,并且為了抗干擾采用跳頻技術
為此,LL為通信雙方實體定義了以下狀態及切換條件
BLE-state
- Standby:初始狀態,不收發數據,接受上層協議命令與其他狀態切換 - Advertising:通過廣播發送數據的狀態,建立連接后可進入Connection - Scanning:接收廣播的數據的狀態 - Initiating:特殊的接收狀態,類似Scanning,接收Advertiser廣播的連接數據,建立連接后進入Connection - Connection:建立連接后擁有單獨的通道 12345
這里會使用空中接口協議(Air Interface Protocol,AIP)來負責實體之間的數據交換和狀態切換
Host Controller Interface(HCI)
定義Host和Contorller之間的通信協議,如兩個芯片之間的串口
L2CAP
邏輯控制和適配協議的工作就是實現邏輯信道的多路復用(multiplexing),對上層數據進行分割和重組,以及后續的流控、錯誤控制和重傳等
多路復用思想:將要發送的數據分割成一個個數據包(Packet Data Unit,PDU),添加包含特定ID的頭部,接收方解析頭部ID進行重組
多路復用實現
基于連接:L2CAP會為每個邏輯信道分配一個編號(Channel ID,CID),有些CID會有固定用途
基于協議(略)
Attribute Protocol(ATT)
屬性協議主要是針對物聯網場景,核心思想就是將采集的信息或控制的命令以屬性的形式抽象出來,提供接口供遠端設備讀寫
采用C/S形式,信息提供方為ATT Server,如傳感器,訪問方為ATT Client
為每個Attribute定義了三個屬性
Type,即Attribute的類型,使用UUID區分
Handle,服務端用來唯一標識Attribute的16-bit數值
Value,Attribute的值
為每個Attribute定義了一系列權限,方便服務端控制客戶端的行為,包括訪問/加密/認證/授權
對于不同的Attribute,客戶端對服務端的訪問方式也不一樣,包括Find/Read/Write
傳輸過程是在L2CAP的基礎上,使用基于通道的多路復用,CID為0x0004
Generic Attribute Profile(GATT)
通用屬性配置文件,Attribute只是將信息(或者說通信數據)做一下抽象,但是真正對抽象的信息做分類管理則是GATT來完成,形成profile的概念(解決了很多無線協議的兼容問題),profile可以理解成應用場景或者使用方式
GATT提供了這樣一種通用的、信息存儲與共享的profile framework,實現BLE雙向通信
GATT的層次結構
GATT_profile_hierarchy
Profile位于最頂層,不是真正存在的配置文件,而是一個或多個場景相關的service的抽象集合
Service(服務)是一種行為的抽象,具有唯一標識UUID,每個service包含一個或多個Characteristic,也可以通過include的方式包含其他service
Characteristic(特征)可以理解成一個屬性,是真正與設備通信相關的,數據發送和接收的最基本單位,通過對特征的讀寫實現藍牙雙向通信,它由一個Propertities(定義Value的使用規范和Descriptor的訪問規范)、一個Value(特征的實際取值)和一個或多個Descriptor(Value相關的描述信息)組成,每個特征也具有自己的唯一標識,但是有三種形式:
16-bit是官方認證,收費,Bluetooth_Base_UUID 為 00000000-0000-1000-8000-00805F9B34FB
16-bit轉128-bit,格式為 0000xxxx-0000-1000-8000-00805F9B34FB
32-bit轉128-bit,格式為 xxxxxxxx-0000-1000-8000-00805F9B34FB
事實上,目前幾乎所有的BLE應用都基于GATT實現通信
GATT通信基于C/S模型,外圍設備作為Server端,維護ATT結構及產出數據,中心設備作為client端,請求連接獲取數據
GATT連接對外圍設備是獨占的,即一個外圍設備同時與一個中心設備建立連接,一個中心設備可同時與多個外圍設備建立連接
Security Manager(SM)
安全管理協議主要負責BLE通信過程中安全相關的內容,包括認證、加密這些過程
Generic Access Profile(GAP)
通用訪問配置文件,定義了藍牙設備的通用的訪問功能,與GATT的數據通信過程對應,處理無連接及連接建立過程的通信,也就是為廣播、掃描、發起連接這些過程定義統一規范
定義了用戶接口的基本參數,包括藍牙地址、名稱、pincode、class等概念
定義了設備的角色:
Broadcaster Role:正在發送advertising events的設備
Observer Role:正在接收advertising events的設備
Peripheral Role:接受Link Layer連接的設備(對應Link Layer的slave角色)
Central Role,發起Link Layer連接的設備(對應Link Layer的master角色)
定義了通信的過程和操作模式:
Broadcast mode and observation procedure:實現單向的、無連接的通信
Discovery modes and procedures:實現藍牙設備的發現操作
Connection modes and procedures:實現藍牙設備的連接操作
Bonding modes and procedures:實現藍牙設備的配對操作
Part606 BLE的廣播
使用場景
單向、無連接的數據通信,發送者使用廣播信道發送數據,接受者掃描接收數據
連接建立階段
協議層次
GAP:以應用程序角度進行功能封裝,提供一套統一的、通用的廣播規范
HCI:將LL提供的功能抽象成Command/Events的形式,供上層使用
LL:負責廣播通信相關功能的定義和實現,包括信道選擇、鏈路狀態定義、PDU定義、設備過濾機制等
LL
信道選擇。BLE將藍牙頻段分成了40個物理信道,綜合考慮(抗干擾等)后將其中三個作為廣播信道,頻段為0/12/39,編號是37-39
鏈路狀態。參與廣播的BLE設備,總是處于這三種狀態之一
Advertising:廣播狀態,周期性地廣播,數據發送方
Scanning:掃描狀態,掃描并接受廣播數據,數據接收方
Initiating:初始化狀態,掃描到可連接的廣播時,發起連接請求,連接發起方
PDU(Packet Data Unit)格式
pdu
Type是指PDU的類型,如不同的狀態下也有不同的消息類型,TxAdd和RxAdd都是地址類型flag,針對不同的type有不同的含義,RFU都是保留字段,Length標明payload的長度
Payload內容
State | Type | Descriptions | Payload | length | Descriptions |
---|---|---|---|---|---|
Advertising | ADV_IND | 常規廣播,可連接可掃描 | AdvA | 6 | address of broadcaster |
【后續建立點對點連接,監聽CONNECT_REQ請求】 | AdvData | 0~31 | Broadcast data | ||
ADV_NONCONN_IND | 同ADV_IND,不可連接不可掃描 | AdvA | 6 | address of broadcaster | |
【用于定時傳輸簡單數據】 | AdvData | 0~31 | Broadcast data | ||
ADV_SCAN_IND | 同ADV_IND,不可連接可掃描 | AdvA | 6 | address of broadcaster | |
【用于傳輸額外數據,監聽SCAN_REQ請求】 | AdvData | 0~31 | Broadcast data | ||
ADV_DIRECT_IND | 點對點連接,已知雙方藍牙地址,無廣播數據,可被指定設備連接不可掃描 | AdvA | 6 | address of broadcaster | |
【快速建立連接,不關心廣播數據,監聽CONNECT_REQ請求】 | InitA | 6 | address of receiver/initiater | ||
Scanning | SCAN_REQ | 接收ADV_IND/ADV_SCAN_IND后,請求更多信息 | ScanA | 6 | address of scanne r |
【接收廣播數據后請求更多信息】 | AdvA | 6 | address of broadcaster | ||
SCAN_RSP | SCAN_REQ的響應,返回更多信息 | 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設備地址類型
Static Device Address:上電生成,46-bit的random+11,斷電后可變
Private Device Address:進一步提供定時更新和地址加密提高可靠行和安全性
Non-resolvable Private Address:按周期定時更新,46-bit的random+00
Resolvable Private Address:通過隨機數和IRK(Identity Resolving Key)生成,24-bit的hash+22-bit的random+10
Public Device Address:IEEE分配,24-bit的company_id+24-bit的company_assigned,類似MAC
Random Device Address:隨機生成,解決費用和維護、設備身份綁定的問題
HCI
將Link Layer提供的功能封裝成Command/Event組
Command格式
command
OCF(Opcode Command Field)表示特定的HCI命令,OGF(Opcode Group Field)表示該HCI命令所屬組別,他們共同組成16位操作碼;Parameter Total Length表示所有參數總長度
所有BLE相關的HCI Command的OGF都是0x08
Event格式
event
這些Command/Event包括廣播、掃描、連接建立的相關操作,這些都可以通過hcitool命令進行測試
GAP
會從應用程序角度對各種狀態和操作再一次進行封裝,包括設備角色,通信的模式和操作的定義
與GAP廣播通信相關的是廣播和發現模式
Broadcast mode and observation procedure,廣播模式及對應的解析過程,對應狀態下的角色雙方就是Broadcaster和Observer
Discovery modes and procedures,發現模式及對應的發現過程,對應的角色就是Peripheral和Central
廣播數據格式
adv_data
廣播/掃描應答數據,包含有意義部分和無意義部分(補齊為0),有意義部分是由一個個廣播塊(AD Structure)組成,每個廣播塊包含1字節長度(指示數據部分長度)和剩下的數據部分,數據部分又分為數據類型和數據內容,數據類型會指示真實Data部分的內容,例如0x01表示Data內容是描述設備物理連接狀態,再例如0x08表示Data內容是設備名稱,更多可以參考generic-access-profile
舉個廣播數據的例子
0201060303aafe1716aafe00-10000102030405060708090a0b0e0f00000000 1
02 01 06是一個AD Structure,數據部分長度為2字節,類型是0x01,描述設備物理連接狀態,數據部分0x06,1字節8bit,每bit都是一個標志位([預留]|[預留]|[預留]|[同時支持BLE和BR/EDR(Host)]|[同時支持BLE和BR/EDR(Controller)]|[不支持BR/EDR]|[普通發現模式]|[有限發現模式]),那么這個廣播就是普通發現模式,不支持BR/EDR
03 03 aa fe是第二個AD Structure,數據部分長度為3字節,類型是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,數據部分長度為0x17即23字節,類型是0x16,表示服務數據
AD Type | Description | AD Data |
---|---|---|
0x01 | 設備物理連接狀態 | 1字節8bit,每個bit都是一個標志位 [預留]|[預留]|[預留]|[同時支持BLE和BR/EDR(Host)]|[同時支持BLE和BR/EDR(Controller)]|[不支持BR/EDR]|[普通發現模式]|[有限發現模式] |
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 | 設備名稱 | 縮寫設備名稱 |
0x09 | 設備名稱 | 完整設備名稱 |
0x0a | TX Power Level | TX Power Level |
0xff | 廠商數據 | [廠商ID]|[廠商自定義數據] |
Part707 BLE的連接
經典藍牙中保持連接非常耗費資源,但是每次連接建立效率又非常低,為了優化體驗,BLE簡化了連接過程(毫秒級),極大的降低了面向連接通信的代價
藍牙通信系統中,對于連接的定義是:在約定的時間段內,雙方都到一個指定的物理Channel上通信。
LL
角色定義。BLE為處于連接狀態的兩個設備定義了兩個角色,Master和Slave。Master作為連接發起方,定義和連接相關的參數,Slave是連接的接收方,請求連接參數
PDU(Packet Data Unit)格式
DC_PDU
面向連接的通信使用特定的PDU,稱為Data channel PDU
LLID指示Data Channel傳輸的PDU類型,傳輸數據是LL Data PDU,傳輸控制信息是LL Control PDU,NESN(Next Expected Sequence Number)和SN(Sequence Number)用于數據傳輸過程中的應答和流控,MD(More Data)用于關閉連接,RFU是預留位,Length指示有效數據長度,包括Payload和MIC
LLID | type | Description |
---|---|---|
01b | LL Data PDU | 空包或未傳輸完成的消息(被拆包) |
10b | LL Data PDU | (不需拆包)完整消息或第一個包 |
11b | LL Control PDU | 用于控制、管理LL連接的數據包,此時Payload為1字節Opcode和剩余的控制數據 |
建立連接
可連接狀態的設備(Advertiser)按照一定周期廣播可連接數據包
主動連接的設備(Initiator)收到廣播包后回應一個連接請求(約定時間點、物理信道等)
Initiator發送完畢后進入連接狀態,成為Master
Advertiser接收到連接請求后也進入連接狀態,成為Slave
雙方按照參數約定,定時切換到某一物理信道,開始依次收發數據,直至連接斷開
HCI
封裝Link Layer的功能,主要包括連接的建立、關閉、參數設置和管理,以及數據封裝和轉發
GAP
定義設備具有的能力和操作
Part808 BLE安全機制
White List
白名單就是一組藍牙地址列表,通過設置白名單可以允許掃描、連接特定的藍牙設備,以及被掃描、連接
LL Privacy
在白名單的基礎上將設備地址進行加密,轉變成Resolvable Private addresses
本地Resolving List需要保存每一對BLE設備的key/address信息,格式為:Local IRK|Peer IRK|Peer Device Identity Address|Address Type
LL Privacy機制在Controller中完成,即加解密操作對HCI上層是透明的
LL Encryption
數據發送和接收過程進行加解密
加解密操作在Link Layer完成
Host會保存至少1280bit的LTK(根密鑰),啟動加密傳輸時,LL會首先協商出一個128-bit的隨機數SDK(Session Key Diversifier)和64-bit的iv,經過Encryption Engine和LTK生成此次通信的會話密鑰SessionKey
SecurityManager
為BLE設備提供加密連接相關的key,包含以下規范:
定義了配對(Pairing)的相關概念及過程
定義了密碼工具箱,包含配對、加密所需的各種算法
定義了安全管理協議(Security Manager Protocol,SMP),實現配對、密碼傳輸等操作
SMP規范中,配對的定義是,Master和Slave通過協商確定用于加密通信的key的過程,包含三個階段:
階段一、Pairing Feature Exchange,交換雙方配對相關的設置,如配對方法、鑒權方式等
鑒權方式可以采用交換額外的信息(Out of band,OOB),或者彈窗引入人來判定,稱為MITM鑒權,還有輸入配對碼進行對比(Passkey Entry/Numeric Comparison),或者不鑒權(Just Work)
階段二、通過SMP協議進行配對操作,有兩種配對方案,LE Legacy Pairing和LE Secure Connections
階段三、協商好密鑰key,建立加密連接,傳輸密鑰信息(可選)
Part909 Android中的BLE應用
具體可以參考開發的藍牙測試工具:BLETool
BLE,藍牙低功耗(極低的運行和待機功耗)
Android 4.3(API 18) 開始引入 BLE ,即藍牙4.0
Android 4.3 的 BLE 只支持 Central Role(中心設備,掃描并連接外圍設備)
Android 5.0 開始同時支持 Central Role 和 Peripheral Role(外圍設備,向外廣播發送數據)
1、權限
2、開啟/關閉藍牙
//判斷支持藍牙功能 if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){ //獲取藍牙管理服務 BluetoothManagerbluetoothManager=(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); //獲取藍牙適配器 BluetoothAdapterbluetoothAdapter=bluetoothManager.getAdapter(); if(bluetoothAdapter!=null){ //判斷藍牙是否開啟 if(bluetoothAdapter.isEnabled()){ //關閉藍牙 bluetoothAdapter.disable(); }else{ //1、靜默開啟藍牙 bluetoothAdapter.enable(); //2、顯式請求開啟藍牙 Intentintent=newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent,REQUEST_BLUETOOTH_ENABLE); } } }
3、掃描與監聽
if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){ //獲取藍牙管理服務 BluetoothManagerbluetoothManager=(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); //獲取藍牙適配器 BluetoothAdapterbluetoothAdapter=bluetoothManager.getAdapter(); if(bluetoothAdapter!=null){ //判斷藍牙是否開啟 if(bluetoothAdapter.isEnabled()){ //獲取掃描器實例 BluetoothLeScannerbluetoothLeScanner=bluetoothAdapter.getBluetoothLeScanner(); booleanisScanning=false; if(bluetoothLeScanner!=null){ if(isScanning){ //停止掃描 isScanning=false; bluetoothLeScanner.stopScan(scanCallback); }else{ //開始掃描 isScanning=true; bluetoothLeScanner.startScan(scanCallback); } } } } }
Android 8 開始提供一個后臺持續掃描的API,應用殺死后也可以繼續掃描,直到關閉藍牙【待驗證】
publicintstartScan(Listfilters,ScanSettingssettings,PendingIntentcallbackIntent); //設置攔截器和掃描選項 bluetoothLeScanner.startScan(scanFilters,scanSettings,scanCallback);
初始化掃描過濾器
scanFilters=newArrayList<>(); ScanFilterscanFilter=newScanFilter.Builder() .setDeviceName("lalala") .setServiceUuid(newParcelUuid(UUID.randomUUID())) .build(); scanFilters.add(scanFilter);
初始化掃描設置
scanSettings=newScanSettings.Builder() //設置掃描模式 .setScanMode(ScanSettings.SCAN_MODE_BALANCED) //設置回調類型 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) //設置配對模式 .setMatchMode(ScanSettings.MATCH_MODE_STICKY) //設置報告延遲 .setReportDelay(0) .build();
兩個類都是通過 Builder 構造,提供系列函數用于參數設置,如 setDeviceName()、setScanMode()、setMatchMode() 等
4、掃描回調
scanCallback=newScanCallback{ @Override publicvoidonScanResult(intcallbackType,ScanResultresult){ super.onScanResult(callbackType,result); } @Override publicvoidonScanFailed(interrorCode){ super.onScanFailed(errorCode); } @Override publicvoidonBatchScanResults(Listresults){ super.onBatchScanResults(results); } }
其中 onBatchScanResults() 是批量返回掃描結果。可通過下面的接口判斷藍牙芯片是否支持批處理
Bluetoothadapter.isOffloadedScanBatchingSupported(); 1
注意 onScanResult() 和 onBatchScanResults() 是互斥的,ScanSettings 中 setReportDelay() 設置為0(默認)則通過 onScanResult() 返回掃描結果,否則開啟批處理掃描模式,并觸發 onBatchScanResults() 回調。
5、廣播數據解析
掃描成功會返回 ScanResult 廣播數據類,然后進一步解析
//返回遠程設備類 BluetoothDevicedevice=scanResult.getDevice(); //返回掃描記錄,包含廣播和掃描響應 ScanRecordscanRecord=scanResult.getScanRecord(); //返回信號強度,[-127,126] intrssi=scanResult.getRssi()
BluetoothDevice 是設備信息類,常用的方法有
//獲取硬件地址 Stringaddress=device.getAddress(); //獲取藍牙名稱 Stringname=device.getName(); //獲取設備類型,如DEVICE_TYPE_CLASSIC、DEVICE_TYPE_LE、DEVICE_TYPE_DUAL、DEVICE_TYPE_UNKNOWN inttype=device.getType(); //獲取綁定狀態,如BOND_NONE、BOND_BONDING、BOND_BONDED intstate=device.getBondState();
6、連接設備
掃描返回的廣播消息中可以獲取到遠程設備的MAC地址,可用于設備的連接
if(bluetoothAdapter.isEnabled()){ //獲取遠程設備對象 BluetoothDevicebluetoothDevice=bluetoothAdapter.getRemoteDevice(address); if(bluetoothDevice!=null){ handler.post(newRunnable(){ @Override publicvoidrun(){ BluetoothGattbluetoothGatt; if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){ //連接遠程設備 bluetoothGatt=bluetoothDevice.connectGatt(context,false,bluetoothGattCallback,BluetoothDevice.TRANSPORT_LE); }else{ bluetoothGatt=bluetoothDevice.connectGatt(context,false,bluetoothGattCallback); } } }); } }
bluetoothGatt 是藍牙通用屬性協議的封裝,定義了BLE通信的一些基本規則和連接通信操作
7、連接回調
bluetoothGattCallback 則是 bluetoothGatt 連接的回調類,通知客戶端連接狀態和結果
bluetoothGattCallback=newBluetoothGattCallback{ @Override publicvoidonConnectionStateChange(BluetoothGattgatt,finalintstatus,finalintnewState){ super.onConnectionStateChange(gatt,status,newState); } @Override publicvoidonServicesDiscovered(BluetoothGattgatt,finalintstatus){ super.onServicesDiscovered(gatt,status); } @Override publicvoidonCharacteristicChanged(BluetoothGattgatt,finalBluetoothGattCharacteristiccharacteristic){ super.onCharacteristicChanged(gatt,characteristic); } @Override publicvoidonCharacteristicRead(BluetoothGattgatt,finalBluetoothGattCharacteristiccharacteristic,finalintstatus){ super.onCharacteristicRead(gatt,characteristic,status); } @Override publicvoidonCharacteristicWrite(BluetoothGattgatt,finalBluetoothGattCharacteristiccharacteristic,finalintstatus){ super.onCharacteristicWrite(gatt,characteristic,status); } @Override publicvoidonDescriptorRead(BluetoothGattgatt,finalBluetoothGattDescriptordescriptor,finalintstatus){ super.onDescriptorRead(gatt,descriptor,status); } @Override publicvoidonDescriptorWrite(BluetoothGattgatt,finalBluetoothGattDescriptordescriptor,finalintstatus){ super.onDescriptorWrite(gatt,descriptor,status); } }
連接成功及其他連接狀態改變都會調用 onConnectionStateChange() 方法。status 表示這個操作的狀態,是 BluetoothGatt.GATT_SUCCESS 或者讀寫受限、超過范圍等其他錯誤狀態。newState 則表示當前設備的連接狀態,連接成功為 BluetoothProfile.STATE_CONNECTED,連接失敗是BluetoothProfile.STATE_DISCONNECTED。
8、發現服務
連接成功后就可以開始通信,從請求服務開始(Profile只是一系列具有共同業務需求的服務的抽象集合,服務才是實體)
bluetoothGatt.discoverServices(); 1
發現服務后會觸發 onServicesDiscovered() 回調,然后繼續獲取服務
//獲取所有服務 ListbleServiceList=bluetoothGatt.getServices(); //通過uuid獲取特定的服務 BluetoothGattServicebleService=bluetoothGatt.getService(serviceUuid);
BluetoothGattService 是藍牙服務類,是與某個場景相關的一系列行為的抽象,具有一個唯一的UUID,然后服務類型,如SERVICE_TYPE_PRIMARY、SERVICE_TYPE_SECONDARY(主要服務可以包含二級服務),包含的特征列表
9、獲取特征
//獲取所有特征 ListbleCharacteristicList=bleService.getCharacteristics(); //通過uuid獲取特定的特征 BluetoothGattCharacteristicbleCharacteristic=bleService.getCharacteristic(characteristicUuid);
BluetoothGattCharacteristic 是藍牙特征類,是通信的基本數據單位,包含標志特征的唯一的UUID,描述特征訪問權限的特性,如PROPERTY_BROADCAST、PROPERTY_READ、PROPERTY_WRITE等,特征的實際取值,以及特征的描述
10、讀寫特征
//讀取特征 bluetoothGatt.readCharacteristic(bleCharacteristic); //設置并寫入特征 bleCharacteristic.setValue("XXX"); bluetoothGatt.writeCharacteristic(bleCharacteristic);
這里的讀寫特征函數都是返回布爾類型表示是否操作成功,如果成功真正的值會在 onCharacteristicRead/onCharacteristicWrite 回調中讀取/寫入
@Override publicvoidonCharacteristicRead(BluetoothGattgatt,BluetoothGattCharacteristiccharacteristic,intstatus){ switch(status){ caseGATT_SUCCESS: StringvalueStr=BLEUtils.byte2HexString(characteristic.getValue()); break; caseGATT_READ_NOT_PERMITTED: ToastUtils.showShort(context,"GATT_READ_NOT_PERMITTED"); break; default: ToastUtils.showShort(context,"CHARACTERISTIC_READ_FAILED"); break; } } @Override publicvoidonCharacteristicWrite(BluetoothGattgatt,BluetoothGattCharacteristiccharacteristic,intstatus){ switch(status){ caseGATT_SUCCESS: StringvalueStr=BLEUtils.byte2HexString(characteristic.getValue()); break; default: ToastUtils.showShort(context,"CHARACTERISTIC_WRITE_FAILED"); break; } }
11、監聽特征
真正要實現通信除了單方面讀寫,還需要對數據變化進行監聽,這樣就可以進行數據交換
//設置特征監聽為true,且要求特征具有NOTIFY屬性 bluetoothGatt.setCharacteristicNotification(characteristic,true);
這樣,自己或對方特征改變時就會回調函數從而獲取改變后的特征值
@Override publicvoidonCharacteristicChanged(BluetoothGattCharacteristiccharacteristic){ Log.d("bledemo","uuid="+characteristic.getUuid().toString()); Log.d("bledemo","value=0x"+BLEUtils.byte2HexString(characteristic.getValue())); }
12、獲取描述
//獲取所有描述 ListbleDescriptorList=bleCharacteristic.getDescriptors(); //通過uuid獲取特定的描述 BluetoothGattDescriptorbleDescriptor=bleCharacteristic.getDescriptor(descriptorUuid);
BluetoothGattDescriptor 是藍牙特征描述類,包含對特征的一些額外描述信息
13、讀寫描述
//讀取描述 bluetoothGatt.readDescriptor(bleDescriptor); //設置并寫入描述 bleDescriptor.setValue("XXX"); bluetoothGatt.writeDescriptor(bleDescriptor);
同樣讀寫成功會觸發onDescriptorRead/onDescriptorWrite 回調
@Override publicvoidonDescriptorRead(BluetoothGattDescriptordescriptor,intstatus){ switch(status){ caseGATT_SUCCESS: StringvalueStr=BLEUtils.byte2HexString(descriptor.getValue()); break; caseGATT_READ_NOT_PERMITTED: ToastUtils.showShort(context,"GATT_READ_NOT_PERMITTED"); break; default: ToastUtils.showShort(context,"DESCRIPTOR_READ_FAILED"); break; } } @Override publicvoidonDescriptorWrite(BluetoothGattDescriptordescriptor,intstatus){ switch(status){ caseGATT_SUCCESS: StringvalueStr=BLEUtils.byte2HexString(descriptor.getValue()); break; default: ToastUtils.showShort(context,"DESCRIPTOR_WRITE_FAILED"); break; } }
14、斷開連接
//斷開連接,會觸發onConnectionStateChange()回調 bluetoothGatt.disconnect(); //關閉連接,不會觸發回調 bluetoothGatt.close(); 1234
15、開啟/關閉廣播
if(bluetoothAdapter.isEnabled()){ //設置廣播設備的名稱,方便搜索 bluetoothAdapter.setName("XXX"); //獲取廣播類 BluetoothLeAdvertiserbluetoothLeAdvertiser=bluetoothAdapter.getBluetoothLeAdvertiser(); if(bluetoothLeAdvertiser!=null){ if(isAdvertising){ //關閉廣播 bluetoothLeAdvertiser.stopAdvertising(advertiseCallback); }else{ //開始廣播 bluetoothLeAdvertiser.startAdvertising(advertiseSetting,advertiseData,advertiseCallback); } } }
還可以發送帶響應報文的廣播包
bluetoothLeAdvertiser.startAdvertising(advertiseSetting,advertiseData,advertiseResData,advertiseCallback); 1
其中 advertiseSetting 為廣播設置類對象
advertiseSetting=newAdvertiseSettings.Builder() //廣播模式,控制廣播功率和延遲 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //廣播發射功率級別 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //廣播超時時間,最大值為3*60*1000毫秒,為0時禁用超時,默認無限廣播 .setTimeout(advertiseTimeout) //廣播連接類型 .setConnectable(true) .build(); 12345678910
advertiseData、advertiseResData 為廣播包
advertiseData=newAdvertiseData.Builder() //廣播是否包含設備名稱 .setIncludeDeviceName(true) //廣播是否包含發射功率 .setIncludeTxPowerLevel(true) //添加服務uuid .addServiceUuid(newParcelUuid(UUID.randomUUID())) .build(); advertiseResData=newAdvertiseData.Builder() //添加自定義服務數據 .addServiceData(newParcelUuid(UUID.randomUUID()),newbyte[]{1,2,3,4}) //添加自定義廠商數據 .addManufacturerData(0x06,newbyte[]{5,6,7,8}) .build();
16、廣播回調
advertiseCallback 是廣播回調
advertiseCallback=newAdvertiseCallback{ @Override publicvoidonStartSuccess(AdvertiseSettingssettingsInEffect){ super.onStartSuccess(settingsInEffect); } @Override publicvoidonStartFailure(interrorCode){ super.onStartFailure(errorCode); } }
17、啟動GATT服務
只有廣播仍然不夠,作為外圍角色的設備還需要啟動GATT服務,等待中心設備與之建立連接之后就可以通過服務通信
//啟動Gatt服務 bluetoothGattServer=bluetoothManager.openGattServer(context,bluetoothGattServerCallback); 12
接下來可以向啟動的GATT服務中添加Service
//構造服務 BluetoothGattServicebluetoothGattService=newBluetoothGattService(UUID.randomUUID(),BluetoothGattService.SERVICE_TYPE_PRIMARY); //構造特征 BluetoothGattCharacteristicbluetoothGattCharacteristic=newBluetoothGattCharacteristic(UUID.randomUUID(),BluetoothGattCharacteristic.PROPERTY_READ|BluetoothGattCharacteristic.PERMISSION_WRITE|BluetoothGattCharacteristic.PROPERTY_NOTIFY,BluetoothGattCharacteristic.PERMISSION_WRITE|BluetoothGattCharacteristic.PERMISSION_READ); bluetoothGattCharacteristic.setValue("character_test_value"); //構造描述 BluetoothGattDescriptorbluetoothGattDescriptor=newBluetoothGattDescriptor(UUID.randomUUID(),BluetoothGattDescriptor.PERMISSION_READ|BluetoothGattDescriptor.PERMISSION_WRITE); bluetoothGattDescriptor.setValue("descriptor_test_value".getBytes()); //添加描述到特征中 bluetoothGattCharacteristic.addDescriptor(bluetoothGattDescriptor); //添加特征到服務中 bluetoothGattService.addCharacteristic(bluetoothGattCharacteristic); //添加服務 bluetoothGattServer.addService(bluetoothGattService);
18、GATT服務回調
bluetoothGattServerCallback 是GATT服務的回調,當設備被連接、通信(讀寫特征)時都會觸發響應的回調函數
bluetoothGattServerCallback=newBluetoothGattServerCallback{ @Override publicvoidonConnectionStateChange(BluetoothDevicedevice,intstatus,intnewState){ super.onConnectionStateChange(device,status,newState); } @Override publicvoidonServiceAdded(intstatus,BluetoothGattServiceservice){ super.onServiceAdded(status,service); } @Override publicvoidonCharacteristicReadRequest(BluetoothDevicedevice,intrequestId,intoffset, BluetoothGattCharacteristiccharacteristic){ super.onCharacteristicReadRequest(device,requestId,offset,characteristic); } @Override publicvoidonCharacteristicWriteRequest(BluetoothDevicedevice,intrequestId, BluetoothGattCharacteristiccharacteristic, booleanpreparedWrite,booleanresponseNeeded, intoffset,byte[]value){ super.onCharacteristicWriteRequest(device,requestId,characteristic,preparedWrite, responseNeeded,offset,value); } @Override publicvoidonDescriptorReadRequest(BluetoothDevicedevice,intrequestId, intoffset,BluetoothGattDescriptordescriptor){ super.onDescriptorReadRequest(device,requestId,offset,descriptor); } @Override publicvoidonDescriptorWriteRequest(BluetoothDevicedevice,intrequestId, BluetoothGattDescriptordescriptor, booleanpreparedWrite,booleanresponseNeeded, intoffset,byte[]value){ super.onDescriptorWriteRequest(device,requestId,descriptor,preparedWrite,responseNeeded, offset,value); } @Override publicvoidonNotificationSent(BluetoothDevicedevice,intstatus){ super.onNotificationSent(device,status); }
審核編輯:劉清
-
藍牙技術
+關注
關注
45文章
342瀏覽量
52941 -
BLE協議棧
+關注
關注
0文章
5瀏覽量
4766 -
EDR
+關注
關注
0文章
23瀏覽量
1996
發布評論請先 登錄
相關推薦
評論