網絡藍牙簡介
已發表: 2022-03-10借助 Progressive Web Apps,Web 越來越接近原生應用程序。 但是,具有網絡固有的額外好處,例如隱私和跨平台兼容性。
傳統上,Web 非常擅長與網絡上的服務器通信,特別是與 Internet 上的服務器通信。 現在 Web 正在向應用程序發展,我們還需要原生應用程序具有的相同功能。
過去幾年在瀏覽器中實現的新規範和功能的數量是驚人的。 我們已經制定了處理 3D 的規範,例如 WebGL 和即將推出的 WebGPU。 我們可以流式傳輸和生成音頻、觀看視頻並將網絡攝像頭用作輸入設備。 我們還可以使用 WebAssembly 以幾乎原生的速度運行代碼。 此外,儘管最初是一種僅限網絡的媒體,但網絡已經轉向由服務人員提供的離線支持。
這很好,但有一個領域幾乎是原生應用程序的專屬領域:與設備通信。 這是我們長期以來一直試圖解決的問題,而且每個人都可能在某一時刻遇到過。 網絡非常適合與服務器交談,但不適用於與設備交談。 例如,考慮嘗試在您的網絡中設置路由器。 您可能必須輸入 IP 地址並通過純 HTTP 連接使用 Web 界面,而沒有任何安全性。 那隻是糟糕的體驗和糟糕的安全性。 最重要的是,您如何知道正確的 IP 地址是什麼?
當我們嘗試創建一個嘗試與設備對話的漸進式 Web 應用程序時,HTTP 也是我們遇到的第一個問題。 PWA 僅支持 HTTPS,本地設備始終只是 HTTP。 您需要一個 HTTPS 證書,並且為了獲得證書,您需要一個具有域名的公開可用的服務器(我說的是我們本地網絡上無法訪問的設備)。
因此,對於許多設備,您需要原生應用程序來設置和使用設備,因為原生應用程序不受 Web 平台的限制,可以為用戶提供愉快的體驗。 但是,我不想下載一個 500 MB 的應用程序來做到這一點。 也許您擁有的設備已經使用了幾年,並且該應用程序從未更新為在您的新手機上運行。 也許您想使用台式機或筆記本電腦,而製造商只構建了一個移動應用程序。 也不是理想的體驗。
WebBluetooth 是一種已在 Chrome 和三星 Internet 中實施的新規範,它允許我們從瀏覽器直接與藍牙低功耗設備進行通信。 漸進式 Web 應用程序與 WebBluetooth 相結合,提供 Web 應用程序的安全性和便利性,並具有直接與設備對話的能力。
由於範圍有限、音頻質量差和配對問題,藍牙的名聲很差。 但是,幾乎所有這些問題都已成為過去。 低功耗藍牙是一種現代規範,除了使用相同的頻譜之外,與舊藍牙規範幾乎沒有關係。 每天有超過 1000 萬台設備支持藍牙。 這包括電腦和手機,還包括心率和血糖監測儀等各種設備、燈泡等物聯網設備以及遙控汽車和無人機等玩具。
推薦閱讀:了解基於 API 的平台:產品經理指南
無聊的理論部分
由於藍牙本身不是一種網絡技術,它使用了一些我們可能不熟悉的詞彙。 因此,讓我們回顧一下藍牙的工作原理和一些術語。
每個藍牙設備要么是“中央設備”,要么是“外圍設備”。 只有中央設備可以發起通信,並且只能與外圍設備通信。 中央設備的一個示例是計算機或移動電話。
外圍設備無法啟動通信,只能與中央設備通信。 此外,外圍設備只能同時與一個中央設備通信。 外圍設備不能與另一個外圍設備通信。

