大家周二早。如果你用过智能家居设备或者运动手环,是不是对“扫码下载App,打开蓝牙连接”的流程感到一丝厌烦?作为程序员,你可能会想:为什么不能直接在浏览器里打开一个URL搞定?
其实,完全可以。这得益于 Web Bluetooth API,它允许浏览器直接与低功耗蓝牙(BLE)设备进行交互。如今,共享单车开锁、乐心编程积木,甚至是某些医疗设备,都在尝试使用 PWA 结合蓝牙技术来替代笨重的原生 App。
今天,我们就用原生 JavaScript,连接一个标准的心率广播设备(市面上大部分手环、手表都支持),将实时心跳数据显示在网页上。
01. 核心概念:GATT 协议栈
蓝牙开发有一套自己的“术语”,别被它吓到,本质上和我们调用API接口差不多。
我们可以把一个蓝牙设备想象成一栋“大楼”:
- GATT Server:整栋大楼,也就是设备本身。
- Service (服务):大楼里的某一层。例如,设备可能提供“心率服务层”、“电池服务层”等。
- Characteristic (特征值):楼层里的某个房间。例如,在“心率服务层”里,有“心率数值房间”、“传感器位置房间”等。
我们的工作流程可以简化为:找到大楼 -> 坐电梯去“心率服务”那一层 -> 敲开“心率数值”那个房间的门 -> 接收并解析房间(特征值)里传出来的数据。
02. 实战:连接与授权
请注意,出于安全和隐私考虑,Web Bluetooth API 的使用有两个硬性前提:
- 网站必须运行在 HTTPS 协议下(
localhost 本地开发环境除外)。
- 必须由用户的手势操作(例如点击按钮)来触发连接流程,代码不能自动扫描。
接下来,我们通过一个按钮点击事件来启动连接流程。代码如下:
async function connectBluetooth() {
try {
// 1. 🔍 扫描并请求设备
// 我们通过指定服务UUID来筛选,这里只请求标准的“心率服务”(UUID: 0x180D)
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['heart_rate'] }],
// 或者也可以接受所有设备(不推荐,用户体验较差)
// acceptAllDevices: true,
// optionalServices: ['heart_rate']
});
console.log('连接到设备:', device.name);
// 2. 🤝 连接设备的 GATT 服务器
const server = await device.gatt.connect();
// 3. 🏢 找到“心率服务”层 (UUID: 0x180D)
const service = await server.getPrimaryService('heart_rate');
// 4. 🚪 找到“心率测量”特征值房间 (UUID: 0x2A37)
const characteristic = await service.getCharacteristic('heart_rate_measurement');
// 5. 👂 开始监听该特征值的通知(Notify)
await characteristic.startNotifications();
// 绑定数据变更的回调函数
characteristic.addEventListener('characteristicvaluechanged', handleHeartRateChange);
console.log('开始监听心率...');
} catch (error) {
console.error('连接失败:', error);
}
}
03. 难点:解析二进制数据 (DataView)
蓝牙设备传回的不是我们熟悉的 JSON 或字符串,而是原始的 ArrayBuffer(二进制数据流)。
我们需要对照 Bluetooth SIG 官方标准文档 中定义的格式,使用位操作来解码。心率数据(特征值 UUID 0x2A37)的格式大致如下:
- 第 1 个字节 (Flags - 标志位):
- 第 0 位:心率值格式标志(
0 表示心率值为 8 位,1 表示心率值为 16 位)。
- 后续字节:根据标志位,从第 2 个字节开始,存储着实际的心率数值。
解码函数 handleHeartRateChange 的实现如下:
function handleHeartRateChange(event) {
const value = event.target.value; // 这是一个 DataView 对象
// 1. 读取第一个字节 (Flags)
const flags = value.getUint8(0);
// 2. 判断第 0 位 (心率值格式位)
// 如果为 1,说明心率值是 16 位的 (占2个字节)
// 如果为 0,说明心率值是 8 位的 (占1个字节)
const rate16Bits = flags & 0x1;
let heartRate;
if (rate16Bits) {
// 读取一个 16 位的无符号整数 (小端字节序)
heartRate = value.getUint16(1, true);
} else {
// 读取一个 8 位的无符号整数
heartRate = value.getUint8(1);
}
console.log(`当前心率: ${heartRate} BPM`);
updateUI(heartRate); // 更新页面UI的函数
}
04. 写入数据:控制设备
除了“读”,我们自然也能“写”。例如,控制一个智能灯泡的颜色。其思路与读取类似,只是最后调用的是 writeValue 方法。
// 假设我们已经连接并获取了控制颜色的特征值
const colorChar = await service.getCharacteristic('light_color');
// 准备要写入的数据:红色 (RGB: 255, 0, 0)
const data = new Uint8Array([255, 0, 0]);
await colorChar.writeValue(data);
05. 2026 兼容性现状
一项技术的实用性离不开浏览器支持。截至目前,Web Bluetooth API 的兼容性情况如下:
- Chrome / Edge (桌面版) / Android WebView:提供完整且稳定的支持。
- iOS Safari:老大难问题。直到2026年,Apple 出于其严格的隐私保护策略(实质上也是为了维护 App Store 生态),仍未完全开放 Web Bluetooth API 的支持。
- 替代方案:iOS 用户需要使用如 Bluefy 或 WebBLE 这类专门支持 Web Bluetooth 的第三方浏览器 App 来访问你的网页。另一种思路是将网页打包为 PWA 应用时进行“套壳”处理。
结语
Web Bluetooth 是迈向 Web of Things (WoT) 的重要基石。它极大地拓展了前端工程师的能力边界,让我们编写的代码能够与物理世界进行交互。
想象一下未来的场景:在医院,你无需排队领取纸质报告,只需用手机贴近医疗设备,健康数据便能通过网页同步展示;在健身房,扫描跑步机上的二维码,你的实时配速和心率就直接呈现在浏览器页面中。这些并非遥不可及的幻想,而是正在发生的技术演进。如果你对这类连接物理世界的 前端 开发感兴趣,可以在 云栈社区 的 前端 & 移动 等板块找到更多相关资源和深度讨论,探索更多关于 JavaScript 和物联网结合的实战技巧。