浏览器 Web Bluetooth API使用方法

浏览器 Web Bluetooth API使用方法

浏览器 Web Bluetooth API 完整指南

一、简介

什么是 Web Bluetooth API?

Web Bluetooth API 让网页应用可以与蓝牙设备通信。通过这个 API,你可以:

  • 👂 扫描并连接蓝牙设备
  • 📤 发送命令到设备
  • 📥 接收数据从设备返回
  • ⚙️ 控制设备的各种操作

适用场景

医疗设备、手环、手表、传感器、遥控器、音箱、灯等 ↓ 所有支持蓝牙的设备都可以通过这个 API 与网页应用通信 

浏览器支持

浏览器支持最低版本
Chrome/Edge56+ / 79+
Firefox⚠️ 需启用98+
Safari-

二、核心概念(5 分钟快速理解)

2.1 蓝牙通信的三层结构

物理设备(血压计、手环等) ↓ GATT 服务器(设备内的数据服务) ├─ Service(服务,定义功能) │ ├─ Characteristic(特征,具体的数据) │ └─ Characteristic └─ Service └─ Characteristic 

2.2 最重要的 4 个概念

名称说明类比
Device蓝牙设备一台手机
Service功能模块手机的相机模块
Characteristic具体数据相机拍摄的照片
UUID全局唯一标识身份证号

2.3 通信方式

发送数据:Web App → 设备 ↓ writeValue() 向设备发送命令 接收数据:设备 → Web App ↓ 两种方式: 1. startNotifications() 设备主动推送(推荐) 2. readValue() 手动读取(备用) 

三、完整的 API 用法

3.1 第一步:扫描并选择设备

// 让用户选择要连接的设备const device =await navigator.bluetooth.requestDevice({// 按服务过滤(设备必须提供这些服务才会出现) filters:[{ services:['battery_service']// 电池服务}],// 可选:列出可能需要的其他服务 optionalServices:['device_information']}); console.log('选中设备:', device.name);

重点

  • requestDevice() 会弹出浏览器对话框,让用户选择
  • 只有提供了指定服务的设备才会显示
  • 返回一个 Device 对象

3.2 第二步:连接到设备

// 连接到 GATT 服务器const gattServer =await device.gatt.connect(); console.log('✅ 已连接');// 监听断开连接事件 device.addEventListener('gattserverdisconnected',()=>{ console.log('❌ 设备已断开');});

重点

  • gatt.connect() 是异步的,需要等待
  • 连接后会返回 gattServer 对象
  • 设备可能会主动断开,要监听事件

3.3 第三步:获取服务

// 从设备获取指定的服务const service =await gattServer.getPrimaryService('battery_service'); console.log('✅ 获取服务成功');

重点

  • 一个设备可能有多个服务
  • 需要知道你要的服务的 UUID
  • 返回一个 Service 对象

3.4 第四步:获取特征

// 从服务中获取特征const characteristic =await service.getCharacteristic('battery_level'); console.log('✅ 获取特征成功');

重点

  • 一个服务可能有多个特征
  • 特征是真正的数据承载者
  • 返回一个 Characteristic 对象

3.5 第五步:读取数据

// 方式 1:手动读取一次const value =await characteristic.readValue();// 转换为可读的格式const battery = value.getUint8(0); console.log('电池电量:', battery,'%');

重点

  • readValue() 返回 DataView 对象
  • 需要用 getUint8()getInt16() 等方法提取数据
  • 每次调用都重新读取最新值

3.6 第六步:启用通知(推荐)

// 启用通知,让设备主动推送数据await characteristic.startNotifications(); console.log('✅ 通知已启用,等待数据...');// 监听数据变化 characteristic.addEventListener('characteristicvaluechanged',event=>{const value = event.target.value;// 提取数据const battery = value.getUint8(0); console.log('电池电量变化:', battery,'%');});

重点

  • startNotifications() 让设备主动推送数据
  • 每次数据变化时触发 characteristicvaluechanged 事件
  • 比手动读取效率高

3.7 第七步:写入数据(发送命令)

