透過 JavaScript 與藍牙裝置通訊

Web Bluetooth API 可讓網站與藍牙裝置通訊。

François Beaufort
François Beaufort

如果我說過網站可以與附近的藍牙裝置通訊,該怎麼辦? 能更安全且保護隱私權呢?因此透過這種方式 燈泡和海龜都可以直接與網站互動。

在此之前,使用者或許可以與藍牙裝置互動 但僅適用於平台專屬應用程式Web Bluetooth API 的目標是 並匯入網路瀏覽器

課前說明

本文假設您已具備一些有關藍牙低功耗的基本知識 能源 (BLE) 和一般屬性設定檔可正常運作。

雖然 Web Bluetooth API 規格尚未結束, 許多作者都積極尋找熱心的開發人員試用這個 API, 針對規格提供意見對導入作業的意見回饋

ChromeOS、Chrome for Android 都提供部分的 Web Bluetooth API 6.0、Mac (Chrome 56) 和 Windows 10 (Chrome 70)。也就是說,您應該 要求連線至附近的藍牙低功耗裝置。 讀取/寫入藍牙特性、接收 GATT 通知、瞭解 藍牙裝置連線中斷,甚至讀取和寫入 藍牙描述元。詳情請參閱 MDN 的瀏覽器相容性表格 可能不準確或不適當

如果是 Linux 和較舊版本的 Windows,請啟用 about://flags 中的 #experimental-web-platform-features 標記。

適用於來源試用

使用網頁版服務,盡可能向開發人員收集意見 Chrome 已在實地運用 Bluetooth API,因此 Chrome 先前已在 Chrome 中新增這項功能 53 是一項適用於 ChromeOS、Android 和 Mac 的來源試用

試用期已順利結束於 2017 年 1 月。

安全性需求

如要瞭解安全性的取捨,建議您參考「網路藍牙安全性」 Chrome 團隊軟體工程師 Jeffrey Yasskin 的貼文 Web Bluetooth API 規格

僅限 HTTPS

這個實驗性 API 是新增在網路上的強大新功能 只提供給安全內容。也就是說,建立應用程式時 考量TLS

使用者必須做出手勢

偵測藍牙裝置是安全功能 navigator.bluetooth.requestDevice 必須經由使用者手勢觸發,例如 以輕觸或點擊的方式傳送我們是收聽 pointerupclicktouchend 事件。

button.addEventListener('pointerup', function(event) {
  // Call navigator.bluetooth.requestDevice
});

進入程式碼

Web Bluetooth API 依賴 JavaScript Promise。如果不是 熟悉這些功能,歡迎參考這個絕佳的 Promise 教學課程。還有一件事 () => {} 是 ECMAScript 2015 年箭頭函式

要求藍牙裝置

此版本的 Web Bluetooth API 規格允許網站在 扮演核心角色,可透過 BLE 連線連至遠端 GATT 伺服器。這項服務 支援在實作藍牙 4.0 以上版本的裝置之間進行通訊。

網站要求存取鄰近裝置時: navigator.bluetooth.requestDevice,瀏覽器會透過裝置提示使用者 透過選擇器選擇一部裝置或取消要求。

藍牙裝置使用者提示。

navigator.bluetooth.requestDevice() 函式採用必要物件 定義篩選器。這些篩選條件只會傳回符合特定條件的裝置 宣傳藍牙 GATT 服務和/或裝置名稱。

服務篩選器

例如,要求要求宣傳藍牙 GATT 的藍牙裝置 電池服務

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });

如果您的藍牙 GATT 服務未出現在標準化藍牙 GATT 服務但您可以提供完整的藍牙 UUID 或簡短 16 或 32 位元格式。