中央設備可以同時與多個外圍設備通信,並且可以根據需要中繼消息。 因此,心率監測器無法與您的燈泡通信,但是,您可以編寫一個在中央設備上運行的程序,該程序會接收您的心率並在心率超過某個閾值時將燈變為紅色。
當我們談論 WebBluetooth 時,我們談論的是藍牙規範的一個特定部分,稱為 Generic Attribute Profile,它有一個非常明顯的縮寫 GATT。 (顯然,GAP 已經被採用了。)
在 GATT 的上下文中,我們不再談論中央設備和外圍設備,而是客戶端和服務器。 你的燈泡是服務器。 這可能看起來違反直覺,但如果你仔細想想,它實際上是有道理的。 燈泡提供服務,即光。 就像瀏覽器連接到 Internet 上的服務器一樣,您的手機或計算機是一個客戶端,連接到燈泡中的 GATT 服務器。
每台服務器都提供一項或多項服務。 其中一些服務是標準的正式一部分,但您也可以定義自己的服務。 對於心率監測器,規範中定義了官方服務。 就燈泡而言,沒有,幾乎每個製造商都試圖重新發明輪子。 每個服務都有一個或多個特徵。 每個特徵都有一個可以讀取或寫入的值。 現在,最好將其視為一個對像數組,每個對像都有具有值的屬性。

與對象的屬性不同,服務和特徵不是由字符串標識的。 每個服務和特性都有一個唯一的 UUID,它可以是 16 位或 128 位長。 正式地,16 位 UUID 是為官方標准保留的,但幾乎沒有人遵循該規則。 最後,每個值都是一個字節數組。 藍牙中沒有花哨的數據類型。
近距離觀察藍牙燈泡
因此,讓我們看一個實際的藍牙設備:Mipow Playbulb Sphere。 您可以使用 BLE Scanner 或 nRF Connect 等應用程序連接到設備並查看所有服務和特徵。 在這種情況下,我使用的是適用於 iOS 的 BLE Scanner 應用程序。
當您連接到燈泡時,您首先看到的是服務列表。 有一些標準化的,如設備信息服務和電池服務。 但也有一些定制服務。 我對0xff0f
的 16 位 UUID 的服務特別感興趣。 如果你打開這個服務,你可以看到一長串特徵。 我不知道這些特徵中的大多數是做什麼的,因為它們僅由 UUID 標識,並且不幸的是它們是自定義服務的一部分; 它們沒有標準化,製造商也沒有提供任何文件。

UUID 為0xfffc
的第一個特徵似乎特別有趣。 它有四個字節的值。 如果我們將這些字節的值從0x00000000
更改為0x00ff0000
,燈泡就會變成紅色。 將其更改為0x0000ff00
會將燈泡變為綠色,將0x000000ff
變為藍色。 這些是 RGB 顏色,與我們在 HTML 和 CSS 中使用的十六進制顏色完全對應。
第一個字節有什麼作用? 好吧,如果我們將值更改為0xff000000
,燈泡就會變成白色。 燈泡包含四個不同的 LED,通過更改四個字節中每個字節的值,我們可以創建我們想要的每種顏色。
網絡藍牙 API
我們可以使用本機應用程序來更改燈泡的顏色真是太棒了,但是我們如何從瀏覽器中做到這一點呢? 事實證明,有了我們剛剛學習的藍牙和GATT的知識,這要歸功於WebBluetooth API,這還是比較簡單的。 只需要幾行 JavaScript 就可以改變燈泡的顏色。
讓我們回顧一下 WebBluetooth API。
連接到設備
我們需要做的第一件事是從瀏覽器連接到設備。 我們調用函數navigator.bluetooth.requestDevice()
並為函數提供一個配置對象。 該對象包含有關我們想要使用的設備以及我們的 API 應該可以使用哪些服務的信息。
在以下示例中,我們過濾設備名稱,因為我們只想查看名稱中包含前綴PLAYBULB
的設備。 我們還將0xff0f
指定為我們要使用的服務。 由於requestDevice()
函數返回一個 Promise,我們可以等待結果。
let device = await navigator.bluetooth.requestDevice({ filters: [ { namePrefix: 'PLAYBULB' } ], optionalServices: [ 0xff0f ] });
當我們調用此函數時,會彈出一個窗口,其中包含符合我們指定過濾器的設備列表。 現在我們必須手動選擇要連接的設備。 這是安全和隱私的重要步驟,並為用戶提供控制權。 用戶決定是否允許 Web 應用程序連接,當然還決定允許連接到哪個設備。 如果用戶不手動選擇設備,Web 應用程序無法獲取設備列表或連接。

