你不知道的前端藍牙應用實踐-心率帶
本文為來自 字節教育-成人與創新前端團隊 成員的文章,已授權 ELab 發布。
一、背景
最近開啟了減肥計劃,購入了一條心率帶,期望在使用劃船機過程中監測心率情況。購入后的情況如下:
心率帶不直接顯示數值,需要連接APP或相關設備使用。
官方APP僅實時顯示心率數據,無法生成心率統計圖表。
通過咕咚APP連接心率帶,開啟運動后可以監測心率變化,但劃船機不在支持的運動范圍內。
自己簡單實現了一個劃船機節拍器的小程序。
于是萌生了在自己的節拍器小程序內監聽心率數據的想法,即Taro小程序中的藍牙應用實踐。
二、簡單了解藍牙
對于概念類的知識筆者畢竟不是專業的,感興趣的同學可以通過百科、搜索引擎等渠道進行了解。這里僅簡單介紹接下來會運用到的一些知識。
中心設備/外圍設備
客戶端(中心設備):在本次實踐中為筆者的手機。
服務端(外圍設備):在本次實踐中為心率帶、耳機等設備。
BLE(Bluetooth Low Energy)
藍牙低能耗技術,實現設備間的連接與通信。顧名思義,能耗與成本都更低,與之相對應的則稱為經典藍牙。基于筆者的開發目的,本文簡單了解設備連接前(GAP)和連接后(GATT)所涉及的兩個協議。
(了解更多:藍牙低能耗——百度百科[1]、一文讀懂藍牙技術從 1.0 到 5.0 的前世今生[2])
GAP(Generic Access Profile)
主要用來控制設備連接和廣播。通常是由外圍設備主動、間隔性地廣播設備信息,等待中心設備發現并建立連接。需要注意的是,這種連接方式是獨占的,在建立連接后,外圍設備將停止廣播。
另一方面,還存在僅向外廣播而不建立連接的iBeacon設備。
GATT(Generic Attribute Profile)
定義了兩個設備間的數據傳輸方式。GATT中有兩個關鍵概念:service(服務)與characteristic(特征)。
Service就是一個獨立的邏輯項,它包含一個或多個Characteristic。
Characteristic是GATT中最小的邏輯數據單元,其屬性包含properties,標識該特性是否能夠被read、write、notify、indicate。notify與indicate的區別在于,indicate需要有回復才能發送下一個數據包。
每個服務和特性都具有唯一的UUID標識,其中部分是由Bluetooth SIG官方定義的, Assigned Numbers | Bluetooth? Technology Website[3],如設備名、心率數據等常用屬性都是官方定義來統一規范。此外UUID也可以由硬件工程師來自定義實現。
(了解更多:BLE相關協議(GAP&GATT)[4])
三、API簡介
Taro中的藍牙API
Taro.openBluetoothAdapter(option) | Taro 文檔[5]
初始化藍牙模塊
Taro.getBluetoothDevices(option) | Taro 文檔[6]
獲取在藍牙模塊生效期間所有已發現的藍牙設備。包括已經和本機處于連接狀態的設備。
也可以使用 Taro.onBluetoothDeviceFound(callback) | Taro 文檔[7]來發現設備。
Taro.createBLEConnection(option) | Taro 文檔[8]
連接低功耗藍牙設備。
若小程序在之前已有搜索過某個藍牙設備,并成功建立連接,可直接傳入之前搜索獲取的 deviceId 直接嘗試連接該設備,無需進行搜索操作。
Taro.getBLEDeviceServices(option) | Taro 文檔[9]
獲取藍牙設備所有服務(service)。
Taro.getBLEDeviceCharacteristics(option) | Taro 文檔[10]
獲取藍牙設備某個服務中所有特征值(characteristic)。
Taro.readBLECharacteristicValue(option) | Taro 文檔[11]
讀取低功耗藍牙設備的特征值的二進制數據值。注意:必須設備的特征值支持 read 才可以成功調用。
接口讀取到的信息需要在onBLECharacteristicValueChange 方法注冊的回調中獲取。
Taro.notifyBLECharacteristicValueChange(option) | Taro 文檔[12]
啟用低功耗藍牙設備特征值變化時的 notify 功能,訂閱特征值。注意:必須設備的特征值支持 notify 或者 indicate 才可以成功調用。
另外,必須先啟用 notifyBLECharacteristicValueChange 才能監聽到設備 characteristicValueChange 事件
Taro.onBLECharacteristicValueChange(callback) | Taro 文檔[13]
監聽低功耗藍牙設備的特征值變化事件。必須先啟用 notifyBLECharacteristicValueChange 接口才能接收到設備推送的 notification。
WEB藍牙API
此處貼一些資料,感興趣可自行閱讀
Web Bluetooth API - Web APIs | MDN[14]
通過 JavaScript 與藍牙設備通信[15]
四、設備名稱(Device Name)詳解
首先getBLEDeviceServices獲取到服務列表:
查詢資料
可知0x1800是我們需要的服務。getBLEDeviceCharacteristics獲取其特征列表:
查詢資料
可知0x2A00是我們需要的特征。此時可以看到read屬性為true,我們通過onBLECharacteristicValueChange和readBLECharacteristicValue讀一下數據看看。
得到輸出,某米耳機:
大家應該已經發現,給到的特征值其實是ArrayBuffer格式。
(了解更多:談談JS二進制:File、Blob、FileReader、ArrayBuffer、Base64 - 掘金[16])
此時需要我們將其轉化為字符串。除了上面的方法外,還可以先轉16進制,再轉字符串:
五、心率測量(Heart Rate Measurement)詳解
同樣,首先我們拿到了所需的服務和特征如下。
在設備連接后,通過onBLECharacteristicValueChange和notifyBLECharacteristicValueChange訂閱特征值變化。
此時我們已經能夠獲取到心率帶發送的心率數據如下。
但是此時如果按照上文解析設備名稱的方式,將其轉化為字符串,將得到一串亂碼。
所以需要根據協議文檔(https://www.bluetooth.com/specifications/specs/heart-rate-service-1-0/)來解讀這幾個數據。
標志字段:
- 二進制:10110、十進制:22、十六進制:16
- 其中位0為心率格式位,決定心率數據是uint8(位0 === 0)還是unit16(位0 === 1)
- 位1、位2為傳感器接觸狀態位。11表示支持皮膚接觸檢測且接觸正常。
- 位3為能量消耗狀態位,0表示能量消耗字段不存在.
- 位4為RR-Interval狀態位,1表示存在一個或多個 RR-Interval 值。
心率數值:
- 二進制:1100101、十進制:101、十六進制:65
- 當心率小于255時,unit8足以。但如果需要支持更高的心率值(某些動物),則需要為unit16
- 此時我們根據標志字段0,已經能夠獲取到需要的心率值:101。那么還剩下兩個字段代表什么意思呢?
RR-intreval 心率間隔:
- 從前面的標志字段分析中,我們發現該設備不支持能量消耗狀態但支持RR-interval。且RR-interval可能是一位也可能是多位。那么我們該怎么讀取這個數字呢?
- 首先我們顧名思義,心率間隔就是兩次心跳間的間隔時長,從上面的心率值推算:
- 60*1000/101 ≈594ms
- 接下來看這兩個數字。
- 二進制:1010010、十進制:82、十六進制:52
- 二進制:10、十進制:2、十六進制:02
- 嘗試2*256+82 = 594,謎團解開了~
六、總結與疑惑
至此整個藍牙心率設備數據獲取的實現就完成了,可以在使用節拍器的同時監控自身的心率數據了。
整體來說整個開發過程還是比較簡單的,畢竟API文檔描述的非常清晰,主要時間耗費在解讀心率數據這個過程,后來知道應該從協議文檔出發解讀就好說了。
由于是在業余時間實現的上述能力,開發過程中存在不少疑惑沒來得及研究。這里先拋兩個問題出來,希望了解相關知識的同學能夠不吝賜教。
- 在arraybuffer轉16進制過程中("00" + bit.toString(16)).slice(-2);?,為什么要先"00" +?,然后再.slice(-2)??直接bit.toString(16)是否可行?
- 針對某米耳機,筆者暫時沒有在服務和特征中找到電量信息相關的數據,那么手機設備又是如何獲取到耳機電量的呢?
猜測電池服務和特性分別是0x180F Battery service 和 0x2A19 Battery Level ,但上圖耳機返回的service列表中沒找到該服務。
本文作者正在等待你的幫助。
參考資料
[1]藍牙低能耗——百度百科: https://baike.baidu.com/item/%E8%93%9D%E7%89%99%E4%BD%8E%E8%83%BD%E8%80%97
[2]一文讀懂藍牙技術從 1.0 到 5.0 的前世今生: https://zhuanlan.zhihu.com/p/37717509
[3]Assigned Numbers | Bluetooth? Technology Website: https://www.bluetooth.com/specifications/assigned-numbers/
[4]BLE相關協議(GAP&GATT): https://www.jianshu.com/p/62eb2f5407c9
[5]Taro.openBluetoothAdapter(option) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth/openBluetoothAdapter
[6]Taro.getBluetoothDevices(option) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth/getBluetoothDevices
[7]Taro.onBluetoothDeviceFound(callback) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth/onBluetoothDeviceFound
[8]Taro.createBLEConnection(option) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth-ble/createBLEConnection
[9]Taro.getBLEDeviceServices(option) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth-ble/getBLEDeviceServices
[10]Taro.getBLEDeviceCharacteristics(option) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth-ble/getBLEDeviceCharacteristics
[11]Taro.readBLECharacteristicValue(option) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth-ble/readBLECharacteristicValue
[12]Taro.notifyBLECharacteristicValueChange(option) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth-ble/notifyBLECharacteristicValueChange
[13]Taro.onBLECharacteristicValueChange(callback) | Taro 文檔: https://docs.taro.zone/docs/apis/device/bluetooth-ble/onBLECharacteristicValueChange
[14]Web Bluetooth API - Web APIs | MDN: https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API
[15]通過 JavaScript 與藍牙設備通信: https://web.dev/i18n/zh/bluetooth/
[16]談談JS二進制:File、Blob、FileReader、ArrayBuffer、Base64 - 掘金: https://juejin.cn/post/7148254347401363463#heading-9