藍牙低功耗BLE調(diào)研與開發(fā)

藍牙低功耗BLE調(diào)研與開發(fā)

一: 藍牙簡介

藍牙是一種近距離無線通信技術棺亭,運行在2.4GHz免費頻段穿铆,它的特性就是近距離通信彰触,典型距離是 10 米以內(nèi)都许,傳輸速度最高可達 24 Mbps,支持多連接押桃,安全性高葵萎,非常適合用智能設備上。

1. 藍牙技術的發(fā)展

1999年 藍牙1.0:

早期的藍牙 1.0 A 和 1.0B 版存在多個問題唱凯,有多家廠商指出他們的產(chǎn)品互不兼容羡忘。
同時,在兩個設備“鏈接”(Handshaking)的過程中磕昼,藍牙硬件的地址(BD_ADDR)會被發(fā)送出去卷雕,在協(xié)議的層面上不能做到匿名,造成泄漏數(shù)據(jù)的危險掰烟。
因此爽蝴,當 1.0 版本推出以后沐批,藍牙并未立即受到廣泛的應用纫骑。除了當時對應藍牙功能的電子設備種類少,藍牙裝置也十分昂貴九孩。

2001年 藍牙1.1版本:

藍牙 1.1 版正式列入 IEEE 802.15.1 標準先馆,該標準定義了物理層(PHY)和媒體訪問控制(MAC)規(guī)范,用于設備間的無線連接躺彬,傳輸率為 0.7Mbps煤墙。但因為是早期設計,容易受到同頻率之間產(chǎn)品干擾宪拥,影響通訊質(zhì)量仿野。

2003年:藍牙 1.2

藍牙 1.2 版針對 1.0 版本暴露出的安全性問題,完善了匿名方式她君,新增屏蔽設備的硬件地址(BD_ADDR)功能脚作,保護用戶免受身份嗅探攻擊和跟蹤,同時向下兼容 1.1 版缔刹。此外球涛,還增加了四項新功能:
(1)AFH(Adaptive Frequency Hopping)適應性跳頻技術,減少了藍牙產(chǎn)品與其它無線通訊裝置之間所產(chǎn)生的干擾問題校镐;
(2)eSCO(Extended Synchronous Connection-Oriented links)延伸同步連結導向信道技術亿扁,用于提供 QoS 的音頻傳輸,進一步滿足高階語音與音頻產(chǎn)品的需求鸟廓;
(3)Faster Connection 快速連接功能从祝,可以縮短重新搜索與再連接的時間襟己,使連接過程更為穩(wěn)定快速;
(4)支持 Stereo 音效的傳輸要求牍陌,但只能以單工方式工作稀蟋。

2004年:藍牙 2.0

藍牙 2.0 是 1.2 版本的改良版,新增的 EDR(Enhanced Data Rate)技術通過提高多任務處理和多種藍牙設備同時運行的能力呐赡,
使得藍牙設備的傳輸率可達 3Mbps退客。
藍牙 2.0 支持雙工模式:可以一邊進行語音通訊,一邊傳輸文檔/高質(zhì)素圖片链嘀。
同時萌狂,EDR 技術通過減少工作負債循環(huán)來降低功耗,由于帶寬的增加怀泊,藍牙 2.0 增加了連接設備的數(shù)量茫藏。

2007年:藍牙 2.1

藍牙 2.1 新增了 Sniff Subrating 省電功能,將設備間相互確認的訊號發(fā)送時間間隔從舊版的 0.1 秒延長到 0.5 秒左右霹琼,從而讓藍牙芯片的工作負載大幅降低务傲。
另外,新增 SSP 簡易安全配對功能枣申,改善了藍牙設備的配對體驗售葡,同時提升了使用和安全強度。
支持 NFC 近場通信忠藤,只要將兩個內(nèi)置有 NFC 芯片的藍牙設備相互靠近挟伙,配對密碼將通過 NFC 進行傳輸,無需手動輸入模孩。

2009 年:藍牙 3.0

藍牙 3.0 新增了可選技術 High Speed尖阔,High Speed 可以使藍牙調(diào)用 802.11 WiFi 用于實現(xiàn)高速數(shù)據(jù)傳輸,傳輸率高達 24Mbps榨咐,是藍牙 2.0 的 8 倍介却,輕松實現(xiàn)錄像機至高清電視、PC 至 PMP块茁、UMPC 至打印機之間的資料傳輸齿坷。
藍牙 3.0 的核心是 AMP(Generic Alternate MAC/PHY),這是一種全新的交替射頻技術龟劲,允許藍牙協(xié)議棧針對任一任務動態(tài)地選擇正確射頻胃夏。
功耗方面,藍牙 3.0 引入了 EPC 增強電源控制技術昌跌,再輔以 802.11仰禀,實際空閑功耗明顯降低。
此外蚕愤,新的規(guī)范還加入 UCD 單向廣播無連接數(shù)據(jù)技術答恶,提高了藍牙設備的相應能力饺蚊。

2010 年:藍牙 4.0

藍牙 4.0 是迄今為止第一個藍牙綜合協(xié)議規(guī)范,將三種規(guī)格集成在一起悬嗓。其中最重要的變化就是 BLE(Bluetooth Low Energy)低功耗功能污呼,提出了低功耗藍牙、傳統(tǒng)藍牙和高速藍牙三種模式:
”高速藍牙“主攻數(shù)據(jù)交換與傳輸包竹;“傳統(tǒng)藍牙”則以信息溝通燕酷、設備連接為重點;”低功耗藍牙“以不需占用太多帶寬的設備連接為主周瞎,功耗較老版本降低了 90%苗缩。
BLE 前身是 NOKIA 開發(fā)的 Wibree 技術,本是作為一項專為移動設備開發(fā)的極低功耗的移動無線通信技術声诸,在被 SIG 接納并規(guī)范化之后重命名為 Bluetooth Low Energy(后簡稱低功耗藍牙)酱讶。這三種協(xié)議規(guī)范還能夠互相組合搭配、從而實現(xiàn)更廣泛的應用模式彼乌。
藍牙 4.0 的芯片模式分為 Single mode 與 Dual mode泻肯。Single mode 只能與藍牙 4.0 互相傳輸無法向下與 3.0/2.1/2.0 版本兼容;Dual mode 可以向下兼容 3.0/2.1/2.0 版本慰照。前者應用于使用紐扣電池的傳感器設備灶挟,例如對功耗要求較高的心率檢測器和溫度計;后者應用于傳統(tǒng)藍牙設備焚挠,同時兼顧低功耗的需求膏萧。
此外漓骚,藍牙 4.0 還把藍牙的傳輸距離提升到100米以上(低功耗模式條件下)蝌衔。擁有更快的響應速度,最短可在 3 毫秒內(nèi)完成連接設置并開始傳輸數(shù)據(jù)蝌蹂。更安全的技術噩斟,使用 AES-128 CCM 加密算法進行數(shù)據(jù)包加密和認證。

2013 年:藍牙 4.1

