通過 Web 控制藍芽裝置:WebBluetooth入門 [每日前端夜話0x27]

語言: CN / TW / HK

通過 Web 控制藍芽裝置:WebBluetooth入門 [每日前端夜話0x27]

京程一燈 前端先鋒

每日前端夜話0x27
每日前端夜話,陪你聊前端。
每天晚上18:00準時推送。
正文共:5950 字 3 圖
預計閱讀時間: 15 分鐘
翻譯:瘋狂的技術宅
原文:https://www
.smashingmagazine.com/2019/02/introduction-to-webbluetooth/






摘要:通過漸進式 Web 應用(Progressive Web Apps)技術,你可以開發成熟的 Web 應用。 得益於大量新規範和新功能,以前需要在本機執行的應用,現在可以基於 Web 實現。 不過迄今為止,與硬體裝置的互動仍然是遙不可及。 感謝 WebBluetooth 的出現,現在我們可以開發能夠控制燈光、駕駛汽車甚至是無人機的 PWA。


通過PWA技術,Web 應用越來越和本機應用相差無幾。同時 Web 應用也有其額外的好處,比如隱私和跨平臺相容性。

傳統的 Web 應用與網路上伺服器的通訊機制非常出色。現在 Web 正在向本機應用靠攏,所以我們還需要與本機應用相同的功能。

過去幾年在瀏覽器中實現的新規範和功能的數量是驚人的。我們已經有了處理3D的規範,例如 WebGL 和即將推出的 WebGPU。我們可以通過流式傳輸並生成音訊,來觀看視訊並將網路攝像頭用作輸入裝置。我們還可以使用WebAssembly 以幾乎原生的速度執行程式碼。此外,儘管 web 最初只是一種網路媒體,但是它已經轉向 service workers 的離線支援。

儘管這些功能非常強大,但是還有一個本機應用的專屬領域:與裝置進行通訊。這是我們長期以來一直試圖解決的問題,也是每個人可能會遇到的問題。Web非常適合與伺服器通訊,但不適合與裝置通訊。例如,在網路中設定路由器時,您可能需要輸入 IP 地址並通過純 HTTP 連線來使用 Web 介面,但是沒有任何安全保護。這不管是從體驗上還是在安全性上都是非常糟糕的。最重要的是,您怎樣才能得到正確的IP地址呢?

當我們嘗試建立一個試圖與裝置通訊的 PWA 時,HTTP 協議是我們遇到的第一個問題。 PWA 只能只用 HTTPS 協議,而本地裝置始終只使用 HTTP 。您還需要一個 HTTPS 證書,並且為了獲得證書,還需要一個帶有域名的公共伺服器( 我正在談論本地網路上無法訪問的裝置 )。

因此,對於許多裝置來說,您需要使用本機應用來設定並使用它們,因為本機應用不受 Web 平臺的限制,可以為其使用者提供愉快的體驗。但是我並不想下載一個 500 MB 的程式來做到這一點。也許您擁有的裝置已經有幾年了,應用程式從來沒有為支援您的新手機做過更新。也許你想使用桌上型電腦或膝上型電腦,而製造商只提供一個移動應用。也不是一個理想的體驗。

WebBluetooth 是一種新規範,已在 Chrome 和 Samsung Internet 中實現,它允許我們通過瀏覽器直接與Bluetooth Low Energy 裝置進行通訊。 PWA 通過與 WebBluetooth 相結合,可以提供 Web 應用的安全性和便利性,並具有直接與裝置通訊的能力。

由於通訊範圍有限,音訊質量差和配對上存在的問題,藍芽的名聲比較差。但是,幾乎所有這些問題都已成為過去式。 Bluetooth Low Energy 是一種現代規範,除了使用的無線頻段相同外,它和舊的藍芽規範幾乎沒有任何關係。每天有超過 1000 萬臺裝置提供藍芽支援,其中包括計算機和手機,還有各種裝置,如心率和血糖監測儀,物聯網裝置,如燈泡和遙控汽車和無人機等玩具。

無聊的理論部分

由於藍芽本身不是一種網路技術,它使用了一些我們可能不太熟悉的詞彙。 先讓我們看看藍芽是如何工作的和一些涉及到的術語。

每個藍芽裝置都是“中央裝置”(Central device)或“外圍裝置”( Peripheral )。 只有中央裝置才能啟動通訊,並且只能與外圍裝置通訊。 中央裝置可以是計算機或行動電話。

外圍裝置無法啟動通訊,只能與中央裝置通訊。 此外,同一時間外圍裝置只能與一箇中央裝置通訊。 外圍裝置無法與其他外圍裝置通訊。