訪問設備後,我們可以通過調用設備的gatt
屬性上的connect()
函數連接到 GATT 服務器並等待結果。
let server = await device.gatt.connect();
一旦我們有了服務器,我們就可以在服務器上調用getPrimaryService()
並使用我們想要使用的服務的 UUID 作為參數並等待結果。
let service = await server.getPrimaryService(0xff0f);
然後使用特徵的 UUID 作為參數在服務上調用getCharacteristic()
並再次等待結果。
我們現在有了可以用來寫入和讀取數據的特徵:
let characteristic = await service.getCharacteristic(0xfffc);
寫入數據
要寫入數據,我們可以在特徵上調用函數writeValue()
,將我們要寫入的值作為ArrayBuffer,這是一種二進制數據的存儲方法。 我們不能使用正則數組的原因是正則數組可以包含各種類型的數據,甚至可以有空洞。
由於我們無法直接創建或修改 ArrayBuffer,因此我們使用“類型化數組”來代替。 類型化數組的每個元素總是相同的類型,並且沒有任何漏洞。 在我們的例子中,我們將使用Uint8Array
,它是無符號的,因此它不能包含任何負數; 一個整數,所以它不能包含分數; 它是 8 位,只能包含 0 到 255 之間的值。換句話說:字節數組。
characteristic.writeValue( new Uint8Array([ 0, r, g, b ]) );
我們已經知道這個特殊的燈泡是如何工作的。 我們必須提供四個字節,每個 LED 一個。 每個字節都有一個介於 0 和 255 之間的值,在這種情況下,我們只想使用紅色、綠色和藍色 LED,因此我們將白色 LED 關閉,使用值 0。
讀取數據
要讀取燈泡的當前顏色,我們可以使用readValue()
函數並等待結果。
let value = await characteristic.readValue(); let r = value.getUint8(1); let g = value.getUint8(2); let b = value.getUint8(3);
我們返回的值是一個 ArrayBuffer 的 DataView,它提供了一種從 ArrayBuffer 中獲取數據的方法。 在我們的例子中,我們可以使用帶有索引的getUint8()
函數作為參數來從數組中提取單個字節。
收到更改通知
最後,還有一種方法可以在設備的值發生變化時得到通知。 這對於燈泡來說並不是很有用,但對於我們的心率監測器來說,我們的值是不斷變化的,我們不想每秒鐘手動輪詢當前值。
characteristic.addEventListener( 'characteristicvaluechanged', e => { let r = e.target.value.getUint8(1); let g = e.target.value.getUint8(2); let b = e.target.value.getUint8(3); } ); characteristic.startNotifications();
要在值更改時獲取回調,我們必須在帶有參數characteristicvaluechanged
和回調函數的特徵上調用addEventListener()
函數。 每當值發生變化時,都會以事件對象為參數調用回調函數,我們可以從事件目標的 value 屬性中獲取數據。 最後,再次從 ArrayBuffer 的 DataView 中提取單個字節。
由於藍牙網絡的帶寬是有限的,我們必須通過在特性上調用startNotifications()
來手動啟動這個通知機制。 否則,網絡將被不必要的數據淹沒。 此外,由於這些設備通常使用電池,因此我們不必發送的每個字節都將最終提高設備的電池壽命,因為不需要經常打開內部無線電。
結論
我們現在已經完成了 90% 以上的 WebBluetooth API。 只需幾個函數調用並發送 4 個字節,您就可以創建一個控制燈泡顏色的 Web 應用程序。 如果再添加幾行代碼,您甚至可以控制玩具車或駕駛無人機。 隨著越來越多的藍牙設備進入市場,可能性是無窮無盡的。
更多資源
- 藍牙.rocks! 演示 | (GitHub上的源代碼)
- “網絡藍牙規範”,網絡藍牙社區組
- Open GATT Registry 用於低功耗藍牙設備的通用屬性服務的非官方文檔集合。