藍牙 4.1 在傳輸速度和傳輸范圍上變化很小孤个,但在軟件方面有著明顯的改進剃允。此次更新目的是為了讓 Bluetooth Smart 技術最終成為物聯(lián)網(wǎng)(Internet of Things)發(fā)展的核心動力。
支持與 LTE 無縫協(xié)作齐鲤。當藍牙與 LTE 無線電信號同時傳輸數(shù)據(jù)時斥废,那么藍牙 4.1 可以自動協(xié)調(diào)兩者的傳輸信息,以確保協(xié)同傳輸给郊,降低相互干擾牡肉。
允許開發(fā)人員和制造商「自定義」藍牙 4.1 設備的重新連接間隔,為開發(fā)人員提供了更高的靈活性和掌控度淆九。
支持「云同步」统锤。藍牙 4.1 加入了專用的 IPv6 通道毛俏,藍牙 4.1 設備只需要連接到可以聯(lián)網(wǎng)的設備(如手機),就可以通過 IPv6 與云端的數(shù)據(jù)進行同步饲窿,滿足物聯(lián)網(wǎng)的應用需求煌寇。
支持「擴展設備」與「中心設備」角色互換。支持藍牙 4.1 標準的耳機逾雄、手表阀溶、鍵鼠,可以不用通過 PC鸦泳、平板淌哟、手機等數(shù)據(jù)樞紐,實現(xiàn)自主收發(fā)數(shù)據(jù)辽故。例如智能手表和計步器可以繞過智能手機徒仓,直接實現(xiàn)對話。

2014 年:藍牙 4.2

藍牙 4.2 的傳輸速度更加快速誊垢,比上代提高了 2.5 倍掉弛,因為藍牙智能(Bluetooth Smart)數(shù)據(jù)包的容量提高,其可容納的數(shù)據(jù)量相當于此前的10倍左右喂走。
改善了傳輸速率和隱私保護程度殃饿,藍牙信號想要連接或者追蹤用戶設備,必須經(jīng)過用戶許可芋肠。用戶可以放心使用可穿戴設備而不用擔心被跟蹤乎芳。
支持 6LoWPAN,6LoWPAN 是一種基于 IPv6 的低速無線個域網(wǎng)標準帖池。藍牙 4.2 設備可以直接通過 IPv6 和 6LoWPAN 接入互聯(lián)網(wǎng)奈惑。這一技術允許多個藍牙設備通過一個終端接入互聯(lián)網(wǎng)或者局域網(wǎng),這樣睡汹,大部分智能家居產(chǎn)品可以拋棄相對復雜的 WiFi 連接酷愧,改用藍牙傳輸馁龟,讓個人傳感器和家庭間的互聯(lián)更加便捷快速承桥。

2016 年:藍牙 5.0

藍牙 5.0 在低功耗模式下具備更快更遠的傳輸能力柒爵,傳輸速率是藍牙 4.2 的兩倍(速度上限為 2Mbps),有效傳輸距離是藍牙 4.2 的四倍(理論上可達 300 米)彤叉,數(shù)據(jù)包容量是藍牙 4.2 的八倍庶柿。
支持室內(nèi)定位導航功能,結合 WiFi 可以實現(xiàn)精度小于 1 米的室內(nèi)定位秽浇。
針對 IoT 物聯(lián)網(wǎng)進行底層優(yōu)化浮庐,力求以更低的功耗和更高的性能為智能家居服務。

  • 藍牙版本總結
  • 2007年發(fā)布的2.1版本兼呵,是之前使用最廣的兔辅,也是我們所謂的經(jīng)典藍牙腊敲。
  • 2009年推出藍牙 3.0版本,也就是所謂的高速藍牙维苔,傳輸速率理論上可高達24 Mbit/s碰辅;
  • 2010年推出藍牙4.0版本,它是相對之前版本的集大成者介时,它包括經(jīng)典藍牙没宾、高速藍牙和藍牙低功耗協(xié)議。
    經(jīng)典藍牙包括舊有藍牙協(xié)議沸柔,高速藍牙基于Wi-Fi循衰,低功耗藍牙就是BLE。
  • 2016年藍牙技術聯(lián)盟提出了新的藍牙技術標準褐澎,即藍牙5.0版本会钝。
    藍牙5.0針對低功耗設備速度有相應提升和優(yōu)化,結合wifi對室內(nèi)位置進行輔助定位工三,
    提高傳輸速度迁酸,增加有效工作距離,主要是針對物聯(lián)網(wǎng)方向的改進俭正。

2. Android版本中藍牙簡介

  • Android1.5 中增加了藍牙功能奸鬓,立體聲 Bluetooth 支持:A2DP [Advanced Audio Distribution Profile]、AVCRP [Audio/Video Remote Control Profile]掸读,自動配對串远。
  • Android2.0 中支持Bluetooth2.1協(xié)議。
  • Android3.0 中能讓應用查詢已經(jīng)連接上 Bluetooth 設備的 Bluetooth Profile儿惫、音頻狀態(tài)等澡罚,然后通知用戶。
  • Android3.1 中系統(tǒng)可以通過 Bluetooth HID 方式同時接入一到多款輸入設備姥闪。
  • Android4.0 中新增支持連接 Bluetooth HDP [Health Device Profile)] 設備始苇,通過第三方應用的支持,用戶可以連接到醫(yī)院筐喳、健身中心或者家庭等場合中的無線醫(yī)療設備和傳感器。
  • Android4.2 中引入了一種新的針對 Android 設備優(yōu)化的 Bluetooth 協(xié)議棧 BlueDroid函喉,從而取代 BlueZ 協(xié)議棧避归。Bluedroid 協(xié)議棧由 Google 和 Broadcom 公司共同開發(fā),相對于 BlueZ 協(xié)議棧管呵,BlueDroid 提升了兼容性和可靠性梳毙。
  • Android4.3 中增加了對低功耗藍牙的支持,內(nèi)置支持 Bluetooth AVRCP 1.3捐下,基于 Google 和 Broadcom 公司功能研發(fā)的針對于 Android 設備優(yōu)化的新的藍牙協(xié)議棧 BlueDroid账锹。
  • Android4.4 中新增兩種新 Proifle 支持:HID [Human Interface Device]萌业、MAP [Message Access Profile]
  • Android5.0 中支持Bluetooth4.1協(xié)議。
  • Android6.0 中掃描藍牙需要動態(tài)獲取定位才行奸柬。
  • Android7.0 中支持Bluetooth4.2協(xié)議生年。
  • Android8.0 中支持Bluetooth5.0協(xié)議,強化了藍牙音頻的表現(xiàn)廓奕。比如編碼/傳輸格式可選SBC抱婉、AAC、aptX/aptX HD桌粉、LDAC等四種蒸绩,音質(zhì)依次提高。
  • Android10.0 中支持Bluetooth5.1協(xié)議铃肯,在5.0的基礎上患亿,增加了側(cè)向功能和厘米級定位服務,大幅度提高了定位精度押逼。使室內(nèi)定位更精準窍育。
  • Android11.0 中支持Bluetooth5.2協(xié)議,增強版ATT協(xié)議宴胧,LE功耗控制和信號同步漱抓,連接更快,更穩(wěn)定恕齐,抗干擾性更好乞娄。
  • Android12.0 中支持Bluetooth5.3協(xié)議,增強了經(jīng)典藍牙BR/EDR(基礎速率和增強速率)的安全性显歧。藍牙5.3的延遲更低仪或、抗干擾性更強、提升了電池續(xù)航時間士骤。系統(tǒng)引入了新的運行時權限 BLUETOOTH_SCAN范删、BLUETOOTH_ADVERTISE 和 BLUETOOTH_CONNECT權限,用于更好地管理應用于附近藍牙設備的連接拷肌。
  • Android13.0 中引入LE Audio支持到旦,LE Audio 是藍牙技術聯(lián)盟(SIG Q) 在 2020 年國際消費電子展上推出的新一代藍牙低功耗音頻技術,能以藍牙低功耗狀態(tài)下傳遞音頻有利于提升藍牙耳機續(xù)航力和性能巨缘,同時也導入新世代藍牙音頻編碼 LC3(Low Complexity Communication Codec)添忘,以及多種音頻分享、廣播音頻模式等若锁,并且有利于助聽器產(chǎn)品搁骑,能夠達到低延遲、高音質(zhì)等功能。
    藍牙低功耗音頻LE Audio 算是藍牙技術聯(lián)盟發(fā)展 20 年全新的音頻技術仲器,中間歷經(jīng)8年發(fā)展與兩次核心規(guī)格更新煤率,也算是有史以來最大型開發(fā)項目,能夠分許開發(fā)者通過23種不同文件配置與服務規(guī)范乏冀,開發(fā)出藍牙高音質(zhì)蝶糯、新拓樸結構和省電音頻裝置。
  • 目前最新的藍牙協(xié)議是藍牙5.3版本(截止到2023年9月22日)
  • Android 4.3 開始煤辨,開始支持BLE功能裳涛,但只支持Central(中心角色or主機)
  • Android 5.0開始,開始支持Peripheral(外設角色or從機)

