Web Bluetooth API 可讓網站與藍牙裝置通訊。
假如我告訴你,網站可以透過安全且兼顧隱私權的方式,與附近的藍牙裝置通訊,你會怎麼想?這樣一來,心率監測器、會唱歌的燈泡,甚至是烏龜,都能直接與網站互動。
在此之前,只有特定平台的應用程式才能與藍牙裝置互動。Web Bluetooth API 旨在改變這種情況,並將其帶入網頁瀏覽器。
事前準備
本文假設您具備藍牙低功耗 (BLE) 和通用屬性設定檔的基礎知識。
雖然 Web Bluetooth API 規格尚未定案,但規格作者正積極尋找熱心的開發人員來試用這個 API,並提供規格相關意見和實作相關意見。
ChromeOS、Android 版 Chrome 6.0、Mac (Chrome 56) 和 Windows 10 (Chrome 70) 提供 Web Bluetooth API 的子集。也就是說,您應該可以要求並連線至附近的藍牙低功耗裝置、讀取/寫入藍牙特性、接收 GATT 通知,以及瞭解藍牙裝置斷線的時間,甚至讀取及寫入藍牙描述符。詳情請參閱 MDN 的「瀏覽器相容性」表格。
如果是 Linux 和舊版 Windows,請在 about://flags
中啟用 #experimental-web-platform-features
標記。
適用於原始測試
為了盡可能從在該領域使用 Web Bluetooth API 的開發人員那裡獲得意見回饋,Chrome 先前已在 Chrome 53 中新增這項功能,做為 ChromeOS、Android 和 Mac 的原始版本試用版。
試用期已於 2017 年 1 月順利結束。
安全性規定
如要瞭解安全性取捨,建議您參閱 Jeffrey Yasskin 的文章「Web Bluetooth 安全性模型」,他是 Chrome 團隊的軟體工程師,負責 Web Bluetooth API 規格。
僅限 HTTPS
由於這個實驗性 API 是新增至網際網路的強大新功能,因此只提供給安全情境。也就是說,您需要考量 TLS 來建構應用程式。
需要使用者手勢
為了確保安全性,使用 navigator.bluetooth.requestDevice
探索藍牙裝置時,必須由使用者動作觸發,例如輕觸或滑鼠點選。我們要談的是監聽 pointerup
、click
和 touchend
事件。
button.addEventListener('pointerup', function(event) {
// Call navigator.bluetooth.requestDevice
});
進入程式碼
Web Bluetooth API 大量仰賴 JavaScript Promise。如果您不熟悉這些概念,請參閱這份實用的承諾教學課程。另外,() => {}
是 ECMAScript 2015 箭頭函式。
要求藍牙裝置
這個版本的 Web Bluetooth API 規格可讓在中央角色中執行的網站,透過 BLE 連線連線至遠端 GATT 伺服器。支援實作藍牙 4.0 以上版本的裝置間通訊。
當網站使用 navigator.bluetooth.requestDevice
要求鄰近裝置的存取權時,瀏覽器會提示使用者使用裝置選擇器,讓他們選擇裝置或取消要求。
navigator.bluetooth.requestDevice()
函式會採用定義篩選條件的必要物件。這些篩選器只會傳回符合部分宣傳的藍牙 GATT 服務和/或裝置名稱的裝置。
服務篩選器
舉例來說,如要要求藍牙裝置宣傳 Bluetooth 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
鍵,才能存取服務篩選器中未包含的任何服務。如果未執行這項操作,日後嘗試存取這些資料時,系統會顯示錯誤訊息。
navigator.bluetooth.requestDevice({
filters: [{
name: 'Francois robot'
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
製造商資料篩選器
您也可以根據使用 manufacturerData
篩選器鍵宣傳的製造商特定資料,要求藍牙裝置。這個鍵是物件陣列,其中包含名為 companyIdentifier
的強制 Bluetooth 公司 ID 鍵。您也可以提供資料前置字串,篩選出以該字串開頭的藍牙裝置製造商資料。請注意,您還需要定義 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); });
沒有篩選器
最後,您可以使用 acceptAllDevices
鍵,而非 filters
顯示附近的所有藍牙裝置。您也需要定義 optionalServices
金鑰,才能存取部分服務。如果未執行這項操作,日後嘗試存取這些資料時,系統就會顯示錯誤訊息。
navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
連線至藍牙裝置
那麼,現在您有 BluetoothDevice
了,該怎麼做呢?讓我們連線至負責保存服務和特徵定義的 Bluetooth 遠端 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 特徵的操作與讀取操作一樣簡單。這次,我們將使用心率控制點,將心率監測器裝置的「Energy Expended」欄位值重設為 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
}
「Notifications Sample」會說明如何使用 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
事件監聽器。請注意,如果其他應用程式已與藍牙裝置通訊,此應用程式不會停止藍牙裝置通訊。如需進一步瞭解,請參閱「裝置斷線範例」和「自動重新連線範例」。
讀取及寫入藍牙描述元
Bluetooth 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); });
範例、試用版和程式碼研究室
以下所有 Web Bluetooth 範例都已通過測試。為了充分享受這些範例,建議您安裝 [BLE 周邊模擬器 Android 應用程式],該應用程式可模擬使用電池服務、心率服務或健康溫度計服務的 BLE 周邊裝置。
新手
- 裝置資訊:從 BLE 裝置擷取基本裝置資訊。
- Battery Level:從 BLE 裝置廣告的電池資訊中擷取電池資訊。
- Reset Energy:重設 BLE 裝置廣播心率所耗用的能量。
- 特性屬性:顯示 BLE 裝置特定特性的所有屬性。
- 通知:開始和停止來自 BLE 裝置的特徵通知。
- Device Disconnect:中斷連線,並在連線後收到 BLE 裝置中斷連線的通知。
- Get Characteristics:取得 BLE 裝置廣告服務的所有特性。
- Get Descriptors:取得 BLE 裝置宣傳服務的所有屬性描述項。
- 製造商資料篩選器:從符合製造商資料的 BLE 裝置中擷取基本裝置資訊。
- 排除篩選器:從 BLE 裝置擷取基本裝置資訊,並提供基本排除篩選器。
合併多項作業
- GAP 特性:取得 BLE 裝置的所有 GAP 特性。
- 裝置資訊特性:取得 BLE 裝置的所有裝置資訊特性。
- Link Loss:設定 BLE 裝置的 Alert Level 特性 (readValue 和 writeValue)。
- Discover Services & Characteristics:從 BLE 裝置探索所有可存取的主要服務及其特徵。
- 自動重新連線:使用指數輪詢演算法重新連線至已中斷連線的 BLE 裝置。
- Read Characteristic Value Changed:讀取電池電量,並接收 BLE 裝置的變更通知。
- 讀取描述符:從 BLE 裝置讀取服務的所有特徵描述符。
- 寫入描述元:寫入 BLE 裝置上的描述元「特徵使用者說明」。
歡迎參閱我們精選的 Web Bluetooth 示範和官方 Web Bluetooth 程式碼研究室。
程式庫
- web-bluetooth-utils 是 npm 模組,可為 API 新增一些方便的函式。
- noble 是目前最熱門的 Node.js BLE 中樞模組,其中提供 Web Bluetooth API 墊片。這樣一來,您就能使用 webpack/browserify noble,而不需要 WebSocket 伺服器或其他外掛程式。
- angular-web-bluetooth 是 Angular 模組,可抽離設定 Web Bluetooth API 所需的所有樣板。
工具
- 「Get Started with Web Bluetooth」是個簡單的網路應用程式,會產生所有 JavaScript 樣板程式碼,以便開始與藍牙裝置互動。輸入裝置名稱、服務、特性,並定義其屬性即可。
- 如果您是藍牙開發人員,Web Bluetooth Developer Studio 外掛程式也會為藍牙裝置產生 Web Bluetooth JavaScript 程式碼。
提示
Chrome 中的 藍牙內部頁面 (about://bluetooth-internals
) 可讓您檢查附近藍牙裝置的所有資訊,包括狀態、服務、特性和描述符。
我還建議您查看官方的「如何回報 Web Bluetooth 錯誤」頁面,因為有時要對藍牙進行偵錯作業可能會很困難。
後續步驟
請先檢查瀏覽器和平台實作狀態,瞭解目前正在實作的 Web Bluetooth API 部分。
雖然仍未完成,但我們先搶先一窺近期的更新內容:
navigator.bluetooth.requestLEScan()
會掃描附近的 BLE 廣告。- 新的
serviceadded
事件會追蹤新發現的藍牙 GATT 服務,而serviceremoved
事件則會追蹤已移除的服務。當任何特徵和/或描述元從藍牙 GATT 服務中新增或移除時,系統會觸發新的servicechanged
事件。
顯示對 API 的支援
您打算使用 Web Bluetooth API 嗎?你的公開支持有助於 Chrome 團隊決定功能優先順序,並向其他瀏覽器供應商顯示支援這些功能的重要性。
使用主題標記 #WebBluetooth
發送推文給 @ChromiumDev,告訴我們你在何處使用這項功能,以及使用方式。
資源
特別銘謝
感謝 Kayce Basques 審查本文。主頁橫幅圖片由 美國博爾德的 SparkFun Electronics 提供。