跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端

浏览器 Web Bluetooth API 使用方法

浏览器 Web Bluetooth API 的使用方法,涵盖核心概念如设备、服务、特征及 UUID,以及完整的通信流程。内容包括如何扫描并选择设备、连接 GATT 服务器、获取服务与特征、读写数据(包括通知模式)、数据转换方法(DataView 处理)、标准 UUID 参考、错误处理机制及重连策略。文章还提供了 JavaScript 类封装示例和 Vue 组件集成方案,帮助开发者实现网页与蓝牙设备的交互。

林间仙子发布于 2026/4/6更新于 2026/9/888 浏览
浏览器 Web Bluetooth API 使用方法

浏览器 Web Bluetooth API 完整指南

一、简介

什么是 Web Bluetooth API?

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

  • 扫描并连接蓝牙设备
  • 发送命令到设备
  • 接收数据从设备返回
  • 控制设备的各种操作
适用场景

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

浏览器支持
浏览器支持最低版本
Chrome/Edge✅56+ / 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({
  // 按服务过滤(设备必须提供这些服务才会出现)
  : [{ : [] }], 
  
  : []
});
.(, device.);
filters
services
'battery_service'
// 电池服务
// 可选:列出可能需要的其他服务
optionalServices
'device_information'
console
log
'选中设备:'
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 = new Uint8Array([
  0x01, // 命令代码
  0x02, // 参数 1
  0x03  // 参数 2
]);
// 发送数据到设备
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;
  }

  /**
   * 连接设备
   * @param {string} serviceName - 服务名称(如 'battery_service')
   * @param {string} characteristicName - 特征名称(如 'battery_level')
   */
  async connect(serviceName, characteristicName) {
    try {
      // 1. 让用户选择设备
      this.device = await navigator.bluetooth.requestDevice({
        filters: [{ services: [serviceName] }],
        optionalServices: []
      });
      console.log('✅ 已选择设备:', this.device.name);

      // 2. 连接到设备
      this.gattServer = await this.device.gatt.connect();
      console.log('✅ GATT 连接成功');

      // 3. 获取服务
      this.service = await this.gattServer.getPrimaryService(serviceName);
      console.log('✅ 获取服务成功');

      // 4. 获取特征
      this.characteristic = await this.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 - 收到数据时的回调
   */
  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;
    }
  }

  /**
   * 发送命令
   * @param {Uint8Array} data - 要发送的数据
   */
  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 中使用
<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-255
const 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 = new Uint8Array([
  0x01, // 第 1 字节:命令 ID
  0x02, // 第 2 字节:参数 1
  0x03, // 第 3 字节:参数 2
  0x04  // 第 4 字节:参数 3
]);

// 发送
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)); // 0x01, 0x02, 0x03, ...

// 十六进制字符串转数组
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') {
    // 浏览器不支持 Web Bluetooth
    console.log('浏览器不支持 Web Bluetooth API');
  } else if (error.name === 'SecurityError') {
    // 需要 HTTPS 或 localhost
    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) {
        // 等待 1 秒后重试
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    }
  }
  throw new Error('连接失败,已达到最大重试次数');
}

八、调试技巧

8.1 打印数据便于调试
// 将 DataView 转为可读的格式
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 在浏览器中查看已连接的设备
// 在 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(new Uint8Array([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,你就可以与任何蓝牙设备通信!

目录

  1. 浏览器 Web Bluetooth API 完整指南
  2. 一、简介
  3. 什么是 Web Bluetooth API?
  4. 适用场景
  5. 浏览器支持
  6. 二、核心概念(5 分钟快速理解)
  7. 2.1 蓝牙通信的三层结构
  8. 2.2 最重要的 4 个概念
  9. 2.3 通信方式
  10. 三、完整的 API 用法
  11. 3.1 第一步:扫描并选择设备
  12. 3.2 第二步:连接到设备
  13. 3.3 第三步:获取服务
  14. 3.4 第四步:获取特征
  15. 3.5 第五步:读取数据
  16. 3.6 第六步:启用通知(推荐)
  17. 3.7 第七步:写入数据(发送命令)
  18. 3.8 第八步:禁用通知和断开连接
  19. 四、完整的实战代码示例
  20. 4.1 简单的设备连接管理器
  21. 4.2 在 Vue 中使用
  22. 五、数据转换方法
  23. 5.1 从 DataView 提取数据
  24. 5.2 构造要发送的数据
  25. 5.3 十六进制和数组互转
  26. 六、常见蓝牙服务和特征
  27. 6.1 标准服务 UUID
  28. 6.2 标准特征 UUID
  29. 七、错误处理
  30. 7.1 常见错误类型
  31. 7.2 重连机制
  32. 八、调试技巧
  33. 8.1 打印数据便于调试
  34. 8.2 在浏览器中查看已连接的设备
  35. 8.3 监听所有事件
  36. 九、使用前的检查清单
  37. ✅ 环境要求
  38. ✅ 连接步骤
  39. ✅ 数据处理
  40. ✅ 资源清理
  41. 十、快速参考
  42. 基本流程
  43. 数据提取
  44. 总结

更多推荐文章

查看全部
  • 青少年机器人编程系统化学习路径:从机械启蒙到人工智能
  • 基于 Rokid 灵珠平台搭建旅游 AR 智能体指南
  • 基于 React 19+Vite+TypeScript 的现代前端项目初始化实战
  • PyCharm 基础操作指南:快速入门
  • C++ io_uring 原理与使用指南
  • Apache IoTDB 全场景部署:跨「端 - 边 - 云」的时序数据库实践
  • OpenCode 结合 GitHub Copilot 实现 Claude Code 替代方案
  • 攻防世界 Web 实战:弱类型绕过与文件包含漏洞分析
  • React Router 页面跳转的三种实现方式
  • 银行大模型应用现状与落地进展分析
  • YOLOv8 算法详解:架构、核心创新与部署实践
  • 2023 年电赛 H 题信号分离装置 FPGA+STM32 解法
  • DeepSeek-R1 大模型基于 MS-Swift 框架的部署、推理与微调实践
  • Oracle 指定表字段批量转为大写
  • OpenClaw 对接本地 Ollama 与 Qwen WebUI 无响应排查
  • AI+游戏开发:使用 DeepSeek 构建贪吃蛇游戏
  • AI 绘画与摄影:ChatGPT、Midjourney 与文心一格工具解析
  • Stable Diffusion 提示词高阶用法实战:从精准控制到风格迁移
  • Spring Cloud Nacos 服务注册与配置中心实战
  • Llama-Factory 训练进度条卡死排查与优化指南

相关免费在线工具

  • 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