中心模式和外設模式是什么意思众辨?

  • Central Mode: Android端作為中心設備端三,連接其他外圍設備。
  • Peripheral Mode:Android端作為外圍設備鹃彻,被其他中心設備連接郊闯。在Android 5.0支持外設模式之后,才算實現(xiàn)了兩臺Android手機通過BLE進行相互通信蛛株。

3. 藍牙的廣播和掃描

關于這部分內(nèi)容团赁,需要引入一個概念,GAP(Generic Access Profile)谨履,它用來控制設備連接和廣播欢摄。GAP 使你的設備被其他設備可見,并決定了你的設備是否可以或者怎樣與設備進行交互笋粟。例如 Beacon 設備就只是向外發(fā)送廣播怀挠,不支持連接;小米手環(huán)就可以與中心設備建立連接害捕。

在 GAP 中藍牙設備可以向外廣播數(shù)據(jù)包绿淋,廣播包分為兩部分: Advertising Data Payload(廣播數(shù)據(jù))和 Scan Response Data Payload(掃描回復),每種數(shù)據(jù)最長可以包含 31 byte尝盼。這里廣播數(shù)據(jù)是必需的吞滞,因為外設必需不停的向外廣播,讓中心設備知道它的存在盾沫。掃描回復是可選的裁赠,中心設備可以向外設請求掃描回復,這里包含一些設備額外的信息疮跑,例如設備的名字组贺。在 Android 中,系統(tǒng)會把這兩個數(shù)據(jù)拼接在一起祖娘,返回一個 62 字節(jié)的數(shù)組。這些廣播數(shù)據(jù)可以自己手動去解析,在 Android 5.0 也提供 ScanRecord 幫你解析渐苏,直接可以通過這個類獲得有意義的數(shù)據(jù)掀潮。廣播中可以有哪些數(shù)據(jù)類型呢?設備連接屬性琼富,標識設備支持的 BLE 模式仪吧,這個是必須的。設備名字鞠眉,設備包含的關鍵 GATT service薯鼠,或者 Service data,廠商自定義數(shù)據(jù)等等械蹋。

[圖片上傳失敗...(image-d143d8-1695378281196)]

外圍設備會設定一個廣播間隔出皇,每個廣播間隔中,它會重新發(fā)送自己的廣播數(shù)據(jù)哗戈。廣播間隔越長郊艘,越省電,同時也不太容易掃描到唯咬。

剛剛講到纱注,GAP決定了你的設備怎樣與其他設備進行交互。答案是有2種方式:

完全基于廣播的方式
也有些情況是不需要連接的胆胰,只要外設廣播自己的數(shù)據(jù)即可狞贱。用這種方式主要目的是讓外圍設備,把自己的信息發(fā)送給多個中心設備蜀涨。使用廣播這種方式最典型的應用就是蘋果的 iBeacon瞎嬉。這是蘋果公司定義的基于 BLE 廣播實現(xiàn)的功能,可以實現(xiàn)廣告推送和室內(nèi)定位勉盅。這也說明了佑颇,APP 使用 BLE,需要定位權限草娜。

基于非連接的挑胸,這種應用就是依賴 BLE 的廣播,也叫作 Beacon宰闰。這里有兩個角色茬贵,發(fā)送廣播的一方叫做 Broadcaster,監(jiān)聽廣播的一方叫 Observer移袍。

基于GATT連接的方式
大部分情況下解藻,外設通過廣播自己來讓中心設備發(fā)現(xiàn)自己,并建立 GATT 連接葡盗,從而進行更多的數(shù)據(jù)交換螟左。這里有且僅有兩個角色,發(fā)起連接的一方,叫做中心設備—Central胶背,被連接的設備巷嚣,叫做外設—Peripheral。

外圍設備:這一般就是非常小或者簡單的低功耗設備钳吟,用來提供數(shù)據(jù)廷粒,并連接到一個更加相對強大的中心設備,例如小米手環(huán)红且。
中心設備:中心設備相對比較強大坝茎,用來連接其他外圍設備,例如手機等暇番。
GATT 連接需要特別注意的是:GATT 連接是獨占的嗤放。也就是一個 BLE 外設同時只能被一個中心設備連接。一旦外設被連接奔誓,它就會馬上停止廣播斤吐,這樣它就對其他設備不可見了。當設備斷開厨喂,它又開始廣播和措。中心設備和外設需要雙向通信的話,唯一的方式就是建立 GATT 連接蜕煌。

GATT 通信的雙方是 C/S 關系派阱。外設作為 GATT 服務端(Server),它維持了 ATT 的查找表以及 service 和 characteristic 的定義斜纪。中心設備是 GATT 客戶端(Client)贫母,它向 Server 發(fā)起請求。需要注意的是盒刚,所有的通信事件腺劣,都是由客戶端發(fā)起,并且接收服務端的響應因块。

4. BLE通信基礎

BLE通信的基礎有兩個重要的概念橘原,ATT和GATT。

