使用 Chrome Web 藍(lán)牙 API 構(gòu)建藍(lán)牙應(yīng)用
如今,瀏覽器不斷發(fā)展,帶來了新的 API 和連接其他設(shè)備的方式,并允許訪問比以往更多的功能。其中一種 API 是 Web 藍(lán)牙 API[1]。
在撰寫本文時(shí),該 API 仍處于測試階段,但一旦向公眾發(fā)布,它將為想要使用藍(lán)牙但不想為每個(gè)平臺(tái)創(chuàng)建原生應(yīng)用程序的開發(fā)人員提供大量機(jī)會(huì)。
盡管藍(lán)牙 API 仍處于測試階段,但我們會(huì)嘗試并制作一個(gè)簡單的網(wǎng)頁,該網(wǎng)頁將與我們的手機(jī)配對(duì)并提供基本詳細(xì)信息,例如電池百分比、設(shè)備名稱以及設(shè)備提供的基本信息制造商。
我們不會(huì)在本教程中使用樣式,因?yàn)槲覀冎恍枰私馊绾问褂?JavaScript 與藍(lán)牙 API 交互。
請(qǐng)記住,并非所有瀏覽器都支持此 API,您將無法在每部手機(jī)上進(jìn)行測試。某些手機(jī)可能不允許獲取設(shè)備信息。在本教程中,我將使用 Apple iPhone 11,它允許我通過瀏覽器上的藍(lán)牙獲取我的設(shè)備信息而不會(huì)出現(xiàn)任何問題。
前提
- 一個(gè)代碼編輯器;我更喜歡 VS Code
- 使用 VS Code 時(shí)的實(shí)時(shí)服務(wù)器擴(kuò)展
- 具有藍(lán)牙功能(或即插即用藍(lán)牙硬件)的筆記本電腦或 PC
- 有藍(lán)牙功能的移動(dòng)設(shè)備(我用的是iPhone 11,你可以用自己的手機(jī)試試)
- 對(duì)JavaScript有一定的了解
- Chrome Beta 安裝在您的 PC 或筆記本電腦上。藍(lán)牙 API 是 Beta 版功能,在 Chrome Beta 版上效果最佳。
請(qǐng)注意,并非所有基于 Chromium 的瀏覽器(例如 Brave)都支持藍(lán)牙 API。我嘗試在 Brave 上使用 API,但發(fā)現(xiàn) Brave 出于安全原因故意禁用了 API。
如果你對(duì)代碼需要任何幫助,這里是[GitHub倉庫](https://github.com/atharvadeosthale/web-bluetooth-phone)。
讓我們開始吧
首先,我們需要?jiǎng)?chuàng)建一個(gè)文件夾,我們將把它作為一個(gè)工作區(qū)。一旦你創(chuàng)建了一個(gè)文件夾,使用以下命令打開VS Code:
code .
我們將在本教程中使用兩個(gè)文件;將它們命名為 index.html 和 script.js。在 index.html 中,我們只需要基本布局(只是一個(gè)按鈕),并將文件鏈接到我們的 JavaScript 文件。
以下是 index.html 的內(nèi)容:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Document</title>
- </head>
- <body>
- <button id="getDetails">Get device details</button>
- <div id="details"></div>
- <script src="script.js"></script>
- </body>
- </html>
添加藍(lán)牙功能
讓我們從功能開始。轉(zhuǎn)到 script.js 并將 UI 元素存儲(chǔ)在變量中,以便我們以后可以訪問它們:
- const button = document.getElementById("getDetails");
- const details = document.getElementById("details");
現(xiàn)在,讓我們?yōu)槲覀兊陌粹o創(chuàng)建一個(gè)點(diǎn)擊監(jiān)聽器,這樣我們就可以在用戶點(diǎn)擊按鈕時(shí)執(zhí)行我們的操作:
- button.addEventListener("click", async () => {
- try {
- } catch(err) {
- console.error(err);
- alert("An error occured while fetching device details");
- }
- });
我們將該函數(shù)設(shè)為異步函數(shù),因?yàn)樗刮覀兊氖虑樽兊酶唵?,而且我們不需要進(jìn)行很多回調(diào),使我們的代碼看起來更有條理。從現(xiàn)在開始,我們所有的代碼都將在 try 塊中。
請(qǐng)求藍(lán)牙設(shè)備
接下來,讓我們通過瀏覽器請(qǐng)求藍(lán)牙設(shè)備:
- // 通過瀏覽器請(qǐng)求藍(lán)牙設(shè)備
- const device = await navigator.bluetooth.requestDevice({
- optionalServices: ["battery_service", "device_information"],
- acceptAllDevices: true,
- });
在上面的代碼中,我們通過 navigator.bluetooth 使用了藍(lán)牙 API。在連接到設(shè)備之前,我們需要向設(shè)備提供有關(guān)我們將要訪問哪些數(shù)據(jù)的信息。
我們可以使用目標(biāo)藍(lán)牙設(shè)備上存在的各種服務(wù)訪問所需的數(shù)據(jù)。在這種情況下,我們正在與電池和設(shè)備信息進(jìn)行交互,因此我們需要 Battery_service 和 device_information 服務(wù)。
一旦用戶選擇了他想要連接的藍(lán)牙設(shè)備,我們就會(huì)建立到 GATT 服務(wù)器的連接,它為我們提供了對(duì)我們之前請(qǐng)求的服務(wù)的訪問,并將設(shè)備名稱存儲(chǔ)在一個(gè)變量中以供以后使用:
- // 連接到 GATT 服務(wù)器
- // 我們還在這里獲得了藍(lán)牙設(shè)備的名稱
- let deviceName = device.gatt.device.name;
- const server = await device.gatt.connect();
現(xiàn)在,我們需要從 GATT 服務(wù)器單獨(dú)獲取服務(wù),以便我們可以單獨(dú)訪問它們:
- // 通過 GATT 服務(wù)器獲取我們之前提到的服務(wù)
- const batteryService = await server.getPrimaryService("battery_service");
- const infoService = await server.getPrimaryService("device_information");
從設(shè)備獲取信息
首先,讓我們獲取目標(biāo)設(shè)備的電池電量。
每個(gè)藍(lán)牙設(shè)備都有各種服務(wù)可以互動(dòng)。例如,一個(gè)移動(dòng)設(shè)備可以有一個(gè)電池服務(wù),用于所有電池活動(dòng)。還可以提供幫助撥打和接聽電話的電話服務(wù)。不同的設(shè)備都有不同的藍(lán)牙服務(wù)。
每個(gè)服務(wù)都有特征,每個(gè)特征都有一個(gè)值。這個(gè)值是一個(gè)緩沖區(qū),所以我們需要把它轉(zhuǎn)換成人類可讀的形式。
電池電量是一個(gè)百分比,因此我們將緩沖區(qū)轉(zhuǎn)換為整數(shù):
- // 獲取當(dāng)前電池電量
- const batteryLevelCharacteristic = await batteryService.getCharacteristic(
- "battery_level"
- );
- // 將收到的緩沖區(qū)轉(zhuǎn)換為數(shù)字
- const batteryLevel = await batteryLevelCharacteristic.readValue();
- const batteryPercent = await batteryLevel.getUint8(0);
readValue() 函數(shù)返回一個(gè)緩沖區(qū),我們需要將其轉(zhuǎn)換為人類可讀的形式。
現(xiàn)在,讓我們努力獲取更多設(shè)備信息。如前所述,每項(xiàng)服務(wù)都有一個(gè)或多個(gè)特征。device_information 服務(wù)根據(jù)設(shè)備的不同可能有相當(dāng)多的特征,我們無法提取一個(gè)特定的特征,因?yàn)槊總€(gè)設(shè)備都有不同的配置和不同的唯一 ID 來訪問數(shù)據(jù)。因此,我們只需讀取本例中的所有特征。
下面的代碼就是這樣做的:
- // 獲取設(shè)備信息
- // 我們將從 device_information 中獲取所有特征
- const infoCharacteristics = await infoService.getCharacteristics();
- console.log(infoCharacteristics);
- let infoValues = [];
- const promise = new Promise((resolve, reject) => {
- infoCharacteristics.forEach(async (characteristic, index, array) => {
- // Returns a buffer
- const value = await characteristic.readValue();
- console.log(new TextDecoder().decode(value));
- // Convert the buffer to string
- infoValues.push(new TextDecoder().decode(value));
- if (index === array.length - 1) resolve();
- });
- });
我們將 forEach 包裝在 Promise 下,因?yàn)楦讣?jí)和 forEach 本身是一個(gè)異步函數(shù),因此我們需要在繼續(xù)顯示數(shù)據(jù)之前獲取數(shù)據(jù)。在這里,當(dāng)我們使用 readValue() 獲取值時(shí),我們使用的是 TextDecoder,因?yàn)槲覀冎?device_information 服務(wù)中的大部分?jǐn)?shù)據(jù)是字符串類型而不是整數(shù)。
然后我們將所有數(shù)據(jù)推送到一個(gè)數(shù)組中,以便我們可以在 UI 上呈現(xiàn)它,然后在讀取所有特征后解析 Promise。
現(xiàn)在,我們只需在屏幕上渲染數(shù)據(jù):
- promise.then(() => {
- // 在屏幕上顯示所有信息
- // 使用innerHTML
- details.innerHTML = `
- Device Name - ${deviceName}<br />
- Battery Level - ${batteryPercent}%<br />
- Device Information:
- <ul>
- ${infoValues.map((value) => `<li>${value}</li>`).join("")}
- </ul>
- `;
- });
現(xiàn)在,當(dāng)您在 Chrome Beta 上運(yùn)行我們的Web應(yīng)用程序并單擊該按鈕時(shí),您應(yīng)該會(huì)看到一個(gè)連接藍(lán)牙設(shè)備的提示,如下所示:
一旦你選擇了你的手機(jī)(在我的例子中是 Atharva 的 iPhone)并點(diǎn)擊配對(duì),你應(yīng)該會(huì)在幾秒鐘內(nèi)看到屏幕上的信息,就像這樣:
信息是正確的,我截圖的時(shí)候,我的手機(jī)是百分之百開著的。
這里需要注意的一件事是 iPhone 12,1 ,這不是說我有 iPhone12,它是 iPhone 11 的代號(hào),因此,如果您看到一些奇怪的設(shè)備名稱,您應(yīng)該知道它可能是代號(hào)或制造商提供的其他名稱。
你應(yīng)該使用藍(lán)牙API嗎?
這是最重要的問題。此功能在大多數(shù)瀏覽器中處于測試階段,即使向公眾發(fā)布,也可能存在一些問題,例如硬件不支持藍(lán)牙。如果您想為某人創(chuàng)建鏈接其設(shè)備的服務(wù),則應(yīng)牢記這一點(diǎn)。
另一方面,如果你的組織有正確配置了藍(lán)牙的定制系統(tǒng),你肯定可以為組織創(chuàng)建一個(gè)內(nèi)部Web應(yīng)用,可以根據(jù)他們的需要與藍(lán)牙設(shè)備進(jìn)行互動(dòng)。
我認(rèn)為你應(yīng)該在這個(gè)API處于測試階段時(shí)嘗試一下,因?yàn)橐话銇碚f,當(dāng)它向公眾發(fā)布時(shí),你就會(huì)占到先機(jī)。沒有多少人會(huì)知道如何使用這個(gè)API,所以對(duì)它的了解可以幫助你獲得更多的演出機(jī)會(huì)。
在測試階段使用這個(gè)的另一個(gè)原因是為了挑戰(zhàn)自己。當(dāng)API被發(fā)布后,事情可能會(huì)變得更容易。但如果你像我一樣喜歡玩API測試版,你可能會(huì)有一些樂趣,并在這個(gè)過程中學(xué)習(xí)一些新東西。
一旦API向公眾發(fā)布,就會(huì)產(chǎn)生更多的意識(shí),在我看來,越來越多的藍(lán)牙相關(guān)服務(wù)將在Web上而不是在原生應(yīng)用程序中進(jìn)行。這將使這項(xiàng)技術(shù)更容易為Web開發(fā)者所接受。
原文:https://blog.logrocket.com/build-bluetooth-app-chrome-bluetooth-web-api/
作者:Atharva Deosthale
參考資料
[1]Web 藍(lán)牙 API: https://www.chromestatus.com/feature/5264933985976320