通過 Web 控制藍芽裝置:WebBluetooth入門 [每日前端夜話0x27]
中央裝置可以與多個外圍裝置通訊
中央裝置可以與多個外圍裝置通訊

中央裝置可以同時與多個外圍裝置通訊,並且可以根據需要對訊息進行中繼。所以心率監測器無法與您的燈泡進行通訊,但是您可以編寫一個程式,該程式在接收心率的中央裝置上執行,如果心率超過某個閾值就將燈變為紅色。

當我們談論 WebBluetooth 時,我們談論的是藍芽規範的一個特定部分,稱為通用屬性配置檔案(Generic Attribute Profile),它的縮寫是GATT。 (顯然,GAP這個縮寫已經被佔用了。)

在 GATT 的支援下,我們不再談論中央裝置和外圍裝置,而是客戶端和伺服器。你的燈泡是伺服器。這可能和你的直覺相違背,但是如果你仔細想想,實際上是有道理的。燈泡提供服務,即光。就像瀏覽器連線到網際網路上的伺服器一樣,您的手機或計算機也是連線到燈泡中 GATT 伺服器的客戶端。

每個伺服器都提供一個或多個服務。其中一些服務正式成為標準的一部分,但您也可以定義自己的服務。比如心率監測器的規範中就定義了官方服務。但是燈泡就不是這樣,而且幾乎每個製造商都在試圖重新發明輪子。每項服務都有一個或多個特徵。每個特徵都有一個可以讀取或寫入的值。目前,最好將其視為一個物件陣列,每個物件都具有值的屬性。

通過 Web 控制藍芽裝置:WebBluetooth入門 [每日前端夜話0x27]
簡化的服務和特徵層次結構
簡化的服務和特徵層次結構。

與物件屬性不同,服務和特徵不是由字串標識的。 每個服務和特性都有一個唯一的UUID,長度為16 位或128位。嚴格的說,16 位 UUID 是為官方標準保留的,但幾乎沒有人遵循這一規則。 最後,每個值都是一個位元組陣列。 藍芽中沒有樣式繁多的資料型別。

親密接觸藍芽燈泡

讓我們看一個實際的藍芽裝置:Mipow Playbulb Sphere。 您可以用 BLE Scanner 或 nRF Connect 等程式連線到裝置並檢視其所有服務和特徵。 在這種情況下,我正在使用iOS的BLE掃描器應用程式。
通過 Web 控制藍芽裝置:WebBluetooth入門 [每日前端夜話0x27]

視訊地址:https://player.vimeo.com/video/303046505

連線燈泡時首先看到的是服務列表。有一些標準化的服務,如裝置資訊服務和電池。但也有一些自定義服務。我對16 位 UUID 為 0xff0f 的服務特別感興趣。如果您開啟此服務,可以看到一長串特徵值。我不知道這些特徵是做什麼用的,因為它們只是由 UUID 識別,而且不幸的是它們可能定製服務的一部分,它們並不是標準化的,製造商沒有提供任何支援文件。

UUID 為 0xfffc 的第一個特性似乎特別有趣。它的值為四個位元組。如果我們將這些位元組的值從 0x00000000 改為 0x00ff0000 ,則燈泡變為紅色。將其改為 0x0000ff00 會將燈泡變為綠色,修改為 0x000000ff 則變為藍色。這些是RGB顏色,和 HTML 與 CSS 中使用的十六進位制顏色完全對應。

第一個位元組有什麼作用?好吧,如果我們將值更改為 0xff000000 ,則燈泡會變成白色。燈泡包含四個不同的LED,通過更改這四個位元組的值,我們可以建立想要的任何顏色。

WebBluetooth API

可以用原生應用來改變燈泡的顏色,這真是太棒了,但是我們怎樣在瀏覽器中做到這一點呢?事實證明,憑藉我們剛剛學到的關於藍芽和 GATT 的知識,只需幾行JavaScript就可以改變燈泡的顏色,這要歸功 於WebBluetooth API。

我們來研究一下 WebBluetooth API。

連線到裝置

我們要做的第一件事就是從瀏覽器連線到裝置。可以呼叫函式 navigator.bluetooth.requestDevice() 併為函式提供配置物件,該物件含有關我們要使用哪個裝置,以及都有哪些服務可用的資訊。

在以下示例中,我們將過濾裝置的名稱,因為我們只想檢視名稱中包含字首 PLAYBULB 的裝置。我們還指定 0xff0f 作為我們想要使用的服務。由於 requestDevice() 函式返回一個promise,可以等待結果返回。


1let device = await navigator.bluetooth.requestDevice({
2    filters: [ 
3        { namePrefix: 'PLAYBULB' } 
4    ],
5    optionalServices: [ 0xff0f ]
6});