ATT
全稱 attribute protocol涡上,中文名“屬性協(xié)議”趾断。它是 BLE 通信的基礎。
ATT 把數(shù)據(jù)封裝吩愧,向外暴露為“屬性”芋酌,提供“屬性”的為服務端,獲取“屬性”的為客戶端雁佳。
ATT 是專門為低功耗藍牙設計的脐帝,結構非常簡單同云,數(shù)據(jù)長度很短。

GATT
全稱 Generic Attribute Profile腮恩, 中文名“通用屬性配置文件”梢杭。它是在ATT 的基礎上温兼,
對 ATT 進行的進一步邏輯封裝秸滴,定義數(shù)據(jù)的交互方式和含義。GATT是我們做 BLE 開發(fā)的時候直接接觸的概念募判。

GATT 層級
GATT按照層級定義了4個概念:配置文件(Profile)荡含、服務(Service)、特征(Characteristic)和描述(Descriptor)届垫。
他們的關系是這樣的:Profile 就是定義了一個實際的應用場景释液,一個 Profile包含若干個 Service,
一個 Service 包含若干個 Characteristic装处,一個 Characteristic 可以包含若干 Descriptor误债。

[圖片上傳失敗...(image-bff612-1695378281196)]

  • Profile

    Profile 并不是實際存在于 BLE 外設上的,它只是一個被 Bluetooth SIG 或者外設設計者預先定義的 Service 的集合妄迁。例如心率Profile(Heart Rate Profile)就是結合了 Heart Rate Service 和 Device Information Service寝蹈。所有官方通過 GATT Profile 的列表可以從這里找到。

  • Service

    Service 是把數(shù)據(jù)分成一個個的獨立邏輯項登淘,它包含一個或者多個 Characteristic箫老。每個 Service 有一個 UUID 唯一標識。 UUID 有 16 bit 的黔州,或者 128 bit 的耍鬓。16 bit 的 UUID 是官方通過認證的,需要花錢購買流妻,128 bit 是自定義的牲蜀,這個就可以自己隨便設置。官方通過了一些標準 Service绅这,完整列表在這里涣达。以 Heart Rate Service為例,可以看到它的官方通過 16 bit UUID 是 0x180D君躺,包含 3 個 Characteristic:Heart Rate Measurement, Body Sensor Location 和 Heart Rate Control Point峭判,并且定義了只有第一個是必須的,它是可選實現(xiàn)的棕叫。

  • Characteristic

    需要重點提一下Characteristic林螃, 它定義了數(shù)值和操作,包含一個Characteristic聲明俺泣、Characteristic屬性疗认、值完残、值的描述(Optional)。通常我們講的 BLE 通信横漏,其實就是對 Characteristic 的讀寫或者訂閱通知谨设。比如在實際操作過程中,我對某一個Characteristic進行讀缎浇,就是獲取這個Characteristic的value扎拣。

  • UUID

    Service、Characteristic 和 Descriptor 都是使用 UUID 唯一標示的素跺。

    UUID 是全局唯一標識二蓝,它是 128bit 的值,為了便于識別和閱讀指厌,一般以 “8位-4位-4位-4位-12位”的16進制標示刊愚,比如“12345678-abcd-1000-8000-123456000000”。

    但是踩验,128bit的UUID 太長鸥诽,考慮到在低功耗藍牙中,數(shù)據(jù)長度非常受限的情況箕憾,藍牙又使用了所謂的 16 bit 或者 32 bit 的 UUID牡借,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那幾位以外厕九,其他都是固定蓖捶,所以說,其實 16 bit UUID 是對應了一個 128 bit 的 UUID扁远。這樣一來俊鱼,UUID 就大幅減少了,例如 16 bit UUID只有有限的 65536(16的四次方) 個畅买。與此同時并闲,因為數(shù)量有限,所以 16 bit UUID 并不能隨便使用谷羞。藍牙技術聯(lián)盟已經(jīng)預先定義了一些 UUID帝火,我們可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一個是常見于BLE設備中的UUID湃缎。當然也可以花錢定制自定義的UUID犀填。

二: BLE開發(fā)流程-掃描,連接,發(fā)送和接收數(shù)據(jù),分包解包

demo地址:https://github.com/PangHaHa12138/AndroidBLE

demo演示1

涉及ble藍牙通訊的客戶端(中心設備)開啟、掃描嗓违、連接九巡、發(fā)送和接收數(shù)據(jù)、分包解包蹂季,
和服務端(外圍設備)初始化廣播數(shù)據(jù)冕广、開始廣播疏日、配置Services、Server回調(diào)操作

1撒汉,兩臺手機A 小米手機 為客戶端沟优,B 三星手機 為服務端

[圖片上傳失敗...(image-ee8c45-1695378281196)]

2,B開啟廣播睬辐,A掃描設備挠阁,掃描到B Galaxy A20,建立連接溉委,

[圖片上傳失敗...(image-dc8f31-1695378281196)]

[圖片上傳失敗...(image-d2cb6-1695378281196)]

3鹃唯,A通過唯一UUID服務 發(fā)送數(shù)據(jù)給B,B收到數(shù)據(jù)瓣喊,顯示日志

[圖片上傳失敗...(image-17fbc1-1695378281196)]
4,A設置回調(diào)通知黔酥,B綁定通知服務藻三,回調(diào)給A

[圖片上傳失敗...(image-6db590-1695378281196)]
5,A寫入?yún)f(xié)議給B跪者,B通過回調(diào)通知寫入?yún)f(xié)議給A

[圖片上傳失敗...(image-ba850d-1695378281196)]

demo演示2

兩臺手機都是主機棵帽,電腦為從機(電腦通過插入HLK-B40藍牙透傳模塊,獲得藍牙BLE連接能力)

1渣玲,兩臺手機分別掃描低功耗藍牙逗概,并發(fā)現(xiàn)HLK-B40,

[圖片上傳失敗...(image-8422ea-1695378281196)]
2忘衍,手機端與電腦建立連接逾苫,并發(fā)送數(shù)據(jù)

[圖片上傳失敗...(image-c6a14d-1695378281196)]

3,電腦端使用藍牙串口助手調(diào)試 收到數(shù)據(jù)

[圖片上傳失敗...(image-ab930f-1695378281196)]

4枚钓,電腦端發(fā)送數(shù)據(jù)

[圖片上傳失敗...(image-ce234d-1695378281197)]

5铅搓,手機端接收數(shù)據(jù)

[圖片上傳失敗...(image-69a3f6-1695378281197)]

6,電腦端定時發(fā)送數(shù)據(jù)搀捷,手機端定時發(fā)送數(shù)據(jù)

[圖片上傳失敗...(image-df1448-1695378281197)]

[圖片上傳失敗...(image-55582b-1695378281197)]

下面詳細講解下客戶端和服務端的開發(fā)步驟流程

三: BLE客戶端開發(fā)流程

1星掰、申請權限

安卓手機涉及藍牙權限問題,藍牙開發(fā)需要在AndroidManifest.xml文件中添加權限聲明:

<!--藍牙權限-->

<uses-permission android:name="android.permission.BLUETOOTH"

android:maxSdkVersion="30" />

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"

