跳到主要内容
Web Bluetooth API 实战指南:从设备连接到数据交互 | 极客日志
JavaScript 大前端
Web Bluetooth API 实战指南:从设备连接到数据交互 Web Bluetooth API 允许网页直接操作蓝牙设备。核心流程涵盖设备扫描、GATT 服务器连接、服务特征获取及数据读写。通过 requestDevice 弹出选择框,利用 gatt.connect 建立链路,配合 startNotifications 实现实时数据推送。文章提供完整代码示例、Vue 集成方案及常见错误处理策略,帮助开发者快速构建跨平台蓝牙应用。
CryptoLab 发布于 2026/4/9 更新于 2026/4/24 1 浏览Web Bluetooth API 实战指南:从设备连接到数据交互
一、简介
什么是 Web Bluetooth API?
Web Bluetooth API 让网页应用能够直接与蓝牙硬件进行通信。通过这个接口,你可以实现以下功能:
扫描并连接 附近的蓝牙设备
发送命令 给设备执行操作
接收数据 从设备返回的实时信息
控制设备 的各种功能模块
适用场景
医疗设备、手环、手表、传感器、遥控器、音箱、智能灯具等所有支持蓝牙的设备都可以通过这个 API 与网页应用通信。
浏览器支持情况
浏览器 支持状态 最低版本 Chrome/Edge ✅ 原生支持 56+ / 79+ Firefox ⚠️ 需手动启用 98+ Safari ❌ 暂不支持 -
二、核心概念(5 分钟快速理解)
2.1 蓝牙通信的三层结构
理解 GATT 协议是上手的关键。简单来说,物理设备内部运行着一个 GATT 服务器,它由多个服务组成,每个服务又包含具体的特征值。
物理设备(血压计、手环等)
└── GATT 服务器(设备内的数据服务)
├── Service(服务,定义功能模块)
│ ├── Characteristic(特征,具体的数据点)
│ └── Characteristic
└── Service
└── Characteristic
2.2 最重要的 4 个概念
名称 说明 类比 Device 蓝牙设备本身 一台手机 Service 功能模块集合 手机的相机模块 Characteristic 具体数据项 相机拍摄的照片 UUID 全局唯一标识符 身份证号
2.3 通信方式
发送数据 :Web App → 设备。使用 writeValue() 向设备发送指令。
接收数据 :设备 → Web App。主要有两种方式:
startNotifications():设备主动推送(推荐,效率高)。
readValue():手动轮询读取(备用方案)。
三、完整的 API 用法
3.1 第一步:扫描并选择设备
首先,我们需要让用户选择要连接的设备。requestDevice() 会弹出浏览器的原生对话框。
const device = await navigator.bluetooth .requestDevice ({
filters : [
{ services : ['battery_service' ] }
],
optionalServices : ['device_information' ]
});
console .log ('选中设备:' , device.name );
只有提供了指定服务的设备才会显示在列表中。
返回的是一个 Device 对象,后续操作都基于此对象。
3.2 第二步:连接到设备 拿到设备对象后,就可以尝试建立 GATT 连接了。注意这是一个异步过程。
const gattServer = await device.gatt .connect ();
console .log ('✅ 已连接' );
device.addEventListener ('gattserverdisconnected' , () => {
console .log ('❌ 设备已断开' );
});
gatt.connect() 是异步的,需要等待 Promise resolve。
设备可能会因为电量低或距离过远主动断开,务必监听 gattserverdisconnected 事件。
3.3 第三步:获取服务
const service = await gattServer.getPrimaryService ('battery_service' );
console .log ('✅ 获取服务成功' );
提示 :你需要知道目标服务的 UUID,否则无法访问其下的特征。
3.4 第四步:获取特征 特征是真正的数据承载者,比如电量百分比、心率数值等。
const characteristic = await service.getCharacteristic ('battery_level' );
console .log ('✅ 获取特征成功' );
3.5 第五步:读取数据
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, '%' );
});
3.7 第七步:写入数据(发送命令)
const command = new Uint8Array ([
0x01 ,
0x02 ,
0x03
]);
await characteristic.writeValue (command);
console .log ('✅ 命令已发送' );
注意 :必须使用 Uint8Array 格式,每个数字代表 0-255 之间的十六进制值。
3.8 第八步:禁用通知和断开连接
await characteristic.stopNotifications ();
device.gatt .disconnect ();
console .log ('✅ 已清理资源' );
四、完整的实战代码示例
4.1 封装一个简单的设备连接管理器 在实际项目中,我们通常会封装一个类来管理生命周期,避免重复代码。
class SimpleBluetoothClient {
constructor ( ) {
this .device = null ;
this .gattServer = null ;
this .service = null ;
this .characteristic = null ;
}
async connect (serviceName, characteristicName ) {
try {
this .device = await navigator.bluetooth .requestDevice ({
filters : [{ services : [serviceName] }],
optionalServices : []
});
console .log ('✅ 已选择设备:' , this .device .name );
this .gattServer = await this .device .gatt .connect ();
console .log ('✅ GATT 连接成功' );
this .service = await this .gattServer .getPrimaryService (serviceName);
console .log ('✅ 获取服务成功' );
this .characteristic = await this .service .getCharacteristic (characteristicName);
console .log ('✅ 获取特征成功' );
this .device .addEventListener ('gattserverdisconnected' , () => {
console .log ('⚠️ 设备已断开' );
this .device = null ;
});
} catch (error) {
console .error ('❌ 连接失败:' , error);
throw error;
}
}
async startListening (callback ) {
try {
await this .characteristic .startNotifications ();
console .log ('✅ 已启用通知' );
this .characteristic .addEventListener ('characteristicvaluechanged' , event => {
const value = event.target .value ;
callback (value);
});
} catch (error) {
console .error ('❌ 启用通知失败:' , error);
throw error;
}
}
async sendCommand (data ) {
try {
await this .characteristic .writeValue (data);
console .log ('✅ 命令已发送' );
} catch (error) {
console .error ('❌ 发送失败:' , error);
throw error;
}
}
async readOnce ( ) {
try {
const value = await this .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 框架中的集成示例 如果你在使用 Vue,可以将上述逻辑封装成组件或 Composable。
<template>
<div class="bluetooth-demo">
<!-- 状态 -->
<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 提取数据 接收到的原始数据通常是 DataView 类型,需要根据协议定义提取。
const value = dataView;
const byte0 = value.getUint8 (0 );
const byte1 = value.getUint8 (1 );
const signedByte = value.getInt8 (0 );
const int16 = value.getUint16 (0 , true );
const int32 = value.getUint32 (0 , true );
const float = value.getFloat32 (0 , true );
const double = value.getFloat64 (0 , true );
5.2 构造要发送的数据
const command = new Uint8Array ([
0x01 ,
0x02 ,
0x03 ,
0x04
]);
await characteristic.writeValue (command);
5.3 十六进制和数组互转工具
function arrayToHex (dataView ) {
const bytes = new Uint8Array (dataView.buffer || dataView);
return Array .from (bytes).map (b => '0x' + b.toString (16 ).padStart (2 , '0' )).join (', ' );
}
console .log (arrayToHex (value));
function hexToArray (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 ));
}
return new Uint8Array (array);
}
const arr = hexToArray ('01 02 03 04' );
await characteristic.writeValue (arr);
六、常见蓝牙服务和特征
6.1 标准服务 UUID 这些是 Bluetooth SIG 定义的标准服务,可以直接使用。
const STANDARD_SERVICES = {
'180a' : '设备信息服务' ,
'180d' : '心率服务' ,
'180b' : '血压服务' ,
'180f' : '电池服务' ,
'1800' : '通用访问' ,
'1801' : '通用属性'
};
6.2 标准特征 UUID const STANDARD_CHARACTERISTICS = {
'2a19' : '电池电量' ,
'2a29' : '制造商名称' ,
'2a24' : '型号号码' ,
'2a25' : '序列号码' ,
'2a37' : '心率测量' ,
'2a35' : '血压测量'
};
七、错误处理
7.1 常见错误类型 try {
await navigator.bluetooth .requestDevice (...);
} catch (error) {
if (error.name === 'NotFoundError' ) {
console .log ('用户取消了设备选择' );
} else if (error.name === 'NotSupportedError' ) {
console .log ('浏览器不支持 Web Bluetooth API' );
} else if (error.name === 'SecurityError' ) {
console .log ('需要在 HTTPS 或 localhost 上运行' );
} else if (error.name === 'NetworkError' ) {
console .log ('蓝牙通信失败,设备可能已离线' );
} else {
console .error ('未知错误:' , error.name );
}
}
7.2 重连机制 async function connectWithRetry (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 ) {
await new Promise (resolve => setTimeout (resolve, 1000 ));
}
}
}
throw new Error ('连接失败,已达到最大重试次数' );
}
八、调试技巧
8.1 打印数据便于调试
function debugData (dataView ) {
const bytes = new Uint8Array (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 在浏览器中查看已连接的设备
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()。
移除不必要的事件监听器。
十、快速参考
基本流程
const device = await navigator.bluetooth .requestDevice ({
filters : [{ services : ['SERVICE_UUID' ] }]
});
const gattServer = await device.gatt .connect ();
const service = await gattServer.getPrimaryService ('SERVICE_UUID' );
const char = await service.getCharacteristic ('CHARACTERISTIC_UUID' );
await char.startNotifications ();
char.addEventListener ('characteristicvaluechanged' , event => {
const data = event.target .value ;
});
const data = await char.readValue ();
await char.writeValue (new Uint8Array ([0x01 , 0x02 ]));
await char.stopNotifications ();
device.gatt .disconnect ();
数据提取
value.getUint8 (index);
value.getInt8 (index);
value.getUint16 (index, true );
value.getInt16 (index, true );
value.getUint32 (index, true );
value.getInt32 (index, true );
value.getFloat32 (index, true );
value.getFloat64 (index, true );
总结 Web Bluetooth API 的核心就是这 7 个步骤:
扫描 → requestDevice()
连接 → gatt.connect()
获取服务 → getPrimaryService()
获取特征 → getCharacteristic()
启用通知或读取 → startNotifications() 或 readValue()
发送或接收数据 → writeValue() 或监听事件
清理 → disconnect()
掌握这 7 个 API,你就可以与任何蓝牙设备通信!
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online