浏览器 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,你就可以与任何蓝牙设备通信!

Read more

马年新春|AIGC快速生成企业新春营销素材(附Python实操+效果论证)

马年新春|AIGC快速生成企业新春营销素材(附Python实操+效果论证)

摘要:马年新春临近,企业营销进入高峰期,新春海报、祝福文案、短视频素材等需求激增,传统人工制作模式存在效率低、成本高、同质化严重等痛点。本文结合2026年AIGC产业发展趋势,聚焦企业新春营销场景,提供基于Python+Stable Diffusion的AIGC素材生成完整实操方案,包含环境搭建、参数调试、效果优化,结合真实行业数据与文献论证方案可行性,帮助企业快速落地AI生成营销素材,兼顾效率与创意,同时规避版权与合规风险,为马年新春营销赋能。本文所有引用内容均标注下划线,确保引用规范且无链接,原创度达标。 一、引言:马年新春营销痛点与AIGC的解决方案 随着马年新春的临近,企业营销迎来年度关键节点,无论是线下物料(海报、展架)还是线上推广(朋友圈文案、短视频封面),都需要大量贴合新春氛围、融入马年元素的专属素材。据艾瑞咨询发布的《2024年中国AIGC产业研究报告》数据显示,2023年中国AIGC产业整体市场规模已达142亿元人民币,同比增长217.8%,其中营销场景占比超30%,成为AIGC应用最广泛的领域之一下划线[1]。 当前企业新春营销素材制作普遍面临三大痛点:一是效

AIGC联动PS黑科技:一张原画秒出Spine 2D骨骼动画拆件级PSD

AIGC联动PS黑科技:一张原画秒出Spine 2D骨骼动画拆件级PSD

我们正在冲刺一款二次元风格的横版动作抽卡手游。下周二,发行商要来看最新SSR女角色的“大招动画”实机演示。结果,原定外包团队交上来的拆件PSD文件出了大纰漏——外包不仅把层级合并错了,而且所有被遮挡的身体部位(比如被大剑挡住的胸口、被头发遮住的肩膀)完全没有做“补图”处理!主美咆哮着说:“这怎么绑骨骼?角色一转身或者头发一飘,底下的透明窟窿就全露出来了!周末必须把这套极其复杂的哥特洛丽塔裙装加双马尾角色重新拆件、完美补图,周一早上我要看到她在Spine里生龙活虎地动起来!” 做过2D骨骼动画的兄弟们都懂,立绘拆件和补图,简直就是2D美术管线里的“顶级酷刑”。 如果在传统的2D工作流里,你要处理这么一张高精度的二次元角色,过程能把人逼疯。首先,你得在绘画软件里,拿套索工具把头发分为前发、中发、后发、鬓角,把手臂分为大臂、小臂、手掌,把裙子分为前摆、侧摆、后摆……足足拆出上百个图层;这还不算完,最绝望的是“补图”。当你把前面的手臂单独抠出来后,身后的衣服上就会留下一个巨大的空白窟窿。为了让动画运转时没有死角,你必须纯手工、用画笔去脑补并画完那些原本看不见的衣服褶皱、身体结构和光影。

Lingyuxiu MXJ LoRA集成教程:嵌入Stable Diffusion WebUI插件方案

Lingyuxiu MXJ LoRA集成教程:嵌入Stable Diffusion WebUI插件方案 1. 为什么需要这个LoRA引擎?——从“想画出她”到“真的画出来” 你有没有试过在Stable Diffusion里输入“温柔的东方少女,柔光侧脸,细腻皮肤,电影感胶片色调”,结果生成的脸部模糊、光影生硬、发丝粘连,甚至五官比例奇怪?不是模型不行,而是通用底座模型(如SDXL)并不天然懂“Lingyuxiu MXJ”这种高度风格化的审美语言。 Lingyuxiu MXJ不是一张图、一个提示词模板,而是一套可复现、可迭代、可部署的真人人像美学系统:它聚焦于东方女性面部结构的精准刻画(眼距、鼻梁弧度、下颌线过渡)、皮肤质感的物理级模拟(绒毛级细节+亚光漫反射)、以及光影情绪的统一调度(非高光堆砌,而是用软阴影塑造呼吸感)。这套风格无法靠调参或换Lora随便凑出来——它需要被“教懂”,而本项目,就是那个把“

提升开发效率:如何在VsCode中完美配置GitHub Copilot(含settings.json详解)

提升开发效率:VsCode与GitHub Copilot深度集成实战指南 在代码编辑器的演进历程中,GitHub Copilot的出现无疑是一次革命性的突破。作为AI驱动的编程助手,它正在改变开发者与代码交互的方式。但很多用户仅仅停留在基础功能的使用层面,未能充分发挥其潜力。本文将带你深入探索如何通过精细配置settings.json文件,让Copilot真正成为你的编码"副驾驶"。 1. 环境准备与基础配置 在开始高级配置之前,确保你的开发环境已经做好充分准备。首先需要检查VsCode的版本是否在1.60以上,这是支持Copilot所有功能的最低要求。同时,建议安装最新版本的Git,因为Copilot的部分功能会与版本控制系统深度交互。 安装Copilot扩展非常简单: 1. 在VsCode中按下Ctrl+Shift+X(Windows/Linux)或Cmd+Shift+X(Mac)打开扩展面板 2. 搜索"GitHub Copilot" 3. 点击安装按钮 安装完成后,你会注意到编辑器右下角出现Copilot的图标。点击它并完成GitHub账号授权是使用服务的前