android:maxSdkVersion="30" />

<!--Android6及以上 動態(tài)申請位置權限-->

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!--Android12及以上 申請藍牙掃描嫩舟,連接氢烘,廣播權限-->

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!--使用藍牙低功耗BLE-->

<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>

權限說明:

在Android4.3 至 Android6.0 需要藍牙權限

<uses-permission android:name="android.permission.BLUETOOTH" />

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

<!-- true 表示手機必須支持BLE,否則無法安裝家厌!這里設為false, 運行后在代碼中檢查-->

<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>

在Android6.0(包括6.0) 到 Android12.0需要定位權限播玖,包括模糊位置和精準位置,并且需要代碼動態(tài)申請

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Android11之前權限申請:
https://developer.android.com/guide/topics/connectivity/bluetooth/permissions

Android12權限申請:
https://developer.android.google.cn/about/versions/12/features/bluetooth-permissions

在Android12.0及以上像街,需要申請藍牙掃描黎棠,連接晋渺,廣播權限

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

如果App不需要獲取位置權限可以增加應用不推導位置的flag,則不需要申請位置權限了

<!-- 設置最大支持使用版本為30脓斩,即Android12和以后的高版本不再需要老權限了 -->

<uses-permission android:name="android.permission.BLUETOOTH"

                     android:maxSdkVersion="30" />

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"

                     android:maxSdkVersion="30" />



<!-- 僅當你可以強烈斷言你的應用程序永遠不會從藍牙掃描結果中獲取物理位置時木西,才包含“neverForLocation” -->

<uses-permission android:name="android.permission.BLUETOOTH_SCAN"

                     android:usesPermissionFlags="neverForLocation" />

<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

如果要在后臺開啟服務掃描藍牙,則還需要增加后臺訪問位置權限

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

2随静、打開藍牙

在搜索設備之前先要詢問打開手機藍牙

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {

    //不支持BLE

    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();

    finish();

}

final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

mBluetoothAdapter = bluetoothManager.getAdapter();

if (mBluetoothAdapter == null) {

    Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();

    finish();

    return;

}



// 開啟藍牙

if(!mBluetoothAdapter.isEnabled()){

    //不建議強制打開藍牙八千,官方建議通過Intent讓用戶選擇打開藍牙

    //mBluetoothAdapter.enable();

    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH);

}

動態(tài)權限申請,申請權限后還需要檢查手機GPS是否開啟,有沒有定位權限和GPS是否打開是兩回事

List<String> mPermissionList = new ArrayList<>();

// Android 版本大于等于 12 時燎猛,申請新的藍牙權限

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {

    mPermissionList.add(Manifest.permission.BLUETOOTH_SCAN);

    mPermissionList.add(Manifest.permission.BLUETOOTH_ADVERTISE);

    mPermissionList.add(Manifest.permission.BLUETOOTH_CONNECT);

    //根據(jù)實際需要申請定位權限

    mPermissionList.add(Manifest.permission.ACCESS_COARSE_LOCATION);

    mPermissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);

} else {

    //Android 6.0開始 需要定位權限

    mPermissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);

    mPermissionList.add(Manifest.permission.ACCESS_COARSE_LOCATION);

}

boolean hasPermission = false;

for (int i = 0; i < mPermissionList.size(); i++) {

    int permissionCheck = ContextCompat.checkSelfPermission(this, mPermissionList.get(i));

    hasPermission = permissionCheck == PackageManager.PERMISSION_GRANTED;

}

if (hasPermission) {

   //已經(jīng)有權限了

} else {

    ActivityCompat.requestPermissions(this, mPermissionList.toArray(new String[0]), REQUEST_PERMISSION_CODE);

}



//開啟位置服務恋捆,支持獲取ble藍牙掃描結果

if (Build.VERSION.SDK_INT >=  Build.VERSION_CODES.M && !isLocationOpen(getApplicationContext())) {

    Intent enableLocate = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);

    startActivityForResult(enableLocate, REQUEST_LOCATION_PERMISSION);

}



public static boolean isLocationOpen(final Context context){

LocationManager manager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

    //gps定位

    boolean isGpsProvider = manager.isProviderEnabled(LocationManager.GPS_PROVIDER);

    //網(wǎng)絡定位

    boolean isNetWorkProvider = manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

    return isGpsProvider|| isNetWorkProvider;

}

3、搜索設備

注意: BLE設備地址是動態(tài)變化(每隔一段時間都會變化),而經(jīng)典藍牙設備是出廠就固定不變了重绷!

final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

 // 下面使用Android5.0新增的掃描API沸停,掃描返回的結果更友好,比如BLE廣播數(shù)據(jù)以前是byte[] scanRecord昭卓,

 // 而新API幫我們解析成ScanRecord類

BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();

 // 掃描結果Callback

private ScanCallback mScanCallback = new ScanCallback() {

    @Override

    public void onScanResult(int callbackType, ScanResult result) {

        // result.getScanRecord() 獲取BLE廣播數(shù)據(jù)

        BluetoothDevice device = result.getDevice() 獲取BLE設備信息

        String strName = bluetoothDevice.getName();

        if (strName != null && strName.length() > 0) {

            //添加設備到列表

        }



    }

};

bluetoothLeScanner.startScan(mScanCallback); //開啟掃描

mHandler.postDelayed(new Runnable() {

    @Override

    public void run() {

        bluetoothLeScanner.stopScan(mScanCallback); //停止掃描

        isScanning = false;

    }

}, 3000);



// 舊API是BluetoothAdapter.startLeScan(LeScanCallback callback)方式掃描BLE藍牙設備愤钾,如下:

private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {



        @Override

        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {

            //獲取設備信息 device

            String strName = device.getName();

            if (strName != null && strName.length() > 0) {

                //添加設備到列表

            }

        }

    };

bluetoothAdapter.startLeScan(leScanCallback); //開啟掃描

mHandler.postDelayed(new Runnable() {

    @Override

    public void run() {

        bluetoothAdapter.stopLeScan(leScanCallback); //停止掃描

        isScanning = false;

    }

}, 3000);

4、連接設備

通過掃描BLE設備候醒,根據(jù)設備名稱區(qū)分出目標設備targetDevice能颁,下一步實現(xiàn)與目標設備的連接,在連接設備之前要停止搜索藍牙倒淫;停止搜索一般需要一定的時間來完成伙菊,最好調(diào)用停止搜索函數(shù)之后加以100ms的延時,保證系統(tǒng)能夠完全停止搜索藍牙設備敌土。停止搜索之后啟動連接過程镜硕;

BLE藍牙的連接方法相對簡單只需調(diào)用BluetoothDevice的connectGatt方法;

public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback)纯赎;

//示例

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

    mBluetoothGatt = bluetoothDevice.connectGatt(BleClientActivity.this, false, mBluetoothGattCallback, BluetoothDevice.TRANSPORT_LE);

} else {

    mBluetoothGatt = bluetoothDevice.connectGatt(BleClientActivity.this, false, mBluetoothGattCallback);

}

