經過 Web 控制藍牙設備:WebBluetooth入門

翻譯:瘋狂的技術宅javascript

原文:www.smashingmagazine.com/2019/02/int…前端


摘要:經過漸進式 Web 應用(Progressive Web Apps)技術,你能夠開發成熟的 Web 應用。 得益於大量新規範和新功能,之前須要在本機執行的應用,如今能夠基於 Web 實現。 不過迄今爲止,與硬件設備的交互仍然是高不可攀。 感謝 WebBluetooth 的出現,如今咱們能夠開發可以控制燈光、駕駛汽車甚至是無人機的 PWA。java


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

傳統的 Web 應用與網絡上服務器的通訊機制很是出色。如今 Web 正在向本機應用靠攏,因此咱們還須要與本機應用相同的功能。github

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

儘管這些功能很是強大,可是還有一個本機應用的專屬領域:與設備進行通訊。這是咱們長期以來一直試圖解決的問題,也是每一個人可能會遇到的問題。Web很是適合與服務器通訊,但不適合與設備通訊。例如,在網絡中設置路由器時,你可能須要輸入 IP 地址並經過純 HTTP 鏈接來使用 Web 界面,可是沒有任何安全保護。這不論是從體驗上仍是在安全性上都是很是糟糕的。最重要的是,你怎樣才能獲得正確的IP地址呢?vim

當咱們嘗試建立一個試圖與設備通訊的 PWA 時,HTTP 協議是咱們遇到的第一個問題。 PWA 只能只用 HTTPS 協議,而本地設備始終只使用 HTTP 。你還須要一個 HTTPS 證書,而且爲了得到證書,還須要一個帶有域名的公共服務器( 我正在談論本地網絡上沒法訪問的設備 )。數組

所以,對於許多設備來講,你須要使用本機應用來設置並使用它們,由於本機應用不受 Web 平臺的限制,能夠爲其用戶提供愉快的體驗。可是我並不想下載一個 500 MB 的程序來作到這一點。也許你擁有的設備已經有幾年了,應用程序歷來沒有爲支持你的新手機作過更新。也許你想使用臺式機或筆記本電腦,而製造商只提供一個移動應用。也不是一個理想的體驗。promise

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

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

無聊的理論部分

因爲藍牙自己不是一種網絡技術,它使用了一些咱們可能不太熟悉的詞彙。 先讓咱們看看藍牙是如何工做的和一些涉及到的術語。

每一個藍牙設備都是「中央設備」(Central device)或「外圍設備」( Peripheral )。 只有中央設備才能啓動通訊,而且只能與外圍設備通訊。 中央設備能夠是計算機或移動電話。

外圍設備沒法啓動通訊,只能與中央設備通訊。 此外,同一時間外圍設備只能與一箇中央設備通訊。 外圍設備沒法與其餘外圍設備通訊。

中央設備能夠與多個外圍設備通訊

中央設備能夠與多個外圍設備通訊

中央設備能夠同時與多個外圍設備通訊,而且能夠根據須要對消息進行中繼。因此心率監測器沒法與你的燈泡進行通訊,可是你能夠編寫一個程序,該程序在接收心率的中央設備上運行,若是心率超過某個閾值就將燈變爲紅色。

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

在 GATT 的支持下,咱們再也不談論中央設備和外圍設備,而是客戶端和服務器。你的燈泡是服務器。這可能和你的直覺相違背,可是若是你仔細想一想,其實是有道理的。燈泡提供服務,即光。就像瀏覽器鏈接到互聯網上的服務器同樣,你的手機或計算機也是鏈接到燈泡中 GATT 服務器的客戶端。

每一個服務器都提供一個或多個服務。其中一些服務正式成爲標準的一部分,但你也能夠定義本身的服務。好比心率監測器的規範中就定義了官方服務。可是燈泡就不是這樣,並且幾乎每一個製造商都在試圖從新發明輪子。每項服務都有一個或多個特徵。每一個特徵都有一個能夠讀取或寫入的值。目前,最好將其視爲一個對象數組,每一個對象都具備值的屬性。

簡化的服務和特徵層次結構

簡化的服務和特徵層次結構。

與對象屬性不一樣,服務和特徵不是由字符串標識的。 每一個服務和特性都有一個惟一的UUID,長度爲16 位或128位。嚴格的說,16 位 UUID 是爲官方標準保留的,但幾乎沒有人遵循這一規則。 最後,每一個值都是一個字節數組。 藍牙中沒有樣式繁多的數據類型。

親密接觸藍牙燈泡

讓咱們看一個實際的藍牙設備:Mipow Playbulb Sphere。 你能夠用 BLE Scanner 或 nRF Connect 等程序鏈接到設備並查看其全部服務和特徵。 在這種狀況下,我正在使用iOS的BLE掃描儀應用程序。

視頻地址:player.vimeo.com/video/30304…

鏈接燈泡時首先看到的是服務列表。有一些標準化的服務,如設備信息服務和電池。但也有一些自定義服務。我對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,能夠等待結果返回。

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,所以咱們使用值 0 關閉白色LED。

讀數據

要讀取燈泡的當前顏色,可使用 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() 來手動啓動這個通知機制。 不然,網絡將被沒必要要的數據淹沒。 此外,因爲這些設備一般使用電池供電,所以沒有必要的數據通訊會影響設備的電池壽命,因此內置無線發射器不須要常開。

視頻地址:player.vimeo.com/video/30304…

結論

本文已經覆蓋了 WebBluetooth API 的90%。 只需調用幾個函數併發送 4 個字節,你就能夠建立一個控制燈泡顏色的 Web 應用。 若是再添加幾行,你甚至能夠控制玩具車或駕駛無人機。 隨着愈來愈多的藍牙設備進入市場,將產生無窮的可能性。

更多資源

本文首發微信公衆號:jingchengyideng

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章
相關文章
相關標籤/搜索