通过 JavaScript 与蓝牙设备通信

Web Bluetooth API 允许网站与蓝牙设备通信。

François Beaufort
François Beaufort

如果我告诉你网站可以与附近的蓝牙设备通信,该怎么办? 安全、可保护隐私的方式?这样,心率监测器就会开始唱歌 灯泡,甚至海龟都可以直接与网站互动。

到目前为止,用户可以与蓝牙设备互动 。Web Bluetooth API 旨在改变这种情况, 也会将其引入网络浏览器

开始前须知

本文档假定您对低功耗蓝牙的工作原理有基本的了解 能耗 (BLE) 和通用属性配置文件可正常运作。

虽然 Web Bluetooth API 规范尚未最终确定,该规范 作者正在积极寻找有热情的开发者试用此 API 并 提供有关规范的反馈有关实施的反馈

Web Bluetooth API 的部分功能可在 ChromeOS、Chrome(Android 版)中使用 6.0、Mac (Chrome 56) 和 Windows 10 (Chrome 70)。也就是说,您应该能够 请求连接附近的蓝牙低功耗设备; 读取/写入蓝牙特征,接收 GATT 通知,了解 当蓝牙设备断开连接时,甚至读取和写入 蓝牙描述符。如需了解详情,请参阅 MDN 的浏览器兼容性表格 信息。

对于 Linux 和更早版本的 Windows,请启用 about://flags 中的 #experimental-web-platform-features 标志。

可用于源试用

为了从使用 Web 应用的开发者那里获得尽可能多的反馈 Bluetooth API,Chrome 之前已在 Chrome 中添加此功能 53 作为 ChromeOS、Android 和 Mac 的源试用版

试用期已于 2017 年 1 月成功完成。

安全性要求

要了解其安全方面的权衡,建议您参考网络蓝牙安全性 Chrome 团队的软件工程师 Jeffrey Yasskin 发布的模型帖子, 制定 Web Bluetooth API 规范。

仅限 HTTPS

由于这个实验性 API 是添加到 Web 中的强大新功能, 仅可用于安全上下文。也就是说,您需要使用 考虑使用 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 规范允许运行 它 支持在实现蓝牙 4.0 或更高版本的设备之间通信。

当某个网站使用 navigator.bluetooth.requestDevice 时,浏览器提示用户使用设备 选择器,他们可以选择一台设备或取消请求。

<ph type="x-smartling-placeholder">
</ph>
蓝牙设备用户提示

navigator.bluetooth.requestDevice() 函数接受一个必需的对象, 定义了过滤器。这些过滤条件用于仅返回与某些条件匹配的设备 通告的蓝牙 GATT 服务和/或设备名称。

服务过滤条件

例如,请求通告蓝牙 GATT 的蓝牙设备 电池服务

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

如果您的蓝牙 GATT 服务不在标准化蓝牙 GATT 服务,您可以提供完整的蓝牙 UUID 或简短的蓝牙 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。您还可以提供一个数据前缀 制造商数据。请注意,您将 还需要定义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 后要怎么做?让我们连接到 包含服务和特征的蓝牙远程 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() 断开您的 Web 应用与 蓝牙设备。此操作会触发现有的 gattserverdisconnected 事件 监听器。请注意,如果其他蓝牙设备与蓝牙设备通信, 应用程序已在与蓝牙设备通信。请查看设备 断开示例自动重新连接示例以深入了解。

对蓝牙描述符执行读写操作

蓝牙 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); });

示例、演示和 Codelab

以下所有网络蓝牙示例均已成功测试。为了欣赏这些 建议您安装 [BLE 外设模拟器], Android 应用],用于模拟具有电池服务和心率的 BLE 外围设备 服务或健康温度计服务

新手

  • 设备信息 - 从 BLE 设备中检索基本设备信息。
  • 电池电量 - 从通告电池信息的 BLE 设备中检索电池信息。
  • 重置能量 - 重置 BLE 设备通告心率消耗的能量。
  • 特征属性 - 显示 BLE 设备中特定特征的所有属性。
  • 通知 - 开始和停止来自 BLE 设备的特征通知。
  • 设备断开连接 - BLE 设备在连接后断开,当 BLE 设备断开连接时,您会收到相关通知。
  • 获取特征 - 从 BLE 设备获取所通告服务的所有特征。
  • 获取描述符 - 获取所有特征BLE 设备中通告服务的描述符。
  • 制造商数据过滤器 - 从 BLE 设备中检索与制造商数据匹配的基本设备信息。
  • 排除过滤器 - 从具有基本排除过滤器的 BLE 设备中检索基本设备信息。

组合多项操作

您还可以查看我们精选的 Web 蓝牙演示官方的 Web 蓝牙 Codelab

  • web-bluetooth-utils 是一个 npm 模块,它添加了一些便捷函数 该 API。
  • Web Bluetooth API shim 可在最受欢迎的 Node.js BLE noble 中实现 核心模块中这样,您无需编写任何代码 适用于 WebSocket 服务器或其他插件。
  • angular-web-bluetoothAngular 的一个模块,可以抽象化所有 配置 Web Bluetooth API 所需的样板文件。

工具

  • 网络蓝牙入门是一款简单的网络应用,能生成所有 开始与蓝牙设备交互的 JavaScript 样板代码。 输入设备名称、服务和特征,定义其属性并 一切正常。
  • 如果您已经是蓝牙开发者,请访问 Web Bluetooth Developer Studio 插件还会为您的移动设备生成网络蓝牙 JavaScript 代码。 蓝牙设备。

提示

Chrome 中提供了一个蓝牙内部功能页面,网址为: about://bluetooth-internals,以便您可以检查关于附近地点的所有信息 蓝牙设备:状态、服务、特征和描述符。

<ph type="x-smartling-placeholder">
</ph> 用于在 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 提供。