參數(shù)說明

  • 返回值 BluetoothGatt: BLE藍牙連接管理類谦疾,主要負責與設備進行通信已维;
  • boolean autoConnect:建議置為false六孵,能夠提升連接速度咱士;
  • BluetoothGattCallback callback 連接回調(diào)刘离,重要參數(shù)烛芬,BLE通信的核心部分

5硼一、設備通信

與設備建立連接之后與設備通信浸卦,整個通信過程都是在BluetoothGattCallback的異步回調(diào)函數(shù)中完成程帕;

BluetoothGattCallback中主要回調(diào)函數(shù)如下:

private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {



    @Override

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

        //連接狀態(tài)改變的Callback

    }



    @Override

    public void onServicesDiscovered(BluetoothGatt gatt, int status) {

        //服務發(fā)現(xiàn)成功的Callback

    }



    @Override

    public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {

        //寫入Characteristic

    }



    @Override

    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {

         //讀取Characteristic 

     }



    @Override

    public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {

        //通知Characteristic

    }



    @Override

    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {

        //寫入Descriptor

    }



    @Override

    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {

        //讀取Descriptor

    }



};

上述幾個回調(diào)函數(shù)是BLE開發(fā)中不可缺少的

6该默、等待設備連接成功

當調(diào)用targetDevice.connectGatt(context, false, gattCallback)后系統(tǒng)會主動發(fā)起與BLE藍牙設備的連接瞳氓,
若成功連接到設備將回調(diào)onConnectionStateChange方法,其處理過程如下;

@Override

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

     if (newState == BluetoothGatt.STATE_CONNECTED) {

         Log.e(TAG, "設備連接上 開始掃描服務");

         // 連接成功后栓袖,開始掃描服務

         mBluetoothGatt.discoverServices();

     }

     if (newState == BluetoothGatt.STATE_DISCONNECTED) {

         // 連接斷開

         /*連接斷開后的相應處理*/    

     }

};

判斷newState == BluetoothGatt.STATE_CONNECTED表明此時已經(jīng)成功連接到設備

7匣摘、開啟掃描服務

bluetoothGatt.discoverServices() 掃描BLE設備服務是安卓系統(tǒng)中關于BLE藍牙開發(fā)的重要一步店诗,一般在設備連接成功后調(diào)用,
掃描到設備服務后回調(diào)onServicesDiscovered()函數(shù)音榜,函數(shù)原型如下:

public BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {



@Override

public void onConnectionStateChange(BluetoothGatt bluetoothGatt, int status, int newState) {

    BluetoothDevice dev = bluetoothGatt.getDevice();

    if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {

        isConnected = true;

        bluetoothGatt.discoverServices(); //啟動服務發(fā)現(xiàn)

    } else {

        isConnected = false;

    }

}



@Override

public void onServicesDiscovered(BluetoothGatt bluetoothGatt, int status) {

    if (status == BluetoothGatt.GATT_SUCCESS) { //BLE服務發(fā)現(xiàn)成功

      private List<BluetoothGattService> servicesList;

      //獲取服務列表

      servicesList = bluetoothGatt.getServices();

    }

}

...

}
  • BLE藍牙協(xié)議下數(shù)據(jù)的通信方式采用BluetoothGattService庞瘸、BluetoothGattCharacteristic和BluetoothGattDescriptor三個主要的類實現(xiàn)通信;
  • BluetoothGattService 簡稱服務赠叼,是構成BLE設備協(xié)議棧的組成單位擦囊,一個藍牙設備協(xié)議棧一般由一個或者多個BluetoothGattService組成;
  • BluetoothGattCharacteristic 簡稱特征嘴办,一個服務包含一個或者多個特征瞬场,特征作為數(shù)據(jù)的基本單元;
  • 一個BluetoothGattCharacteristic特征包含一個數(shù)據(jù)值和附加的關于特征的描述涧郊;
  • BluetoothGattDescriptor:用于描述特征的類贯被,其同樣包含一個value值;

8底燎、獲取負責通信的BluetoothGattCharacteristic

BLE藍牙開發(fā)主要有負責通信的BluetoothGattService完成的刃榨。當且稱為通信服務。通信服務通過硬件工程師提供的UUID獲取双仍。獲取方式如下:

  • BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(“藍牙模塊提供的負責通信UUID字符串”));
  • 通信服務中包含負責讀寫的BluetoothGattCharacteristic,且分別稱為notifyCharacteristic和writeCharacteristic桌吃。其中notifyCharacteristic負責開啟監(jiān)聽朱沃,也就是啟動收數(shù)據(jù)的通道,writeCharacteristic負責寫入數(shù)據(jù)茅诱;
    具體操作方式如下:
  BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("藍牙模塊提供的負責通信服務UUID字符串"));

   // 例如形式如:49535343-fe7d-4ae5-8fa9-9fafd205e455

  notifyCharacteristic = service.getCharacteristic(UUID.fromString("notify uuid"));

  writeCharacteristic =  service.getCharacteristic(UUID.fromString("write uuid"));

9逗物、開啟監(jiān)聽

開啟監(jiān)聽,即建立與設備的通信的首發(fā)數(shù)據(jù)通道瑟俭,BLE開發(fā)中只有當客戶端成功開啟監(jiān)聽后才能與服務端收發(fā)數(shù)據(jù)翎卓。開啟監(jiān)聽的方式如下:

mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true)

BluetoothGattDescriptor descriptor = characteristic .getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));

descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);

//若開啟監(jiān)聽成功則會回調(diào)BluetoothGattCallback中的onDescriptorWrite()方法,處理方式如下:

@Override

public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {

   if (status == BluetoothGatt.GATT_SUCCESS) {

       //開啟監(jiān)聽成功摆寄,可以向設備寫入命令了

       Log.e(TAG, "開啟監(jiān)聽成功");

   }

};

10失暴、寫入數(shù)據(jù)

BLE單次寫的數(shù)據(jù)量大小是有限制的,通常是20字節(jié)微饥,可以嘗試通過requestMTU增大逗扒,但不保證能成功。分包寫是一種解決方案欠橘,需要定義分包協(xié)議矩肩,假設每個包大小20字節(jié),分兩種包肃续,數(shù)據(jù)包和非數(shù)據(jù)包黍檩。對于數(shù)據(jù)包叉袍,頭兩個字節(jié)表示包的序號,剩下的都填充數(shù)據(jù)刽酱。對于非數(shù)據(jù)包喳逛,主要是發(fā)送一些控制信息。
監(jiān)聽成功后通過向 writeCharacteristic寫入數(shù)據(jù)實現(xiàn)與服務端的通信肛跌。寫入方式如下:

//value為客戶端向服務端發(fā)送的指令

writeCharacteristic.setValue(value);

mBluetoothGatt.writeCharacteristic(writeCharacteristic)

其中:value一般為Hex格式指令艺配,其內(nèi)容由設備通信的藍牙通信協(xié)議規(guī)定;

11衍慎、接收數(shù)據(jù)

若寫入指令成功則回調(diào)BluetoothGattCallback中的onCharacteristicWrite()方法转唉,說明將數(shù)據(jù)已經(jīng)發(fā)送給下位機;