-----

當我們呼叫此函式時,會彈出一個視窗,顯示符合過濾規則的裝置列表。 現在必須手動選擇我們想要連線的裝置。這是出於安全和隱私的需要,併為使用者提供控制的權利。使用者決定是否允許 Web 應用連線到裝置,當然還有已經被允許連線的裝置。 如果沒有使用者手動選擇裝置,Web 應用則無法獲取裝置列表或連線。

通過 Web 控制藍芽裝置:WebBluetooth入門 [每日前端夜話0x27]

使用者必須通過選擇裝置來手動連線。
使用者必須通過選擇裝置來手動連線。

在我們訪問裝置之後,可以通過呼叫裝置 gatt 屬性上的 connect() 函式連線到 GATT 伺服器並等待返回結果。


1let server = await device.gatt.connect();

一旦我們連上伺服器,就可以呼叫 getPrimaryService() 並傳遞服務的UUID,然後等待結果返回。


1let service = await server.getPrimaryService(0xff0f);

然後使用特性的UUID作為引數呼叫服務上的 getCharacteristic() 並再次等待結果返回。

現在就得到了可用於讀寫資料的特性:


1let characteristic = await service.getCharacteristic(0xfffc);

寫資料

要寫入資料,我們可以在特性上呼叫函式 writeValue() ,以 ArrayBuffer 的形式傳遞想要寫入的值 ,這是二進位制資料的儲存方法。 我們不能使用常規陣列的原因是常規陣列中可以包含各種型別的資料,甚至可以存在空洞。

由於我們無法直接建立或修改 ArrayBuffer,因此應該使用“型別化陣列”。 型別化陣列種的每個元素總是相同的型別,並且沒有任何漏洞。 在我們的例子中,將使用 Uint8Array,它是一個無符號的整數,因此不能包含任何負數,也它不能包含分數; 它是 8 位的,只能包含 0 到 255 之間的值。換句話說:這個是一個位元組陣列。


1characteristic.writeValue(
2    new Uint8Array([ 0, r, g, b  ])
3);

我們已經知道這個特殊的燈泡是如何工作的。 必須提供四個位元組,每個LED一個。 每個位元組的值介於 0 到 255 之間,在這種情況下,我們只想使用紅色,綠色和藍色 LED,因此我們使用值 0 關閉白色LED。

讀資料

要讀取燈泡的當前顏色,可以使用 readValue() 函式並等待結果返回。


1let value = await characteristic.readValue();
2
3let r = value.getUint8(1); 
4let g = value.getUint8(2);
5let b = value.getUint8(3);

我們得到的值是 ArrayBuffer 形式的 DataView,它提供了一種從 ArrayBuffer 中獲取資料的方法。 在我們的例子中,可以使用 getUint8() 並以索引作為引數來從陣列中提取單個位元組。

獲得通知變更

最後,還有一種方法可以在裝置值發生變化時收到通知。 這對於燈泡來說並不是很有用,但對於心率監測器來說,我們需要不斷收到改變的值,而且並不希望每秒手動輪詢這些值。


1characteristic.addEventListener(
2    'characteristicvaluechanged', e => {
3        let r = e.target.value.getUint8(1); 
4        let g = e.target.value.getUint8(2);
5        let b = e.target.value.getUint8(3);
6    }
7);
8
9characteristic.startNotifications();

要在值發生變化時及時獲得回撥,必須使用引數 characteristicvaluechanged 和回撥函式呼叫特性上的 addEventListener() 函式。 每當值發生變化時,將使用事件物件作為引數呼叫回撥函式,並且我們可以從事件目標的 value 屬性中獲取資料。 最後,再次從 ArrayBuffer 的 DataView 中提取單個位元組。

由於藍芽網路上的頻寬有限,我們必須通過呼叫特性上的 startNotifications() 來手動啟動這個通知機制。 否則,網路將被不必要的資料淹沒。 此外,由於這些裝置通常使用電池供電,因此沒有必要的資料通訊會影響裝置的電池壽命,所以內建無線發射器不需要常開。

結論

本文已經覆蓋了 WebBluetooth API 的90%。 只需呼叫幾個函式併發送 4 個位元組,您就可以建立一個控制燈泡顏色的 Web 應用。 如果再新增幾行,您甚至可以控制玩具車或駕駛無人機。 隨著越來越多的藍芽裝置進入市場,將產生無窮的可能性。
通過 Web 控制藍芽裝置:WebBluetooth入門 [每日前端夜話0x27]
https://v.qq.com/x/page/g1359srvs8z.html

更多資源