能力 · API
Web Bluetooth API
发布于 更新于
navigator.bluetooth.requestDevice() 弹出一个由浏览器托管的选择器,列出附近匹配所传过滤器的蓝牙低功耗设备,并以用户选中的那一个 BluetoothDevice 兑现。页面随后通过该对象连接设备的 GATT 服务器,读取、写入或订阅特征值,所以 PWA 不需要原生伴侣应用就能与心率带、打印机或灯具通信。
该 API 仅在 Chromium 中实现。Chrome 56 在 Android、ChromeOS 和 macOS 上支持,Chrome 70 加入 Windows,Linux 仍需开启 chrome://flags/#enable-experimental-web-platform-features(实现状态,github.com);Edge 79、Samsung Internet 6.0 与 Opera 43 跟随 Chrome。Firefox 有一个未关闭的跟踪 bug(674737),WebKit 记录了正式的「反对」立场(standards-positions #570),Android WebView 不暴露 navigator.bluetooth。
navigator.bluetooth.requestDevice(options)navigator.bluetooth.getDevices()navigator.bluetooth.getAvailability()
device.gatt.connect()server.getPrimaryService(service)service.getCharacteristic(characteristic)characteristic.readValue()characteristic.writeValueWithResponse(value)characteristic.startNotifications()requestDevice() 返回 Promise<BluetoothDevice>;getDevices() 返回 Promise<sequence<BluetoothDevice>>,列出此前已授权给本源的设备且不弹窗,自 Chrome 85 起位于 chrome://flags/#enable-web-bluetooth-new-permissions-backend 之后(BCD api.Bluetooth.getDevices)。getAvailability() 在存在适配器时兑现为 true。Bluetooth 接口标注为 [Exposed=Window, SecureContext]:在 worker 中不可用,非安全源上 navigator.bluetooth 为 undefined。
requestDevice() 接受一个 RequestDeviceOptions 字典。filters 与 acceptAllDevices: true 必须恰好给出一个;选择器只列出至少匹配一个过滤器且不匹配任何排除过滤器的设备。
| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
filters |
sequence<BluetoothLEScanFilterInit> |
与 acceptAllDevices 二选一 |
匹配任一过滤器的设备会被列出。给出时必须非空。 |
exclusionFilters |
sequence<BluetoothLEScanFilterInit> |
否 | 匹配其中任一项的设备即使匹配 filters 也会被隐藏;需要同时给出 filters。Chrome 114。 |
optionalServices |
sequence<BluetoothServiceUUID> |
否,默认 [] |
连接后页面可以访问、但未在某个 services 过滤器中列出的服务。两处都没有的服务会以 SecurityError 拒绝访问。 |
optionalManufacturerData |
sequence<unsigned short> |
否,默认 [] |
页面可以从广播中读取其厂商数据的公司标识符。 |
acceptAllDevices |
boolean |
与 filters 二选一 |
列出附近所有设备而不过滤。 |
每个 BluetoothLEScanFilterInit 至少需要一个成员。
| 成员 | 类型 | 说明 |
|---|---|---|
services |
sequence<BluetoothServiceUUID> |
广播的服务;可写 16 位别名(0x180f)、GATT 名称('battery_service')或 128 位 UUID 字符串。必须非空。 |
name |
DOMString |
精确的设备名,UTF-8 编码不超过 248 字节。 |
namePrefix |
DOMString |
设备名前缀,非空且 UTF-8 编码不超过 248 字节。 |
manufacturerData |
sequence<BluetoothManufacturerDataFilterInit> |
{ companyIdentifier, dataPrefix?, mask? };同一过滤器内标识符不得重复。Chrome 92。 |
serviceData |
sequence<BluetoothServiceDataFilterInit> |
{ service, dataPrefix?, mask? }。 |
getPrimaryService()、getCharacteristic() 及其复数形式接受同样三种写法的 BluetoothServiceUUID 或 BluetoothCharacteristicUUID。
requestDevice() 以下列之一拒绝;TypeError 检查最先执行,在任何无线电活动之前。
| 异常 | 条件 |
|---|---|
TypeError |
给了 exclusionFilters 但没有 filters;filters 与 acceptAllDevices: true 同时给出或都没给;filters 或 exclusionFilters 为空数组;某个过滤器没有任何成员;services、manufacturerData 或 serviceData 为空数组;name 或 namePrefix 超过 248 个 UTF-8 字节,或 namePrefix 为空;companyIdentifier 重复;dataPrefix 为空;mask 长度与 dataPrefix 不一致。 |
SecurityError |
文档不被允许使用 bluetooth Permissions Policy 特性;调用时没有瞬时用户激活;或过滤器或 optionalServices 中出现被封禁的服务 UUID。 |
NotFoundError |
选择器在未选中设备的情况下关闭,或没有适配器、没有匹配的设备。 |
GATT 方法的拒绝方式不同。device.gatt.connect() 在设备超出范围或链路失败时以 NetworkError 拒绝。getPrimaryService() 与 getCharacteristic() 对未经 filters 或 optionalServices 授权的服务或被封禁的 UUID 以 SecurityError 拒绝,gatt.connected 为 false 时以 NetworkError 拒绝,设备没有所请求项时以 NotFoundError 拒绝。readValue() 与 writeValueWithResponse() 对禁止读或写的 UUID 以 SecurityError 拒绝,对超过 512 字节的值以 InvalidModificationError 拒绝,服务或特征值已不存在时以 InvalidStateError 拒绝,设备拒绝该操作时以 NotSupportedError 拒绝,操作中途断连时以 NetworkError 拒绝。这些都列在「另请参阅」所链规范的各方法步骤中。
每个示例都先检测 navigator.bluetooth,并给出没有它时页面的行为;前两个因激活规则必须由 click 之类的处理函数触发。
读取电量并给出回退提示
Section titled “读取电量并给出回退提示”按标准的 Battery Service 过滤,选择器只显示广播该服务的设备;连接后读取单字节的 Battery Level 特征值。API 缺失时函数返回 null,界面说明该浏览器无法访问蓝牙设备。
async function readBatteryLevel() { if (!('bluetooth' in navigator)) return null;
const device = await navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }], }); const server = await device.gatt.connect(); const service = await server.getPrimaryService('battery_service'); const characteristic = await service.getCharacteristic('battery_level'); const value = await characteristic.readValue(); return value.getUint8(0);}
document.querySelector('#battery').addEventListener('click', async () => { const output = document.querySelector('#battery-output'); try { const level = await readBatteryLevel(); output.textContent = level === null ? '此浏览器无法访问蓝牙设备,请直接在设备上查看。' : `电量:${level}%`; } catch (err) { if (err.name === 'NotFoundError') return; // 选择器被关闭 output.textContent = `${err.name}: ${err.message}`; }});'battery_service' 与 'battery_level' 是 GATT 分配名称,浏览器将其映射为 0x180F 与 0x2A19;厂商自定义服务用 128 位 UUID 字符串,用法相同。
订阅心率通知并在断连后恢复
Section titled “订阅心率通知并在断连后恢复”startNotifications() 之后,通知以 characteristicvaluechanged 事件到达。设备随时可能走出范围,所以在设备上监听 gattserverdisconnected,并提供重连:对同一个对象再次调用 gatt.connect(),不需要重新弹出选择器。
async function watchHeartRate(onBeat, onLost) { if (!('bluetooth' in navigator)) { onLost('此浏览器不提供蓝牙访问'); return; }
const device = await navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }], }); device.addEventListener('gattserverdisconnected', () => onLost('设备已断开'));
const server = await device.gatt.connect(); const service = await server.getPrimaryService('heart_rate'); const measurement = await service.getCharacteristic('heart_rate_measurement');
measurement.addEventListener('characteristicvaluechanged', (event) => { const data = event.target.value; const is16Bit = data.getUint8(0) & 0x1; onBeat(is16Bit ? data.getUint16(1, true) : data.getUint8(1)); }); await measurement.startNotifications();}Heart Rate Measurement 的第一个字节是标志位;bit 0 表示随后的心率值是 8 位还是 16 位,所以处理函数在读取前要分支。
显示配对按钮前先检查有没有适配器
Section titled “显示配对按钮前先检查有没有适配器”getAvailability() 告诉页面这台机器是否有蓝牙无线电,这样可以在没有适配器的桌面机上隐藏「连接」按钮,而不是让用户撞上 NotFoundError。它不弹窗,也不需要用户激活。
async function bluetoothUiState() { if (!('bluetooth' in navigator)) return 'unsupported'; const available = await navigator.bluetooth.getAvailability(); return available ? 'ready' : 'no-adapter';}
const state = await bluetoothUiState();document.querySelector('#connect').hidden = state !== 'ready';document.querySelector('#bluetooth-note').textContent = { unsupported: '请在桌面或 Android 设备上使用 Chrome 或 Edge 进行蓝牙连接。', 'no-adapter': '此设备上未找到蓝牙适配器。', ready: '',}[state];页面打开期间可用性可能变化(插入 USB 适配器、关闭蓝牙);navigator.bluetooth 上的 availabilitychanged 事件会报告新值。
- Web Serial API,对串口采用同一套选择器模型
- WebUSB API
- WebHID API
- Web Bluetooth: requestDevice() method(bluetooth.spec.whatwg.org)
- Web Bluetooth: getDevices() method(bluetooth.spec.whatwg.org)
- WebKit standards position: Web Bluetooth(github.com)
- Mozilla bug 674737: Implement Web Bluetooth(bugzilla.mozilla.org)
- Communicating with Bluetooth devices over JavaScript(developer.chrome.com)
规范
| 规范 | 状态 |
|---|---|
| Web Bluetooth API(网页蓝牙) | 社区草案 |
| Web Bluetooth: requestDevice() method | WHATWG 现行标准 |
| Web Bluetooth: RequestDeviceOptions dictionary | WHATWG 现行标准 |
- 图例
- 支持
- 部分支持
- 需开启标志
- 不支持
- 未知
| 浏览器 / 平台 | 支持 | 版本 | 置信度 | 来源 | 备注 |
|---|---|---|---|---|---|
| Chrome (Desktop) | 支持 | 56 | 高 | 来源 | — |
| Chrome (Android) | 支持 | 56 | 高 | 来源 | 1 |
| Edge (Desktop) | 支持 | 79 | 高 | 来源 | 2 |
| Firefox (Desktop) | 不支持 | — | 高 | 来源 | 3 |
| Firefox (Android) | 不支持 | — | 高 | 来源 | 45 |
| Safari (macOS) | 不支持 | — | 高 | 来源 | 6 |
| Safari (iOS) | 不支持 | — | 高 | 来源 | 78 |
| Samsung Internet | 支持 | 6.0 | 高 | 来源 | 9 |
| WebView (Android) | 不支持 | — | 高 | 来源 | 10 |
| Opera | 支持 | 43 | 高 | 来源 | 11 |
- 由 browser-compat-data 镜像自 Chrome 的数据推导。
- 由 browser-compat-data 镜像自 Chrome 的数据推导。
- 实现跟踪:https://bugzil.la/674737。
- 实现跟踪:https://bugzil.la/674737。
- 由 browser-compat-data 镜像自 Firefox 的数据推导。
- 实现跟踪:https://webkit.org/b/101034。
- 实现跟踪:https://webkit.org/b/101034。
- 由 browser-compat-data 镜像自 Safari 的数据推导。
- 由 browser-compat-data 镜像自 Chrome Android 的数据推导。
- browser-compat-data 未记录 WebView Android 的支持。
- 镜像 Chrome 的实现(BCD `opera: mirror`):Linux 上有支持,但默认未启用。