@Override

public void onCharacteristicWrite(BluetoothGatt gatt,

    BluetoothGattCharacteristic characteristic, int status) {

    if (status == BluetoothGatt.GATT_SUCCESS) {

        Log.e(TAG, "發(fā)送成功");

    }

}

若發(fā)送的數(shù)據(jù)符合通信協(xié)議稳捆,則服務端會向客戶端回復相應的數(shù)據(jù)赠法。發(fā)送的數(shù)據(jù)通過回調(diào)onCharacteristicChanged()方法獲取,其處理方式如下:

@Override

public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {

    // value為設備發(fā)送的數(shù)據(jù)乔夯,根據(jù)數(shù)據(jù)協(xié)議進行解析

    byte[] value = characteristic.getValue();

}

通過向服務端發(fā)送指令獲取服務端的回復數(shù)據(jù)砖织,即可完成與設備的通信過程;

12末荐、斷開連接

當與設備完成通信之后之后一定要斷開與設備的連接侧纯,防止下次連接設備出問題,調(diào)用以下方法斷開與設備的連接:

mBluetoothGatt.disconnect();

mBluetoothGatt.close();

四: BLE服務端開發(fā)流程

1甲脏、設置廣播以及初始化廣播數(shù)據(jù)

//廣播設置(必須)

AdvertiseSettings settings = new AdvertiseSettings.Builder()

        .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //廣播模式: 低功耗,平衡,低延遲

        .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //發(fā)射功率級別: 極低,低,中,高

        .setTimeout(0)

        .setConnectable(true) //能否連接,廣播分為可連接廣播和不可連接廣播

        .build();



//廣播數(shù)據(jù)(必須眶熬,廣播啟動就會發(fā)送)

AdvertiseData advertiseData = new AdvertiseData.Builder()

        .setIncludeDeviceName(true) //包含藍牙名稱

        .setIncludeTxPowerLevel(true) //包含發(fā)射功率級別

        .addManufacturerData(1, new byte[]{23, 33}) //設備廠商數(shù)據(jù),自定義

        .build();



//掃描響應數(shù)據(jù)(可選块请,當客戶端掃描時才發(fā)送)

AdvertiseData scanResponse = new AdvertiseData.Builder()

        .addManufacturerData(2, new byte[]{66, 66}) //設備廠商數(shù)據(jù)娜氏,自定義

        .addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服務UUID

//                .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服務數(shù)據(jù),自定義

        .build();

2墩新、開始廣播

BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

//BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();



// ============啟動BLE藍牙廣播(廣告) ===============

mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();

mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);



// BLE廣播Callback

private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {

    @Override

    public void onStartSuccess(AdvertiseSettings settingsInEffect) {

        logTv("BLE廣播開啟成功");

    }



    @Override

    public void onStartFailure(int errorCode) {

        logTv("BLE廣播開啟失敗,錯誤碼:" + errorCode);

    }

};

3贸弥、配置Services以及Characteristic

// 注意:必須要開啟可連接的BLE廣播,其它設備才能發(fā)現(xiàn)并連接BLE服務端!

// =============啟動BLE藍牙服務端======================================

BluetoothGattService service = new BluetoothGattService(UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);



//添加可讀+通知characteristic

BluetoothGattCharacteristic characteristicRead = new BluetoothGattCharacteristic(UUID_CHAR_READ_NOTIFY,BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);

characteristicRead.addDescriptor(new BluetoothGattDescriptor(UUID_DESC_NOTITY, BluetoothGattCharacteristic.PERMISSION_WRITE));

service.addCharacteristic(characteristicRead);



//添加可寫characteristic

BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHAR_WRITE, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);

service.addCharacteristic(characteristicWrite);



if (bluetoothManager != null){

    mBluetoothGattServer = bluetoothManager.openGattServer(this, mBluetoothGattServerCallback);

}



mBluetoothGattServer.addService(service);

4海渊、Server回調(diào)以及操作

/**

* 服務事件的回調(diào)

*/

private BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() {

    // 1.連接狀態(tài)發(fā)生變化時

    @Override

    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {



    }

    @Override

    public void onServiceAdded(int status, BluetoothGattService service) {



    }

    @Override

    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {

       String response = "CHAR_" + (int) (Math.random() * 100); //模擬數(shù)據(jù)

       // 響應客戶端

       mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes());

    }

    // 3. onCharacteristicWriteRequest,接收具體的字節(jié)

    @Override

    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {

    // 獲取客戶端發(fā)過來的數(shù)據(jù)

    String requestStr = new String(requestBytes);

    // 響應客戶端 

    mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);

    }

    // 5.特征被讀取绵疲。當回復響應成功后,客戶端會讀取然后觸發(fā)本方法

    @Override

    public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {

    String response = "DESC_" + (int) (Math.random() * 100); //模擬數(shù)據(jù)

     // 響應客戶端

    mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes());    

    }

    // 2.描述被寫入時切省,在這里執(zhí)行 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS...  收最岗,觸發(fā) onCharacteristicWriteRequest

    @Override

    public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {

    // 收到客戶端發(fā)過來的數(shù)據(jù)

    String valueStr = Arrays.toString(value);

    mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);// 響應客戶端

    //4.處理響應內(nèi)容

    // 簡單模擬通知客戶端Characteristic變化

    if (Arrays.toString(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE).equals(valueStr)) { //是否開啟通知

       deviceNotify = device;

       descriptorNotify = descriptor;

       final BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();

       new Thread(new Runnable() {

          @Override

          public void run() {

          String response = "CHAR_" + (int) (Math.random() * 100); //模擬數(shù)據(jù)

           characteristic.setValue(response);

           //通知客戶端改變Characteristic

            mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);

          }

       }).start();

    }

    }

    @Override

    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {



    }

    @Override

    public void onNotificationSent(BluetoothDevice device, int status) {



    }

    @Override

    public void onMtuChanged(BluetoothDevice device, int mtu) {



    }

    };

5、Server主動寫入數(shù)據(jù)通知client

private void write(String response) {

  if (deviceNotify == null && descriptorNotify == null) {

     return;

  }

  if (TextUtils.isEmpty(response.trim())) {

      return;

  }

  BluetoothGattCharacteristic characteristic = descriptorNotify.getCharacteristic();

  characteristic.setValue(response);

  mBluetoothGattServer.notifyCharacteristicChanged(deviceNotify, characteristic, false);

}

6朝捆、關閉廣播般渡,斷開連接

if (mBluetoothLeAdvertiser != null) {

   mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);

}



if (mBluetoothGattServer != null) {

   mBluetoothGattServer.close();

}

五:藍牙操作的注意事項