navigator.bluetooth.requestDevice({
  filters: [{
    services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
  }]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

名稱篩選器

您也可以根據要宣傳的裝置名稱來請求藍牙裝置 運算子:name 篩選器鍵,或這個名稱的前置字串 (也就是 namePrefix) 篩選器鍵。請注意,在本範例中,您也需要定義 optionalServices 金鑰:可存取 服務篩選器否則,當您嘗試存取應用程式時,系統將顯示錯誤訊息 具體做法是指示 Kubernetes 建立並維護 一或多個代表這些 Pod 的物件

navigator.bluetooth.requestDevice({
  filters: [{
    name: 'Francois robot'
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

製造商資料篩選器

您也可根據製造商要求使用藍牙裝置 來宣傳特定資料。manufacturerData這組金鑰 是物件的陣列,其中包含必要的藍牙公司 ID 金鑰, companyIdentifier。您也能提供資料前置字串 製造商所提供的資料。請注意, 您也需要定義 optionalServices 鍵,才能存取任何服務 未納入服務篩選器否則在之後 嘗試存取這些檔案

// Filter Bluetooth devices from Google company with manufacturer data bytes
// that start with [0x01, 0x02].
navigator.bluetooth.requestDevice({
  filters: [{
    manufacturerData: [{
      companyIdentifier: 0x00e0,
      dataPrefix: new Uint8Array([0x01, 0x02])
    }]
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

遮罩也可以與資料前置字串搭配使用,藉此比對 製造商資料詳情請參閱藍牙資料篩選器說明 內容。

排除篩選器

navigator.bluetooth.requestDevice() 中的 exclusionFilters 選項可讓您 您從瀏覽器選擇器排除部分裝置。可用來排除 符合範圍較廣但不受支援的裝置

// Request access to a bluetooth device whose name starts with "Created by".
// The device named "Created by Francois" has been reported as unsupported.
navigator.bluetooth.requestDevice({
  filters: [{
    namePrefix: "Created by"
  }],
  exclusionFilters: [{
    name: "Created by Francois"
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

不要使用濾鏡

最後,您可以不使用 filters,而是使用 acceptAllDevices 鍵顯示所有 附近的藍牙裝置。您也需要定義 optionalServices 並使用部分服務。否則,您將收到錯誤訊息 並能有效存取

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

連線至藍牙裝置

那麼,既然您已取得 BluetoothDevice,要做什麼呢?接著連線到 支援藍牙遠端 GATT 伺服器的服務與特性 定義。

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
  // Human-readable name of the device.
  console.log(device.name);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

讀取藍牙特性

我們在此連線至遠端藍牙裝置的 GATT 伺服器。現在我們 並想取得主要 GATT 服務 這項服務。舉例來說,我們可以試著讀取 裝置的電力

在上方範例中,battery_level標準化電池電量 特性

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => device.gatt.connect())
.then(server => {
  // Getting Battery Service…
  return server.getPrimaryService('battery_service');
})
.then(service => {
  // Getting Battery Level Characteristic…
  return service.getCharacteristic('battery_level');
})
.then(characteristic => {
  // Reading Battery Level…
  return characteristic.readValue();
})
.then(value => {
  console.log(`Battery percentage is ${value.getUint8(0)}`);
})
.catch(error => { console.error(error); });

如果您使用自訂的藍牙 GATT 特徵,可提供 完整的藍牙 UUID,或是 16 或 32 位元的簡短格式 service.getCharacteristic

請注意,您也可以將 characteristicvaluechanged 事件監聽器新增至 處理讀取其值的特性。請參閱閱讀特性 值已變更範例,瞭解如何視需要處理即將發布的 GATT 收到通知

…
.then(characteristic => {
  // Set up event listener for when characteristic value changes.
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleBatteryLevelChanged);
  // Reading Battery Level…
  return characteristic.readValue();
})
.catch(error => { console.error(error); });

function handleBatteryLevelChanged(event) {
  const batteryLevel = event.target.value.getUint8(0);
  console.log('Battery percentage is ' + batteryLevel);
}

寫入藍牙特性

寫入藍牙 GATT 特性和閱讀一樣簡單。這次 讓我們使用心率控制點將能量消耗的能量值 心率監測器裝置的電量為 0。

我保證這裡完全沒有魔法。一切就在心率控制 點特性頁面

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
  // Writing 1 is the signal to reset energy expended.
  const resetEnergyExpended = Uint8Array.of(1);
  return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
  console.log('Energy expended has been reset.');
})
.catch(error => { console.error(error); });

接收 GATT 通知

現在,來看看如何在心率測量 裝置的特性變化:

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleCharacteristicValueChanged);
  console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });

function handleCharacteristicValueChanged(event) {
  const value = event.target.value;
  console.log('Received ' + value);
  // TODO: Parse Heart Rate Measurement value.
  // See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js
}

通知樣本說明如何停止接收通知, stopNotifications()並妥善移除加入的 characteristicvaluechanged 事件接聽程式。

中斷與藍牙裝置的連線

為了提供更優質的使用者體驗,建議您監聽連線中斷事件 並邀請使用者重新連線:

navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
  // Set up event listener for when device gets disconnected.
  device.addEventListener('gattserverdisconnected', onDisconnected);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

function onDisconnected(event) {
  const device = event.target;
  console.log(`Device ${device.name} is disconnected.`);
}

你也可以呼叫 device.gatt.disconnect() 將網頁應用程式與 藍牙裝置。這會觸發現有的gattserverdisconnected事件 接聽程式。請注意,如果藍牙裝置的通訊內容 應用程式已經與藍牙裝置通訊。查看 Device 取消連結範例自動重新連線範例,進一步瞭解詳情。

讀取及寫入藍牙描述元

藍牙 GATT 描述元是一種描述特徵值的屬性。 讀取及寫入這類通訊埠的方式與藍牙 GATT 類似

讓我們看看如何解讀使用者描述的測量結果 裝置健康溫度計的間隔時間。

在下例中,health_thermometer健康溫度計服務measurement_interval評估間隔特徵,以及 gatt.characteristic_user_description「特性使用者說明」 描述元

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => descriptor.readValue())
.then(value => {
  const decoder = new TextDecoder('utf-8');
  console.log(`User Description: ${decoder.decode(value)}`);
})
.catch(error => { console.error(error); });

我們完成使用者描述了 讓我們看看如何更新裝置健康溫度計,並編寫自訂 值。

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => {
  const encoder = new TextEncoder('utf-8');
  const userDescription = encoder.encode('Defines the time between measurements.');
  return descriptor.writeValue(userDescription);
})
.catch(error => { console.error(error); });

範例、示範和程式碼研究室

下列所有網路藍牙範例皆已通過測試。享受這些樂趣 建議您安裝 [BLE Peripheral 模擬器 Android 應用程式] 模擬 BLE 週邊裝置,搭配電池服務,一種心率 服務或健康溫度計服務

新手

  • 裝置資訊 - 從 BLE 裝置擷取基本裝置資訊。
  • 電池電量 - 從 BLE 裝置廣告資訊擷取電池資訊。
  • 重設能源:重設因 BLE 裝置廣告心率而消耗的能源。
  • 特性屬性 - 顯示 BLE 裝置中特定特性的所有屬性。
  • 通知 - 開始和停止來自 BLE 裝置的特性通知。
  • 中斷連線:中斷與 BLE 裝置連線後連線中斷的通知。
  • 取得特性 - 透過 BLE 裝置取得廣告宣傳服務的所有特性。
  • 取得描述元 - 取得所有特性BLE 裝置所宣傳服務的描述元。
  • 製造商資料篩選器 - 從符合製造商資料的 BLE 裝置中擷取基本裝置資訊。
  • 排除篩選器 - 從提供基本排除篩選器的 BLE 裝置中擷取基本裝置資訊。

結合多項作業

您也可以參考我們精選的網路藍牙示範模式,以及官方網路藍牙程式碼研究室

程式庫

  • web-bluetooth-utils 是 npm 模組,可新增一些便利函式到 並嚴謹測試及提升 API 的公平性後 我們才能放心地推出 API
  • Web Bluetooth API 輔助程式適用於 noble,這是最熱門的 Node.js BLE 中央模組。如此一來你就可以使用 webpack/browserify 輕鬆瀏覽網路 適用於 WebSocket 伺服器或其他外掛程式
  • angular-web-bluetoothAngular 的模組, 設定 Web Bluetooth API 所需的樣板。

工具

提示

您可以透過 Chrome 存取藍牙內部資源頁面,網址為: about://bluetooth-internals,方便使用者查看附近所有相關資訊 藍牙裝置:狀態、服務、特性和描述元。

在 Chrome 中對藍牙進行偵錯的內部頁面螢幕截圖
Chrome 的內部頁面,用於對藍牙裝置進行偵錯。

我們也建議您參閱官方如何回報網路藍牙錯誤一文 處理藍牙偵錯作業有時並不容易

後續步驟

請先查看瀏覽器和平台導入狀態,瞭解有哪些部分 目前正在實作 Web Bluetooth API。

目前尚未提供完整資訊,但可以先睹為快 未來:

  • 掃描附近的 BLE 廣告 變更作業將以 navigator.bluetooth.requestLEScan() 進行。
  • 新的 serviceadded 事件會追蹤新發現的藍牙 GATT 服務 serviceremoved事件則會追蹤已移除的事件新的 servicechanged 事件就會觸發 並從藍牙 GATT 服務中移除。

顯示對 API 的支援

您是否打算使用 Web Bluetooth API?你的公開支援能協助 Chrome 團隊 優先開發功能,並向其他瀏覽器廠商說明支援這些功能的重要性。

使用主題標記將推文傳送至 @ChromiumDev #WebBluetooth敬上 ,並說明你使用這項服務的位置和方式。

資源

特別銘謝

感謝 Kayce Basques 審閱這篇文章。 主頁橫幅來源:美國博爾德市的 SparkFun Electronics (美國博爾德)