// 构造要发送的数据const command =newUint8Array([0x01,// 命令代码0x02,// 参数 10x03// 参数 2]);// 发送数据到设备await characteristic.writeValue(command); console.log('✅ 命令已发送');

重点

  • 必须用 Uint8Array 格式
  • 每个数字是 0-255 之间的十六进制值
  • 设备收到命令后会执行相应的操作

3.8 第八步:禁用通知和断开连接

// 停止通知await characteristic.stopNotifications();// 断开连接 device.gatt.disconnect(); console.log('✅ 已清理资源');

四、完整的实战代码示例

4.1 简单的设备连接管理器

classSimpleBluetoothClient{constructor(){this.device =null;this.gattServer =null;this.service =null;this.characteristic =null;}/** * 连接设备 * @param {string} serviceName - 服务名称(如 'battery_service') * @param {string} characteristicName - 特征名称(如 'battery_level') */asyncconnect(serviceName, characteristicName){try{// 1. 让用户选择设备this.device =await navigator.bluetooth.requestDevice({ filters:[{ services:[serviceName]}], optionalServices:[]}); console.log('✅ 已选择设备:',this.device.name);// 2. 连接到设备this.gattServer =awaitthis.device.gatt.connect(); console.log('✅ GATT 连接成功');// 3. 获取服务this.service =awaitthis.gattServer.getPrimaryService(serviceName); console.log('✅ 获取服务成功');// 4. 获取特征this.characteristic =awaitthis.service.getCharacteristic(characteristicName); console.log('✅ 获取特征成功');// 5. 监听断开事件this.device.addEventListener('gattserverdisconnected',()=>{ console.log('⚠️ 设备已断开');this.device =null;});}catch(error){ console.error('❌ 连接失败:', error);throw error;}}/** * 启用通知 * @param {Function} callback - 收到数据时的回调 */asyncstartListening(callback){try{awaitthis.characteristic.startNotifications(); console.log('✅ 已启用通知');this.characteristic.addEventListener('characteristicvaluechanged',event=>{const value = event.target.value;callback(value);});}catch(error){ console.error('❌ 启用通知失败:', error);throw error;}}/** * 发送命令 * @param {Uint8Array} data - 要发送的数据 */asyncsendCommand(data){try{awaitthis.characteristic.writeValue(data); console.log('✅ 命令已发送');}catch(error){ console.error('❌ 发送失败:', error);throw error;}}/** * 读取一次数据 */asyncreadOnce(){try{const value =awaitthis.characteristic.readValue(); console.log('✅ 数据已读取');return value;}catch(error){ console.error('❌ 读取失败:', error);throw error;}}/** * 断开连接 */disconnect(){if(this.device &&this.device.gatt.connected){this.device.gatt.disconnect(); console.log('✅ 已断开连接');}}}

4.2 在 Vue 中使用

<template> <div> <!-- 状态 --> <div :class="{ connected: isConnected }"> {{ isConnected ? '已连接' : '未连接' }} </div> <!-- 按钮 --> <button @click="connectDevice" :disabled="isConnected"> 连接设备 </button> <button @click="readData" :disabled="!isConnected"> 读取数据 </button> <button @click="disconnect" :disabled="!isConnected"> 断开连接 </button> <!-- 显示数据 --> <div v-if="lastData"> <p>最后收到的数据:</p> <code>{{ lastData }}</code> </div> </div> </template> <script> import SimpleBluetoothClient from '@/utils/BluetoothClient'; export default { data() { return { bluetooth: null, isConnected: false, lastData: null }; }, methods: { async connectDevice() { try { this.bluetooth = new SimpleBluetoothClient(); // 连接到设备 // 注:这里用的是标准的蓝牙服务 UUID await this.bluetooth.connect( '180a', // 设备信息服务 '2a29' // 制造商名称 ); this.isConnected = true; // 启用通知 await this.bluetooth.startListening(value => { this.lastData = this.formatData(value); }); } catch (error) { alert('连接失败: ' + error.message); } }, async readData() { try { const value = await this.bluetooth.readOnce(); this.lastData = this.formatData(value); } catch (error) { alert('读取失败: ' + error.message); } }, disconnect() { this.bluetooth.disconnect(); this.isConnected = false; this.lastData = null; }, formatData(dataView) { // 将 DataView 转换为十六进制字符串 const bytes = new Uint8Array(dataView.buffer); return Array.from(bytes) .map(b => b.toString(16).padStart(2, '0')) .join(' '); } } }; </script> <style scoped> .bluetooth-demo { padding: 20px; } .status { padding: 10px; margin-bottom: 20px; border-radius: 4px; font-weight: bold; color: white; background: red; } .status.connected { background: green; } button { padding: 10px 20px; margin-right: 10px; cursor: pointer; } button:disabled { opacity: 0.5; cursor: not-allowed; } .data { margin-top: 20px; padding: 10px; background: #f0f0f0; border-radius: 4px; } code { font-family: monospace; font-size: 12px; } </style> 

五、数据转换方法

5.1 从 DataView 提取数据

// 假设接收到的数据const value = dataView;// 类型为 DataView// 提取单个字节(无符号)const byte0 = value.getUint8(0);// 0-255const byte1 = value.getUint8(1);// 提取有符号字节const signedByte = value.getInt8(0);// -128 到 127// 提取 16 位整数const int16 = value.getUint16(0,true);// true = 小端序// 提取 32 位整数const int32 = value.getUint32(0,true);// 提取浮点数const float = value.getFloat32(0,true);const double = value.getFloat64(0,true);

5.2 构造要发送的数据

// 创建一个 4 字节的命令const command =newUint8Array([0x01,// 第 1 字节:命令 ID0x02,// 第 2 字节:参数 10x03,// 第 3 字节:参数 20x04// 第 4 字节:参数 3]);// 发送await characteristic.writeValue(command);

5.3 十六进制和数组互转

// 数组转十六进制字符串functionarrayToHex(dataView){const bytes =newUint8Array(dataView.buffer || dataView);return Array.from(bytes).map(b=>'0x'+ b.toString(16).padStart(2,'0')).join(', ');} console.log(arrayToHex(value));// 0x01, 0x02, 0x03, ...// 十六进制字符串转数组functionhexToArray(hexString){const hex = hexString.replace(/\s+|0x/g,'');const array =[];for(let i =0; i < hex.length; i +=2){ array.push(parseInt(hex.substr(i,2),16));}returnnewUint8Array(array);}const arr =hexToArray('01 02 03 04');await characteristic.writeValue(arr);

六、常见蓝牙服务和特征

6.1 标准服务 UUID

这些是 Bluetooth SIG 定义的标准服务:

constSTANDARD_SERVICES={'180a':'设备信息服务','180d':'心率服务','180b':'血压服务','180f':'电池服务','1800':'通用访问','1801':'通用属性',};

6.2 标准特征 UUID

constSTANDARD_CHARACTERISTICS={'2a19':'电池电量','2a29':'制造商名称','2a24':'型号号码','2a25':'序列号码','2a37':'心率测量','2a35':'血压测量',};

七、错误处理

7.1 常见错误类型

try{await navigator.bluetooth.requestDevice(...)}catch(error){if(error.name ==='NotFoundError'){// 用户取消了选择 console.log('用户取消了设备选择');}elseif(error.name ==='NotSupportedError'){// 浏览器不支持 Web Bluetooth console.log('浏览器不支持 Web Bluetooth API');}elseif(error.name ==='SecurityError'){// 需要 HTTPS 或 localhost console.log('需要在 HTTPS 或 localhost 上运行');}elseif(error.name ==='NetworkError'){// 通信失败 console.log('蓝牙通信失败,设备可能已离线');}else{ console.error('未知错误:', error.name);}}

7.2 重连机制

asyncfunctionconnectWithRetry(maxRetries =3){for(let i =0; i < maxRetries; i++){try{ console.log(`第 ${i +1}/${maxRetries} 次连接尝试...`);const device =await navigator.bluetooth.requestDevice({ filters:[{ services:['battery_service']}]});const gattServer =await device.gatt.connect(); console.log('✅ 连接成功');return gattServer;}catch(error){ console.warn(`第 ${i +1} 次尝试失败:`, error.message);if(i < maxRetries -1){// 等待 1 秒后重试awaitnewPromise(resolve=>setTimeout(resolve,1000));}}}thrownewError('连接失败,已达到最大重试次数');}

八、调试技巧

8.1 打印数据便于调试

// 将 DataView 转为可读的格式functiondebugData(dataView){const bytes =newUint8Array(dataView.buffer || dataView);const hexArray = Array.from(bytes).map(b=> b.toString(16).padStart(2,'0').toUpperCase()); console.log('Raw hex:', hexArray.join(' ')); console.log('Decimal:', Array.from(bytes).join(', ')); console.log('Binary:', Array.from(bytes).map(b=> b.toString(2).padStart(8,'0')).join(' '));}// 使用 characteristic.addEventListener('characteristicvaluechanged',event=>{debugData(event.target.value);});

8.2 在浏览器中查看已连接的设备

// 在 Chrome 控制台运行: navigator.bluetooth.getDevices().then(devices=>{ console.table(devices.map(d=>({ name: d.name, id: d.id, connected: d.gatt.connected })));});

8.3 监听所有事件

// 设备事件 device.addEventListener('gattserverdisconnected',()=>{ console.log('❌ 设备已断开');});// 特征值变化 characteristic.addEventListener('characteristicvaluechanged',event=>{ console.log('📥 数据变化:', event.target.value);});

九、使用前的检查清单

✅ 环境要求

  • 使用 Chrome、Edge 等支持的浏览器
  • 使用 HTTPS 或 localhost
  • 确保用户已授予蓝牙权限
  • 蓝牙设备已打开且可发现

✅ 连接步骤

  • 调用 requestDevice() 让用户选择
  • 调用 gatt.connect()
  • 调用 getPrimaryService()
  • 调用 getCharacteristic()
  • 使用 startNotifications()readValue()

✅ 数据处理

  • getUint8()getInt16() 等正确提取数据
  • Uint8Array 构造发送的数据
  • 处理可能的数据不完整情况

✅ 资源清理

  • 用完通知后调用 stopNotifications()
  • 断开连接时调用 disconnect()
  • 移除事件监听器

十、快速参考

基本流程

// 1. 扫描选择const device =await navigator.bluetooth.requestDevice({ filters:[{ services:['SERVICE_UUID']}]});// 2. 连接const gattServer =await device.gatt.connect();// 3. 获取服务const service =await gattServer.getPrimaryService('SERVICE_UUID');// 4. 获取特征const char =await service.getCharacteristic('CHARACTERISTIC_UUID');// 5. 读取或监听// 方式 A:启用通知await char.startNotifications(); char.addEventListener('characteristicvaluechanged',event=>{const data = event.target.value;});// 方式 B:手动读取const data =await char.readValue();// 6. 写入await char.writeValue(newUint8Array([0x01,0x02]));// 7. 清理await char.stopNotifications(); device.gatt.disconnect();

数据提取

// DataView 方法 value.getUint8(index);// 无符号字节 value.getInt8(index);// 有符号字节 value.getUint16(index,true);// 无符号 16 位 value.getInt16(index,true);// 有符号 16 位 value.getUint32(index,true);// 无符号 32 位 value.getInt32(index,true);// 有符号 32 位 value.getFloat32(index,true);// 浮点数 32 位 value.getFloat64(index,true);// 浮点数 64 位

总结

Web Bluetooth API 的核心就是这 7 个步骤:

  1. 扫描requestDevice()
  2. 连接gatt.connect()
  3. 获取服务getPrimaryService()
  4. 获取特征getCharacteristic()
  5. 启用通知或读取startNotifications()readValue()
  6. 发送或接收数据writeValue() 或监听事件
  7. 清理disconnect()

掌握这 7 个 API,你就可以与任何蓝牙设备通信!

Could not load content