1、如何避免ble藍牙連接出現(xiàn)133錯誤?

  • Android 連接外圍設備的數(shù)量有限驯用,當不需要連接藍牙設備的時候脸秽,必須調(diào)用 BluetoothGatt#close 方法釋放資源;
  • 藍牙 API 連接藍牙設備的超時時間大概在 20s 左右蝴乔,具體時間看系統(tǒng)實現(xiàn)记餐。有時候某些設備進行藍牙連接的時間會很長,大概十多秒薇正。如果自己手動設置了連接超時時間在某些設備上可能會導致接下來幾次的連接嘗試都會在 BluetoothGattCallback#onConnectionStateChange 返回 state == 133片酝;
  • 能否避免android設備與ble設備連接/斷開時上報的133這類錯誤?
  • 1、在連接失敗或者斷開連接之后挖腰,調(diào)用 close 并刷新緩存
  • 2雕沿、盡量不要在startLeScan的時候嘗試連接,先stopLeScan后再去連
  • 3猴仑、對同一設備斷開后再次連接(連接失敗重連)审轮,哪怕調(diào)用完close,需要等待一段時間(400毫秒試了1次辽俗,結果不 行疾渣;1000毫秒則再沒出現(xiàn)過問題)后再去connectGatt
  • 4、可以在連接前都startLeScan一下崖飘,成功率要高一點

2榴捡、單次寫的數(shù)據(jù)大小有20字節(jié)限制,如何發(fā)送長數(shù)據(jù)朱浴?

BLE單次寫的數(shù)據(jù)量大小是有限制的薄疚,通常是20字節(jié),可以嘗試通過requestMTU增大赊琳,但不保證能成功。分包寫是一種解決方案砰碴,需要定義分包協(xié)議躏筏,假設每個包大小20字節(jié),分兩種包呈枉,數(shù)據(jù)包和非數(shù)據(jù)包趁尼。對于數(shù)據(jù)包,頭兩個字節(jié)表示包的序號猖辫,剩下的都填充數(shù)據(jù)酥泞。對于非數(shù)據(jù)包,主要是發(fā)送一些控制信息啃憎。
總體流程如下:

  • (1)芝囤、定義通訊協(xié)議,如下(這里只是個舉例,可以根據(jù)項目需求擴展)
消息號(1個字節(jié)) 功能(1個字節(jié)) 子功能(1個字節(jié)) 數(shù)據(jù)長度(2個字節(jié)) 數(shù)據(jù)內(nèi)容(N個字節(jié)) CRC校驗(1個字節(jié))
01 01 01 0000 2D

消息號(1個字節(jié)) 功能(1個字節(jié)) 子功能(1個字節(jié)) 數(shù)據(jù)長度(2個字節(jié)) 數(shù)據(jù)內(nèi)容(N個字節(jié)) CRC校驗(1個字節(jié))
01 01 01 0000 – 2D

  • (2)悯姊、封裝通用發(fā)送數(shù)據(jù)接口(拆包)
    該接口根據(jù)會發(fā)送數(shù)據(jù)內(nèi)容按最大字節(jié)數(shù)拆分(一般20字節(jié))放入隊列羡藐,拆分完后,依次從隊列里取出發(fā)送
  • (3)悯许、封裝通用接收數(shù)據(jù)接口(組包)
    該接口根據(jù)從接收的數(shù)據(jù)按協(xié)議里的定義解析數(shù)據(jù)長度判讀是否完整包仆嗦,不是的話把每條消息累加起來
  • (4)、解析完整的數(shù)據(jù)包先壕,進行業(yè)務邏輯處理
  • (5)瘩扼、協(xié)議還可以引入加密解密,需要注意的選算法參數(shù)的時候垃僚,加密后的長度最好跟原數(shù)據(jù)長度一致集绰,這樣不會影響拆包組包

3、在Android不同版本或不同的手機掃描不到藍牙設備

一般都是Android版本適配以及不同ROM機型(小米/紅米冈在、華為/榮耀等)(EMUI倒慧、MIUI、ColorOS等)的權限問題

4包券、讀寫問題

藍牙的寫入操作, 讀取操作必須序列化進行. 寫入數(shù)據(jù)和讀取數(shù)據(jù)是不能同時進行的, 如果調(diào)用了寫入數(shù)據(jù)的方法,
馬上調(diào)用又調(diào)用寫入數(shù)據(jù)或者讀取數(shù)據(jù)的方法,第二次調(diào)用的方法會立即返回 false, 代表當前無法進行操作纫谅;

5、一個完整的數(shù)據(jù)包分析

AAAB5D65501E08040004001B130053D550F6

  • AA – 前導幀(preamble)
  • 0x50655DAB – 訪問地址(access address)
  • 1E – LL幀頭字段(LL header)
  • 08 – 有效數(shù)據(jù)包長度(payload length)
  • 04000400 – ATT數(shù)據(jù)長度溅固,以及L2CAP通道編號
  • 1B – notify command
  • 0x0013 – 電量數(shù)據(jù)handle
  • 0x53 – 真正要發(fā)送的電量數(shù)據(jù)
  • 0xF650D5 – CRC24值

[圖片上傳失敗...(image-9979cc-1695378281197)]

參考文檔:

藍牙技術聯(lián)盟:

https://www.bluetooth.com/zh-cn/learn-about-bluetooth/tech-overview/

BLE技術詳解:

http://doc.iotxx.com/BLE%E6%8A%80%E6%9C%AF%E6%8F%AD%E7%A7%98

Android藍牙BLE開發(fā)指南:

https://developer.android.com/guide/topics/connectivity/bluetooth/ble-overview

BLE藍牙開源庫:

https://github.com/Jasonchenlijian/FastBle

https://github.com/dingjikerbo/Android-BluetoothKit

https://github.com/aicareles/Android-BLE

https://github.com/NordicSemiconductor/Android-BLE-Library

https://github.com/xiaoyaoyou1212/BLE

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末付秕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子侍郭,更是在濱河造成了極大的恐慌询吴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亮元,死亡現(xiàn)場離奇詭異猛计,居然都是意外死亡,警方通過查閱死者的電腦和手機爆捞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門奉瘤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人煮甥,你說我怎么就攤上這事盗温。” “怎么了成肘?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵卖局,是天一觀的道長。 經(jīng)常有香客問我双霍,道長砚偶,這世上最難降的妖魔是什么批销? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮蟹演,結果婚禮上风钻,老公的妹妹穿的比我還像新娘。我一直安慰自己酒请,他們只是感情好骡技,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著羞反,像睡著了一般布朦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上昼窗,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天是趴,我揣著相機與錄音,去河邊找鬼澄惊。 笑死唆途,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的掸驱。 我是一名探鬼主播肛搬,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毕贼!你這毒婦竟也來了温赔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鬼癣,失蹤者是張志新(化名)和其女友劉穎陶贼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體待秃,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡拜秧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了章郁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腹纳。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖驱犹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情足画,我是刑警寧澤雄驹,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站淹辞,受9級特大地震影響医舆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一蔬将、第九天 我趴在偏房一處隱蔽的房頂上張望爷速。 院中可真熱鬧,春花似錦霞怀、人聲如沸惫东。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽廉沮。三九已至,卻和暖如春徐矩,著一層夾襖步出監(jiān)牢的瞬間滞时,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工滤灯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坪稽,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓鳞骤,卻偏偏與公主長得像窒百,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子弟孟,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容