简介:Web Bluetooth API是一项新兴的Web技术,允许网页应用直接与蓝牙低功耗(BLE)设备进行安全通信,无需依赖本地应用程序。该存储库“WebBluetoothAPI-main”集中存放了与Web Bluetooth API相关的代码、示例程序和开发工具,重点支持基于BLE UART协议的设备交互,适用于健康监测、智能家居和工业传感等场景。项目包含HTML用户界面与JavaScript核心逻辑,提供设备发现、连接、数据读写等完整流程的实现,帮助开发者快速构建可在浏览器中运行的蓝牙控制应用。
1. Web Bluetooth API 技术概述
随着物联网(IoT)技术的迅猛发展,浏览器与物理设备之间的直接通信成为可能。Web Bluetooth API 作为一项前沿的 Web 标准,允许网页通过 JavaScript 安全地与附近的低功耗蓝牙(BLE)设备进行交互,打破了传统上需要原生应用才能访问蓝牙硬件的限制。该 API 由 W3C 和 Bluetooth SIG 联合推动,旨在实现跨平台、无需安装额外软件即可控制智能硬件的目标。
目前,Web Bluetooth API 已在 Chrome(v56+) 、 Edge(基于 Chromium) 等主流浏览器中得到支持,适用于 Windows 10、macOS、Linux 及 Android 等操作系统,但在 Safari 和 iOS 上仍受限。其典型应用场景涵盖智能家居控制(如灯光调节)、可穿戴设备数据读取(如心率监测)、医疗健康设备集成等。
graph LR
A[Web 应用] -->|JavaScript| B(Web Bluetooth API)
B --> C{浏览器安全策略}
C --> D[BLE 设备]
D --> E((GATT Server))
本章为后续深入理解 BLE 通信机制与开发实践奠定基础。
2. BLE 低功耗蓝牙通信原理
蓝牙技术自1994年由爱立信提出以来,历经多次迭代与架构革新,逐步从一种短距离无线语音通信协议演变为支持海量物联网设备互联的核心无线标准。其中,低功耗蓝牙(Bluetooth Low Energy, BLE)作为蓝牙4.0规范的重要组成部分,专为电池供电、间歇性通信的智能设备设计,在保持通信能力的同时显著降低了能耗。本章深入剖析BLE的底层通信机制,涵盖其协议栈结构、广播连接流程、数据组织模型以及安全与功耗优化策略,为理解Web Bluetooth API如何在浏览器中实现对BLE设备的控制提供坚实的技术基础。
2.1 蓝牙技术演进与 BLE 架构
随着移动互联网和可穿戴设备的兴起,传统经典蓝牙(Classic Bluetooth)在持续连接、高带宽传输场景下的优势逐渐被其高功耗缺陷所抵消。为此,蓝牙技术联盟(Bluetooth SIG)于2010年推出蓝牙4.0标准,首次引入低功耗蓝牙(BLE),标志着蓝牙技术进入“双模并行”时代——即设备可同时支持经典蓝牙和BLE,或仅运行在纯BLE模式下以最大化节能效果。
2.1.1 经典蓝牙与低功耗蓝牙的区别
尽管两者共享2.4GHz ISM频段和跳频扩频技术,但经典蓝牙与BLE在设计目标、协议结构和应用场景上存在本质差异。
| 对比维度 | 经典蓝牙(BR/EDR) | 低功耗蓝牙(BLE) |
|---|---|---|
| 主要用途 | 音频流传输(如耳机)、文件传输 | 传感器数据上报、远程控制、状态同步 |
| 功耗水平 | 较高,适合持续供电设备 | 极低,适用于纽扣电池供电设备 |
| 数据速率 | 1–3 Mbps | 1–2 Mbps(BLE 5.x可达更高) |
| 连接方式 | 点对点主从配对,连接稳定持久 | 快速建立/断开连接,支持一对多广播 |
| 协议复杂度 | 高,依赖SCO/eSCO链路 | 简化协议栈,基于事件驱动 |
| 通信模式 | 持续连接,周期性数据交换 | 广播+按需连接,事件触发式交互 |
例如,一个无线鼠标使用经典蓝牙时需维持长时间连接以保证指针移动的实时性;而一个心率监测手环则通过BLE周期性广播心率值,手机只需在需要时扫描并短暂连接获取数据即可,其余时间设备处于深度睡眠状态,极大延长了续航。
这种根本性的设计理念转变使得BLE更适合现代Web应用中的轻量级设备交互场景——用户点击网页按钮请求连接某个智能灯泡,完成调光后立即断开,整个过程耗时不足1秒,且对设备电量影响微乎其微。
2.1.2 BLE 协议栈分层结构(PHY、LL、HCI、L2CAP、ATT、GATT)
BLE采用分层协议架构,各层职责清晰、模块化强,便于跨平台实现与调试。以下是BLE协议栈的关键层级及其功能说明:
graph TD
A[Application] --> B[GATT]
B --> C[ATT]
C --> D[L2CAP]
D --> E[SM / GAP]
E --> F[Security Manager / Generic Access Profile]
F --> G[Host Control Interface (HCI)]
G --> H[Link Layer (LL)]
H --> I[Physical Layer (PHY)]
物理层(PHY)
负责实际的无线信号调制解调,工作在2.4GHz ISM频段的40个信道上(37个数据信道 + 3个广告信道)。BLE采用GFSK调制方式,发射功率通常为-20dBm至+10dBm,支持多种物理层速率(如1M PHY、2M PHY、Coded PHY用于远距离)。
链路层(Link Layer, LL)
管理射频收发行为,处理广播、扫描、连接建立等底层操作。LL定义了五种设备状态:待机、广告、扫描、发起连接、连接状态,并控制跳频序列与定时窗口。
主机控制接口(HCI)
作为软硬件之间的桥梁,允许主机(Host,如手机CPU)通过命令、事件和数据包与控制器(Controller,如蓝牙芯片)通信。在Web环境中,操作系统已封装HCI细节,开发者无需直接操作。
L2CAP(逻辑链路控制与适配协议)
提供多路复用机制,将不同高层协议的数据流映射到同一物理连接上。BLE中常用固定通道CID(Channel ID),如:
- CID=4:ATT协议
- CID=6:安全管理协议(SMP)
ATT(Attribute Protocol)
是BLE数据访问的核心协议,所有特征值读写均通过ATT完成。它将数据抽象为“属性”,每个属性包含句柄(Handle)、类型(UUID)、权限(读/写/通知)和值字段。
GATT(Generic Attribute Profile)
构建在ATT之上,定义了客户端(Client)与服务器(Server)之间的数据交互规则。GATT规定了服务发现流程、特征值订阅机制和服务组织形式,是Web Bluetooth API直接操作的对象模型。
这些层次共同构成了BLE通信的基础骨架,使上层应用能够以标准化的方式访问设备功能。
2.1.3 主从设备角色与广播机制
在BLE网络中,设备分为 中心设备(Central) 和 外围设备(Peripheral) ,分别对应经典蓝牙中的“主”与“从”。
- 外围设备(Peripheral) :通常是传感器、标签、手环等资源受限设备,运行GATT服务器,主动广播自身存在,并响应来自中心设备的连接请求。
- 中心设备(Central) :如智能手机、笔记本电脑,具备更强计算能力,负责扫描广告包、发起连接、读取GATT服务。
典型的工作流程如下:
- 外围设备每隔一段时间(如100ms)发送一次广播包(Advertising PDU),内容包括设备名称、服务UUID、发射功率等;
- 中心设备开启扫描,监听3个广播信道(37/38/39);
- 用户选择目标设备后,中心设备向其发送连接请求;
- 双方进入连接状态,切换至数据信道进行GATT通信。
值得注意的是,BLE支持多种广播类型,包括:
- 可连接无定向广播(ADV_IND) :最常见的类型,允许任何设备发起连接;
- 不可连接广播(ADV_NONCONN_IND) :仅用于广播信息,不接受连接(如iBeacon);
- 扫描响应(SCAN_RSP) :补充广告数据,需由扫描者主动请求。
该机制确保了设备能在低功耗前提下高效暴露自身服务能力,也为Web应用提供了“发现即用”的用户体验可能。
2.2 BLE 广播与连接过程详解
BLE通信的第一步是设备发现,依赖于广播与扫描机制。这一阶段不仅决定了设备能否被正确识别,还直接影响连接成功率和用户体验。
2.2.1 广播包类型(Advertising PDU)与有效载荷解析
BLE广播帧被称为 Advertising PDU(Protocol Data Unit) ,其格式如下:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| Preamble | 1 | 同步头,固定为0xAA或0x55 |
| Access Address | 4 | 固定为0x8E89BED6,标识BLE广播包 |
| PDU Header | 2 | 包含PDU类型、长度等信息 |
| Payload | 0–37 | 实际携带的数据,最多37字节 |
| CRC | 3 | 校验码 |
Payload部分由一系列“AD Structures”组成,每项结构格式为:
[Length][AD Type][AD Data...]
常见AD类型示例:
| AD Type (Hex) | 名称 | 示例数据 |
|---|---|---|
| 0x01 | Flags | 0x06(LE General Discoverable Mode + BR/EDR not supported) |
| 0x03 | 16-bit Service UUIDs | 0xFEAA(Eddystone) |
| 0x09 | Complete Local Name | “Smart Thermometer” |
| 0x0A | TX Power Level | -59 dBm |
假设某设备广播包Payload为:
02 01 06 03 03 AA FE 0C 09 53 6D 61 72 74 20 54 68 65 72 6D 6F
逐段解析:
- 02 01 06 → 长度2,类型0x01(Flags),值0x06 → 可发现模式;
- 03 03 AA FE → 长度3,类型0x03(16-bit UUID),值FEAA → Google Eddystone服务;
- 0C 09 ... → 长度12,类型0x09(Name),后续ASCII字符为”Smart Thermometer”。
这类结构化编码允许中心设备快速判断是否为目标设备,Web Bluetooth API中的 filters 参数正是基于此进行匹配筛选。
2.2.2 扫描响应与主动/被动扫描模式
为了突破37字节广播限制,BLE支持 扫描响应(Scan Response) 机制。当中心设备收到广告包后,若希望获取更多信息,可发送 SCAN_REQ 请求,外围设备回应 SCAN_RSP 包,其格式与广告包相同,常用于携带完整设备名或厂商数据。
两种扫描模式对比:
| 模式 | 是否发送SCAN_REQ | 获取信息量 | 功耗 |
|---|---|---|---|
| 被动扫描 | 否 | 仅广告包内容 | 低 |
| 主动扫描 | 是 | 广告包 + 扫描响应 | 较高 |
在Web应用中,浏览器通常采用被动扫描以减少干扰,但在某些情况下可通过主动扫描获取更完整的设备描述信息。
2.2.3 建立连接的时序流程与参数协商
一旦用户选定设备,浏览器将触发连接请求。连接建立是一个精确计时的过程,涉及多项参数协商:
sequenceDiagram
participant Central as 中心设备
participant Peripheral as 外围设备
Peripheral->>Central: ADV_IND on Channel 37
Central->>Peripheral: CONNECT_REQ (Initiator Address, Access Address, etc.)
Peripheral-->>Central: 进入连接状态
note right of Peripheral: 使用Access Address和Hop Interval建立同步
Central->>Peripheral: 发起GATT服务发现
关键连接参数包括:
| 参数 | 描述 | 影响 |
|---|---|---|
| Connection Interval | 主从设备通信的时间间隔(单位:1.25ms) | 决定响应速度与功耗,范围7.5ms–4s |
| Slave Latency | 从机可跳过的连接事件次数 | 提高节能性,但增加延迟 |
| Supervision Timeout | 最大允许无响应时间(单位:10ms) | 决定连接稳定性 |
例如,设置Connection Interval=50ms(即40个单位),Slave Latency=4,则从设备每200ms才需唤醒一次,大幅降低功耗。
Web Bluetooth API虽不直接暴露这些参数,但底层操作系统会根据设备能力和当前系统负载自动协商最优配置。
2.3 GATT 模型与数据组织方式
GATT(Generic Attribute Profile)是BLE应用层数据交换的标准框架,几乎所有BLE设备都遵循GATT模型来组织其功能接口。
2.3.1 服务(Service)、特征值(Characteristic)与描述符(Descriptor)的关系
GATT采用树形结构组织数据:
Device
└── Service (UUID: 0x180D)
├── Characteristic (UUID: 0x2A37)
│ ├── Value: [0x06, 0x60]
│ └── Descriptor (Client Characteristic Configuration)
└── Characteristic (UUID: 0x2A38)
└── Value: ...
- 服务(Service) :代表一类功能集合,如“心率服务”(Heart Rate Service)、“设备信息服务”(Device Information Service)。
- 特征值(Characteristic) :服务内的具体数据点,包含一个值和多个属性(如可读、可写、支持通知)。
- 描述符(Descriptor) :附加于特征值的元数据,最常见的是
CCCD(Client Characteristic Configuration Descriptor),用于启用通知或指示。
每个元素都有唯一的16位或128位UUID标识。标准UUID由Bluetooth SIG分配,自定义服务应使用128位UUID避免冲突。
2.3.2 属性协议 ATT 的读写操作机制
所有GATT操作最终转化为ATT协议指令。常见的ATT操作码包括:
| OpCode | 操作 | 示例 |
|---|---|---|
| 0x0B | Read Request | 客户端请求读取某特征值 |
| 0x0C | Read Response | 服务器返回数据 |
| 0x12 | Write Request | 写入特征值(需等待确认) |
| 0x52 | Write Command | “无响应写”,适用于频繁更新场景 |
代码示例(模拟JavaScript中通过Web Bluetooth API写入特征值):
const characteristic = await service.getCharacteristic('00002a37-0000-1000-8000-00805f9b34fb');
await characteristic.writeValue(new Uint8Array([1]).buffer);
console.log('Command sent via ATT Write Request');
执行逻辑分析:
1. getCharacteristic() 触发ATT Discover Characteristics 流程,查找匹配UUID的特征;
2. writeValue() 将数组转换为 ArrayBuffer ,封装为ATT Write Request 发送给设备;
3. 设备处理成功后回传 Write Response ,Promise解析;
4. 若使用 writeValueWithoutResponse() ,则发送 Write Command ,不等待响应,提升效率但牺牲可靠性。
参数说明:
- new Uint8Array([1]) 表示发送数值1,常用于开启传感器;
- .buffer 转换为 ArrayBuffer 类型,符合Web Bluetooth API要求;
- UUID为标准心率测量特征值。
2.3.3 典型 GATT 配置文件(如 Heart Rate Profile)示例
以Heart Rate Profile为例,其GATT结构如下表所示:
| 层级 | UUID | 属性 | 说明 |
|---|---|---|---|
| Service | 0x180D | - | 心率服务 |
| └─ HR Measurement | 0x2A37 | Notify | 心率数据,支持通知 |
| └─ Body Sensor Location | 0x2A38 | Read | 传感器佩戴位置(胸部、手腕等) |
| └─ HR Control Point | 0x2A39 | Write | 控制命令(如重置能量扩展) |
网页应用可通过订阅 0x2A37 特征值的通知,实时接收心率变化:
await characteristic.startNotifications();
characteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const heartRate = value.getUint8(1); // 解析第二个字节
console.log(`Current Heart Rate: ${heartRate} BPM`);
});
逻辑解读:
- startNotifications() 向设备写入CCCD描述符,启用通知;
- 每当设备有新数据,自动推送 characteristicvaluechanged 事件;
- 使用 DataView.getUint8() 解析原始二进制数据,提取心率值。
该模式广泛应用于健康监测、环境传感等领域。
2.4 BLE 通信安全性与功耗优化
2.4.1 加密配对流程(Just Works, Passkey Entry)
BLE支持三种主要配对模式:
| 模式 | 安全性 | 用户交互 | 适用场景 |
|---|---|---|---|
| Just Works | 无MITM保护 | 无 | 不敏感数据(如灯光控制) |
| Passkey Entry | 抵抗MITM攻击 | 输入6位数字 | 高安全需求设备 |
| Out of Band (OOB) | 高安全性 | NFC/二维码辅助 | 工业级设备 |
配对过程涉及以下步骤:
1. Pairing Request/Response :协商I/O能力与认证方式;
2. 生成临时密钥(TK) :根据模式决定;
3. 密钥分发 :交换长期密钥(LTK)、身份密钥(IRK)等;
4. 加密连接建立 。
Web Bluetooth API默认使用“Just Works”模式,除非设备强制要求输入PIN码。出于安全考虑,敏感设备应在固件层面要求Passkey验证。
2.4.2 连接间隔、从机延迟与功耗平衡策略
设备功耗主要取决于射频活动频率。优化策略包括:
- 增大连接间隔 :如设为500ms以上,适合温湿度传感器;
- 启用从机延迟 :允许跳过若干连接事件,进一步节能;
- 使用Write Without Response :减少ACK通信开销;
- 及时断开连接 :空闲时主动释放资源。
例如,某智能门锁在未操作时广播间隔设为1s,连接后设置Connection Interval=100ms以保证响应速度,操作完成后30秒内无交互即自动断开。
综上所述,BLE通过精巧的协议设计实现了高性能与低功耗的统一,为Web端直接操控物理世界提供了可行路径。下一章将聚焦于开源库的设计结构,揭示如何将上述底层机制封装为易用的JavaScript接口。
3. WebBluetoothAPI 存储库结构与使用指南
随着 Web Bluetooth 技术的逐步普及,围绕其构建的开源生态也日益成熟。目前 GitHub 上已有多个以 web-bluetooth 为核心的项目仓库,如 GoogleChromeLabs 的 web-bluetooth 示例库、社区驱动的 @abandonware/bluetooth 封装库等。这些存储库不仅提供了基础 API 使用范例,更通过模块化设计和工程化实践为开发者构建可维护、可扩展的 Web 蓝牙应用奠定了坚实基础。本章将深入剖析典型 WebBluetooth 开源项目的整体架构,解析其目录组织逻辑、依赖管理机制以及核心封装模式,并结合实际操作步骤指导如何在本地环境中部署运行示例程序。同时,还将探讨现代前端工程中常见的工具类抽象方式、事件驱动模型的应用场景,以及如何遵循标准版本控制流程参与项目贡献。
3.1 开源存储库的功能定位与架构设计
现代 WebBluetooth 相关开源项目通常不仅仅是一个简单的代码集合,而是具备完整软件生命周期支持的工程化解决方案。它们的功能定位涵盖 演示性示例 、 API 抽象封装 、 跨平台兼容性适配 及 开发辅助工具集成 等多个维度。这类项目往往采用分层架构设计,确保高内聚低耦合,便于团队协作与长期维护。
3.1.1 项目目录结构解析(src/, examples/, docs/)
一个典型的 WebBluetooth 存储库会遵循标准化的前端项目布局规范,其主要目录包括:
| 目录 | 功能说明 |
|---|---|
/src | 核心源码目录,包含蓝牙设备抽象类、连接管理器、数据处理器等可复用模块 |
/examples | 提供多个独立 HTML 示例页面,用于演示不同 BLE 设备的交互逻辑(如 LED 控制、传感器读取) |
/docs | 文档生成输出目录,通常由 Markdown 源文件经静态站点生成器(如 Docusaurus 或 VitePress)编译而成 |
/tests | 单元测试与端到端测试脚本存放位置,使用 Jest 或 Playwright 实现自动化验证 |
/scripts | 构建、部署、格式化等辅助脚本(如启动本地服务器、打包生产资源) |
package.json | 定义项目元信息、依赖项、构建命令与发布配置 |
下面是一个具体示例的目录结构:
web-bluetooth-demo/
├── src/
│ ├── bluetooth/
│ │ ├── DeviceManager.js
│ │ ├── ServiceHandler.js
│ │ └── utils/DataConverter.js
│ └── index.js
├── examples/
│ ├── led-control.html
│ ├── heart-rate-monitor.html
│ └── uart-terminal.html
├── docs/
│ └── guide.md
├── tests/
│ └── unit/device-manager.test.js
├── scripts/
│ └── start-server.js
├── package.json
└── README.md
该结构体现了清晰的关注点分离原则:业务逻辑集中于 src ,用户界面展示集中在 examples ,文档与测试则各自独立。这种组织方式极大提升了项目的可读性和可维护性,尤其适合多人协作或作为企业级模板使用。
依赖关系可视化:Mermaid 流程图
以下为该项目模块间依赖关系的 Mermaid 图表示意:
graph TD
A[examples/led-control.html] --> B[src/index.js]
B --> C[src/bluetooth/DeviceManager.js]
C --> D[src/bluetooth/ServiceHandler.js]
D --> E[src/utils/DataConverter.js]
F[tests/unit/device-manager.test.js] --> C
G[scripts/start-server.js] --> H[http server]
从图中可见, examples 中的 HTML 页面通过引入 index.js 启动蓝牙控制流程,逐层调用底层服务处理模块。测试文件直接引用 DeviceManager 进行单元验证,而本地服务器脚本负责提供 HTTPS 环境支持——这是 Web Bluetooth API 正常工作的前提条件之一。
3.1.2 核心模块划分与依赖管理(package.json 分析)
现代 WebBluetooth 项目普遍基于 Node.js 生态进行开发与构建,因此 package.json 成为理解其技术栈的关键入口。以下是一个典型 package.json 文件的核心片段:
{
"name": "web-bluetooth-starter",
"version": "1.0.0",
"description": "A starter kit for building Web Bluetooth applications",
"main": "src/index.js",
"scripts": {
"start": "node scripts/start-server.js",
"test": "jest --config jest.config.js",
"build": "vite build",
"format": "prettier --write \"src/**/*.js\""
},
"dependencies": {
"@abandonware/bluetooth": "^6.5.0",
"buffer": "^6.0.3"
},
"devDependencies": {
"jest": "^29.7.0",
"prettier": "^3.0.3",
"vite": "^4.5.0",
"vite-plugin-mkcert": "^1.14.0"
},
"keywords": ["web", "bluetooth", "ble", "iot"],
"author": "Dev Team",
"license": "MIT"
}
参数说明与逻辑分析:
-
"main"字段指定了主入口模块路径,即当其他项目通过import引入此包时,默认加载src/index.js。 -
"scripts"定义了常用命令: -
npm start启动本地 HTTPS 服务器(必须启用 TLS 才能访问 Web Bluetooth API) -
npm test执行 Jest 编写的单元测试 -
npm run build使用 Vite 构建生产环境静态资源 -
npm run format自动格式化 JavaScript 文件 -
dependencies列出了运行时必需的第三方库: -
@abandonware/bluetooth是一个对原生 Web Bluetooth API 的轻量级封装,增强了错误处理和设备发现能力 -
buffer提供了 Node.js 风格的 Buffer 支持,方便在浏览器中处理二进制数据(ArrayBuffer ↔ Buffer 转换) -
devDependencies包含开发阶段所需的工具链: -
vite作为现代前端构建工具,支持快速热更新 -
vite-plugin-mkcert用于生成本地可信 SSL 证书,绕过 Chrome 对非安全上下文的限制
代码块解读:Vite 插件配置示例
// vite.config.js
import { defineConfig } from 'vite';
import mkcert from 'vite-plugin-mkcert';
export default defineConfig({
plugins: [
mkcert() // 自动生成 localhost 的可信证书
],
server: {
https: true, // 必须开启 HTTPS
port: 8080,
host: 'localhost'
}
});
逐行解释 :
- 第 1 行导入 Vite 的配置函数;
- 第 2 行引入mkcert插件,用于创建本地 CA 和证书;
- 第 5 行注册插件,使其在 dev server 启动时自动签发证书;
- 第 9 行强制启用 HTTPS,因为 Web Bluetooth API 只能在安全上下文中运行;
- 第 10–11 行设置监听端口和主机名。
这一配置确保开发者无需手动配置 OpenSSL 或 IIS Express,即可在 https://localhost:8080 下安全调试蓝牙功能,显著降低入门门槛。
3.2 环境搭建与快速入门实践
要成功运行 WebBluetooth 应用,必须满足两个关键条件: HTTPS 安全上下文 和 支持 Web Bluetooth 的浏览器环境 。本节将详细介绍本地开发环境的搭建流程,并通过一个“点亮 BLE LED”的经典示例引导读者完成首次设备交互。
3.2.1 Node.js 环境配置与本地服务器部署
首先确认已安装最新 LTS 版本的 Node.js(建议 v18+),然后执行以下步骤初始化项目:
# 初始化项目
npm init -y
# 安装 Vite 和 mkcert 插件
npm install --save-dev vite vite-plugin-mkcert
# 创建基本目录结构
mkdir src examples scripts
touch vite.config.js
接下来编写一个简易的 HTTPS 服务器启动脚本:
// scripts/start-server.js
const { createServer } = require('vite');
const path = require('path');
async function startServer() {
const server = await createServer({
root: path.resolve(),
configFile: path.resolve('vite.config.js'),
server: {
https: true,
port: 8080
}
});
await server.listen();
server.printUrls(); // 输出访问地址
}
startServer().catch((err) => {
console.error('Failed to start server:', err);
process.exit(1);
});
逻辑分析 :
- 使用 Vite 内置的createServer方法创建开发服务器实例;
- 显式指定https: true并加载vite-plugin-mkcert生成的证书;
-printUrls()输出类似https://localhost:8080的链接供浏览器访问;
- 错误捕获机制防止因端口占用导致进程崩溃。
运行命令 npm start 后,浏览器打开 https://localhost:8080/examples/led-control.html 即可进入示例页面。
3.2.2 示例程序运行:点亮 BLE LED 设备
假设目标设备是一块搭载 Nordic nRF52 芯片的 BLE 开发板,预烧录了 UART over BLE 固件,其中定义了一个自定义服务用于控制 LED。
示例 HTML 页面结构:
<!-- examples/led-control.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>BLE LED Control</title>
<script type="module" src="../src/index.js"></script>
</head>
<body>
<button id="connect">Connect to BLE Device</button>
<button id="toggleLed">Toggle LED</button>
</body>
</html>
JavaScript 控制逻辑(简化版):
// src/index.js
const CONNECT_BTN = document.getElementById('connect');
const TOGGLE_BTN = document.getElementById('toggleLed');
let bluetoothDevice;
let characteristic;
CONNECT_BTN.addEventListener('click', async () => {
try {
bluetoothDevice = await navigator.bluetooth.requestDevice({
filters: [{ services: ['6e400001-b5a3-f393-e0a9-e50e24dcca9e'] }] // Nordic UART Service
});
const server = await bluetoothDevice.gatt.connect();
const service = await server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
characteristic = await service.getCharacteristic('6e400002-b5a3-f393-e0a9-e50e24dcca9e'); // TX Characteristic
TOGGLE_BTN.disabled = false;
console.log('Connected to device!');
} catch (error) {
console.error('Connection failed:', error);
}
});
TOGGLE_BTN.addEventListener('click', async () => {
const value = new Uint8Array([0x01]); // 发送 0x01 表示开灯
await characteristic.writeValue(value);
});
逐行解释 :
- 第 7–14 行请求设备时使用filters限定仅显示具有特定 UUID 服务的设备;
- 第 16 行建立 GATT 连接;
- 第 17–18 行获取主服务及其写入特征值(TX);
- 第 25 行构造一个单字节数组发送指令,触发外设上的 LED 状态切换。
该示例展示了最基础的“发现→连接→写入”流程,是所有 WebBluetooth 应用的起点。
3.3 API 封装与工具类设计模式
为了提升代码复用性与可测试性,应对复杂 BLE 通信逻辑,需对原始 Web Bluetooth API 进行面向对象封装。
3.3.1 蓝牙设备抽象类的设计思路
// src/bluetooth/DeviceManager.js
class BluetoothDeviceManager {
constructor(serviceUuids) {
this.serviceUuids = serviceUuids;
this.device = null;
this.server = null;
this.services = new Map();
}
async connect() {
this.device = await navigator.bluetooth.requestDevice({
filters: [{ services: this.serviceUuids }]
});
this.server = await this.device.gatt.connect();
for (const uuid of this.serviceUuids) {
const service = await this.server.getPrimaryService(uuid);
this.services.set(uuid, service);
}
return this;
}
async getCharacteristic(serviceUuid, characteristicUuid) {
const service = this.services.get(serviceUuid);
if (!service) throw new Error(`Service ${serviceUuid} not found`);
return await service.getCharacteristic(characteristicUuid);
}
async disconnect() {
if (this.device?.gatt.connected) {
this.device.gatt.disconnect();
}
}
}
export default BluetoothDeviceManager;
设计优势分析 :
- 构造函数接收服务 UUID 列表,实现通用性;
-connect()返回this支持链式调用;
- 内部缓存服务实例避免重复查询;
- 提供统一的断开接口。
3.3.2 事件驱动模型在蓝牙通信中的应用
// 使用 EventEmitter 模拟状态通知
import { EventEmitter } from 'events';
class SensorDevice extends BluetoothDeviceManager {
constructor() {
super(['environment_sensing']);
this.emitter = new EventEmitter();
}
async startMonitoring() {
const char = await this.getCharacteristic('environment_sensing', 'temperature');
await char.startNotifications();
char.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const temp = value.getFloat32(0, true); // 小端浮点
this.emitter.emit('temperatureUpdate', temp);
});
}
}
// 订阅温度变化
const sensor = new SensorDevice();
sensor.connect().then(() => sensor.startMonitoring());
sensor.emitter.on('temperatureUpdate', (temp) => {
console.log(`Current Temp: ${temp.toFixed(2)}°C`);
});
事件流说明 :
-startNotifications()开启特征值变更通知;
-addEventListener绑定回调函数;
- 数据通过EventEmitter解耦,UI 层可自由订阅而不依赖具体通信细节。
3.4 版本控制与贡献流程
3.4.1 Git 分支策略与 Pull Request 规范
采用 Gitflow 工作流:
- main :生产就绪代码
- develop :集成分支
- feature/* :功能开发分支
- hotfix/* :紧急修复分支
PR 提交要求:
- 必须关联 Issue 编号
- 提供截图或视频证明功能正常
- 更新相关文档
3.4.2 单元测试与 CI/CD 集成说明
使用 GitHub Actions 自动执行测试:
# .github/workflows/test.yml
name: Run Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test
确保每次提交都经过自动化质量检查,保障主干稳定性。
4. JavaScript 实现蓝牙数据交互
随着 Web Bluetooth API 在现代浏览器中的逐步普及,开发者不再依赖原生应用即可实现对 BLE 设备的直接控制。本章将深入探讨如何通过 JavaScript 实现与低功耗蓝牙设备的数据交互,涵盖从设备发现到 GATT 操作、数据解析、实时通信及异常处理等关键环节。我们将结合实际代码示例和底层原理分析,构建一个完整的 Web 端蓝牙通信模型,帮助读者掌握在真实项目中高效、稳定地集成 Web Bluetooth 技术的能力。
Web 平台上的蓝牙通信本质上是基于事件驱动和异步编程模型的,其核心在于合理组织 Promise 链、正确处理 ArrayBuffer 数据流,并对连接状态进行精细化监控。尤其在工业级或医疗类应用中,数据精度、通信可靠性和用户体验至关重要。因此,理解每一个 API 调用背后的行为逻辑,以及如何在不同网络环境与硬件条件下保持稳定性,是开发高质量 Web 蓝牙应用的前提。
4.1 浏览器中调用 Web Bluetooth API 的编程模型
Web Bluetooth API 提供了一套标准化的 JavaScript 接口,允许网页请求访问附近的 BLE 外设并与其进行双向通信。整个流程遵循“发现 → 连接 → 发现服务 → 操作特征值”的典型模式。该过程完全运行在安全上下文(HTTPS 或 localhost)中,确保用户隐私不被滥用。
4.1.1 requestDevice() 方法的过滤条件设置
navigator.bluetooth.requestDevice() 是启动设备选择对话框的核心方法。它接受一个配置对象作为参数,用于限定可选设备的范围,从而避免用户面对大量无关设备。
const device = await navigator.bluetooth.requestDevice({
filters: [
{ services: ['heart_rate'] },
{ namePrefix: 'SmartSensor-' }
],
optionalServices: ['battery_service', 'device_information']
});
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
filters | Array\<Object> | 必需条件,仅列出满足指定条件的设备 |
services | Array\<UUID> | 设备必须广播此服务 UUID |
name / namePrefix | String | 匹配完整名称或前缀 |
optionalServices | Array\<UUID> | 即使未广播也允许后续访问的服务 |
⚠️ 注意:若使用
acceptAllDevices: true替代filters,则无需预先知道服务类型,但必须声明optionalServices才能在连接后访问非广播服务。
逻辑分析:
- 第1行 :调用
requestDevice()启动系统级设备选择器。 - 第2–4行 :定义两个过滤规则——设备需支持心率服务或名称以
SmartSensor-开头。 - 第5行 :声明额外需要访问的服务(如电池信息),这些服务即使不在广播包中也可通过 GATT 发现。
该设计提升了用户体验的同时增强了安全性——只有明确授权的设备才能被接入。
sequenceDiagram
participant User
participant Browser
participant OS_BLE_Manager
participant BLE_Device
User->>Browser: 点击“连接设备”
Browser->>OS_BLE_Manager: requestDevice(filters)
OS_BLE_Manager->>BLE_Device: 扫描匹配设备
BLE_Device-->>OS_BLE_Manager: 返回广播数据
OS_BLE_Manager-->>Browser: 显示候选设备列表
Browser-->>User: 弹出选择界面
User->>Browser: 选择目标设备
Browser->>OS_BLE_Manager: 请求建立连接
OS_BLE_Manager-->>Browser: 返回 BluetoothDevice 实例
上述流程图展示了从用户操作到获得设备实例的完整时序。值得注意的是,操作系统负责执行底层扫描,浏览器仅作为中介协调权限与展示逻辑。
4.1.2 获取 GATT 服务器(getPrimaryService(s))与特征操作
一旦获取了 BluetoothDevice 实例,下一步是连接其 GATT 服务器并访问服务与特征值。
// 连接到GATT服务器
const server = await device.gatt.connect();
console.log('GATT Server connected');
// 获取主服务(单一服务)
const service = await server.getPrimaryService('heart_rate');
// 获取多个服务(批量获取)
const [hrService, batService] = await Promise.all([
server.getPrimaryService('heart_rate'),
server.getPrimaryService('battery_service')
]);
// 获取特定特征值
const characteristic = await hrService.getCharacteristic('heart_rate_measurement');
逐行解读:
- 第2行 :调用
.gatt.connect()建立与远程设备的 GATT 连接,返回BluetoothRemoteGATTServer对象。 - 第5行 :通过标准 UUID 获取心率服务,该服务必须已在广播或
optionalServices中声明。 - 第9–12行 :并发获取多个服务,提升初始化效率。
- 第15行 :进一步获取该服务下的“心率测量”特征值,准备读取或监听。
特征操作方式对比表:
| 操作类型 | 方法 | 是否需要通知启用 | 典型用途 |
|---|---|---|---|
| 读取当前值 | readValue() | 否 | 获取静态属性(如设备名) |
| 写入指令 | writeValue() | 否 | 控制命令发送(开灯/关灯) |
| 实时监听 | startNotifications() + addEventListener('characteristicvaluechanged') | 是 | 生物信号流、传感器数据推送 |
此结构体现了 GATT 层次化资源访问的设计思想:设备 → 服务 → 特征 → 描述符,每一层都可通过异步方法逐级展开。
characteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const decoder = new DataView(value.buffer);
console.log('Received heart rate:', decoder.getUint8(1));
});
await characteristic.startNotifications();
以上代码注册了一个值变化事件处理器,在开启通知后,每当外设推送新数据,回调函数即被触发。这种响应式机制非常适合处理连续数据流。
4.2 特征值的读取、写入与通知订阅
BLE 通信的本质是围绕特征值(Characteristic)进行数据交换。本节重点讲解三种基本操作:读取、写入与通知,分别对应不同的应用场景和技术挑战。
4.2.1 readValue() 与 writeValue() 的 ArrayBuffer 处理
所有 BLE 数据均以二进制形式传输,表现为 ArrayBuffer 或 DataView 。JavaScript 需借助类型化数组对其进行解析。
// 示例:读取设备电量
async function readBatteryLevel(device) {
const server = await device.gatt.connect();
const service = await server.getPrimaryService('battery_service');
const characteristic = await service.getCharacteristic('battery_level');
const value = await characteristic.readValue(); // 返回ArrayBuffer
return value.getUint8(0); // 解析为无符号8位整数
}
参数说明:
-
readValue():发起 ATT_READ 请求,阻塞等待响应。 -
value.getUint8(0):从第一个字节提取电量百分比(0~100)。
反向操作如下:
// 示例:向LED控制特征写入开关命令
async function setLedState(device, on = true) {
const server = await device.gatt.connect();
const service = await server.getPrimaryService('led_service');
const characteristic = await service.getCharacteristic('led_control');
const command = new Uint8Array([on ? 1 : 0]);
await characteristic.writeValue(command);
}
关键点解析:
-
writeValue()默认采用 Write Without Response 模式(速度快但不可靠),适用于高频小数据。 - 若需确认送达,应使用
writeValueWithResponse(底层协议自动重试)。 - 输入必须是
ArrayBuffer,TypedArray或DataView,原始数字或字符串无法直接写入。
4.2.2 startNotifications() 实现实时数据监听
对于持续更新的数据(如心率、加速度计),轮询读取效率低下。更优方案是启用特征值通知(Notification)或指示(Indication),由设备主动推送。
async function listenToAcceleration(device) {
const server = await device.gatt.connect();
const service = await server.getPrimaryService('custom_motion_service');
const characteristic = await service.getCharacteristic('accel_data');
// 启用通知
await characteristic.startNotifications();
// 注册监听器
characteristic.addEventListener('characteristicvaluechanged', (e) => {
const data = e.target.value;
const view = new DataView(data.buffer);
const x = view.getInt16(0, true); // 小端序
const y = view.getInt16(2, true);
const z = view.getInt16(4, true);
console.log(`Accel: X=${x}, Y=${y}, Z=${z}`);
});
}
字段解释:
-
getInt16(offset, littleEndian): -
offset:起始字节位置(每轴占2字节) -
littleEndian=true:表示小端字节序(Intel 格式),多数 BLE 设备采用此格式
graph TD
A[Start Notifications] --> B{Device Sends Data}
B --> C[Fire 'characteristicvaluechanged' Event]
C --> D[Extract ArrayBuffer from Event]
D --> E[Parse with DataView]
E --> F[Update UI / Store Data]
该流程图清晰呈现了通知机制的事件流路径。相比 polling,这种方式显著降低延迟并节省带宽。
4.3 数据编码与类型转换实战
BLE 传输的数据均为原始字节流,如何准确还原为有意义的信息,取决于双方约定的数据格式。本节介绍常见数据类型的序列化策略与跨平台兼容性问题。
4.3.1 字符串、整数、浮点数在 DataView 中的序列化
假设我们要通过自定义 UART 服务发送一条包含温度和描述的消息:
function encodeMessage(tempCelsius, message) {
const encoder = new TextEncoder();
const textBytes = encoder.encode(message); // UTF-8 编码字符串
// 总长度:2(byte for temp) + 1(len) + n(text)
const buffer = new ArrayBuffer(3 + textBytes.length);
const view = new DataView(buffer);
view.setInt16(0, tempCelsius * 10, true); // 存储带一位小数的温度
view.setUint8(2, textBytes.length); // 记录字符串长度
const uint8View = new Uint8Array(buffer, 3); // 从偏移3开始写文本
uint8View.set(textBytes);
return buffer;
}
// 使用示例
const payload = encodeMessage(23.6, "Room Temp");
await uartChar.writeValue(payload);
分析:
- 第6行 :使用
TextEncoder将字符串转为 UTF-8 字节数组,支持多语言。 - 第11行 :
setInt16(..., true)使用小端存储,符合大多数嵌入式系统习惯。 - 第13–14行 :利用
Uint8Array(buffer, byteOffset)实现零拷贝插入文本部分。
接收端需按相同规则逆向解析:
function decodeMessage(arrayBuffer) {
const view = new DataView(arrayBuffer);
const tempFixed = view.getInt16(0, true) / 10; // 还原浮点温度
const len = view.getUint8(2);
const textBytes = new Uint8Array(arrayBuffer, 3, len);
const decoder = new TextDecoder();
const text = decoder.decode(textBytes);
return { temperature: tempFixed, message: text };
}
此编解码机制广泛应用于自定义 BLE 协议栈中,具备高灵活性与低开销优势。
4.3.2 处理小端与大端字节序问题
字节序(Endianness)是指多字节数据在内存中的排列顺序。x86 架构通常为小端(Little-endian),而某些 MCU 可能使用大端(Big-endian)。错误识别会导致数值错乱。
例如,十六进制 0x1234 在两种模式下存储如下:
| 地址偏移 | 小端(LE) | 大端(BE) |
|---|---|---|
| 0 | 0x34 | 0x12 |
| 1 | 0x12 | 0x34 |
JavaScript 的 DataView 提供显式控制:
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
// 写入 float 3.14159,小端模式
view.setFloat32(0, Math.PI, true);
// 读取时也需指定相同字节序
const piRead = view.getFloat32(0, true); // 必须一致!
🔍 建议:除非设备文档明确说明,否则默认使用
littleEndian=true。可在调试阶段使用 nRF Connect 查看原始字节验证格式。
4.4 异常处理与连接状态监控
Web Bluetooth 操作极易受环境干扰(信号弱、设备断电、权限拒绝等),健全的错误处理机制是保障用户体验的关键。
4.4.1 catch 错误捕获与用户友好的提示机制
所有异步方法均可能抛出异常,必须包裹在 try-catch 中:
async function safeConnectToDevice() {
try {
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['health_thermometer'] }]
});
const server = await device.gatt.connect();
console.log('Connected successfully!');
return server;
} catch (error) {
switch(error.name) {
case 'NotFoundError':
alert('未找到匹配设备,请检查是否开启并靠近。');
break;
case 'SecurityError':
alert('页面未在安全上下文中运行(需要 HTTPS 或 localhost)。');
break;
case 'NotAllowedError':
alert('用户拒绝了设备访问权限。');
break;
default:
console.error('Unexpected error:', error);
alert(`连接失败:${error.message}`);
}
}
}
常见错误类型对照表:
| 错误名 | 触发场景 | 应对策略 |
|---|---|---|
NotFoundError | 无匹配设备 | 提示重新扫描或检查设备电源 |
NotAllowedError | 用户取消选择 | 提供引导按钮再次尝试 |
NetworkError | GATT 连接超时 | 显示“连接中断”并建议重试 |
SecurityError | 非 HTTPS 环境 | 自动跳转至安全链接 |
良好的错误分类有助于精准反馈问题根源。
4.4.2 监听 disconnect 事件并实现自动重连逻辑
BLE 连接不稳定是常态,尤其是移动设备间距离变化频繁时。通过监听 gattserverdisconnected 事件可及时响应中断。
let reconnectAttempts = 0;
const MAX_RETRIES = 5;
function setupDisconnectHandler(device) {
device.addEventListener('gattserverdisconnected', async () => {
if (reconnectAttempts < MAX_RETRIES) {
console.warn(`Connection lost. Attempt ${++reconnectAttempts}...`);
setTimeout(async () => {
try {
await device.gatt.connect();
console.log('Reconnected!');
reconnectAttempts = 0;
} catch (err) {
console.error('Reconnection failed:', err);
setupDisconnectHandler(device); // 继续监听下次断开
}
}, 3000 * reconnectAttempts); // 指数退避
} else {
alert('多次重连失败,请手动重新连接设备。');
}
});
}
策略说明:
- 使用指数退避(Exponential Backoff)防止密集重试加剧负载。
- 每次成功重连后重置计数器,避免永久锁定。
- 可结合 UI 显示“正在重连…”动画提升感知体验。
此外,还可扩展为后台任务队列机制:当连接恢复时,自动重发未完成的写入请求,保证业务连续性。
stateDiagram-v2
[*] --> Connected
Connected --> Disconnected: gattserverdisconnected
Disconnected --> Reconnecting: 启动定时重连
Reconnecting --> Connected: connect() success
Reconnecting --> Failed: attempts > MAX_RETRIES
Failed --> [*]: 用户干预
该状态机清晰表达了连接生命周期的变迁逻辑,可用于指导复杂应用的状态管理设计。
综上所述,JavaScript 实现蓝牙数据交互不仅是 API 的简单调用,更是对异步编程、二进制处理、错误容错和用户体验设计的综合考验。掌握这些核心技术,方能在 Web 平台上构建出稳定、高效的 BLE 应用系统。
5. 基于Web的BLE设备控制实战
5.1 HTML 构建蓝牙应用用户界面
在构建 Web 蓝牙应用时,前端用户界面(UI)是与用户交互的核心。一个设计良好的 UI 不仅能提升用户体验,还能清晰展示 BLE 设备的状态和数据流。本节将详细介绍如何使用标准 HTML 与 CSS 构建一个功能完整的 Web 蓝牙控制页面。
首先,定义基本的页面结构。我们需要包含设备扫描按钮、设备列表显示区、实时数据显示区域以及控制输入组件:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Web BLE 温湿度监控</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
button { padding: 10px 15px; margin: 5px; font-size: 16px; }
ul { list-style-type: none; padding: 0; }
li { padding: 8px; border-bottom: 1px solid #eee; }
.data-display {
margin-top: 20px;
padding: 15px;
background-color: #f4f4f4;
border-radius: 5px;
}
canvas { width: 100%; max-width: 600px; margin-top: 20px; }
</style>
</head>
<body>
<h1>智能传感器 Web 控制面板</h1>
<!-- 扫描与连接 -->
<button id="scanBtn">扫描 BLE 设备</button>
<ul id="deviceList"></ul>
<!-- 实时数据显示 -->
<div class="data-display">
<h3>实时传感器数据</h3>
<p>温度: <span id="temperature">--</span> °C</p>
<p>湿度: <span id="humidity">--</span> %</p>
</div>
<!-- 图表容器 -->
<canvas id="chart"></canvas>
<script src="app.js"></script>
</body>
</html>
上述代码中:
- #scanBtn 触发 requestDevice() 请求;
- #deviceList 动态渲染可连接的 BLE 设备名称;
- #temperature 和 #humidity 用于展示从 BLE 特征值读取的数据;
- <canvas> 将用于集成 Chart.js 绘制动态趋势图。
接下来,在 JavaScript 中绑定事件并更新 DOM:
document.getElementById('scanBtn').addEventListener('click', async () => {
try {
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['environmental_sensing'] }],
optionalServices: ['battery_service']
});
const listItem = document.createElement('li');
listItem.textContent = `${device.name} (RSSI: ${device.rssi || 'N/A'})`;
document.getElementById('deviceList').appendChild(listItem);
// 连接 GATT 并开始通信...
const server = await device.gatt.connect();
console.log('GATT 服务器已连接:', server);
} catch (error) {
console.error('设备选择失败:', error);
alert(`错误: ${error.message}`);
}
});
参数说明:
- filters : 指定服务 UUID 来筛选目标设备(如 environmental_sensing 对应环境传感器);
- optionalServices : 声明需访问但非必需的服务,避免权限不足;
- device.rssi : 表示信号强度,可用于粗略判断距离。
该 UI 设计具备良好的扩展性,后续可加入自动重连提示、连接状态指示灯(LED 样式)、数据导出按钮等高级功能。
5.2 BLE UART 协议解析与应用集成
许多嵌入式开发板(如 Nordic nRF52、ESP32)支持通过 BLE 实现串行通信,即 UART over BLE ,通常称为“BLE Serial”或“NUS”(Nordic UART Service)。这一模式允许网页像使用串口一样发送和接收文本消息。
5.2.1 UART over BLE 的服务 UUID 定义
Nordic 定义的标准 NUS 服务 UUID 如下:
| 类型 | UUID |
|---|---|
| Service UUID | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E |
| TX Characteristic (接收端写入) | 6E400002-B5A3-F393-E0A9-E50E24DCCA9E |
| RX Characteristic (通知启用) | 6E400003-B5A3-F393-E0A9-E50E24DCCA9E |
其中:
- TX : 网页向设备写入数据(Write Without Response)
- RX : 设备向网页推送数据(Notify)
5.2.2 发送与接收文本消息的完整链路实现
以下为完整通信逻辑:
async function connectUARTDevice() {
const serviceUUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
const txCharUUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';
const rxCharUUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e';
const device = await navigator.bluetooth.requestDevice({
filters: [{ namePrefix: 'NUS' }],
optionalServices: [serviceUUID]
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService(serviceUUID);
// 获取 TX 特征(用于发送)
const txCharacteristic = await service.getCharacteristic(txCharUUID);
// 获取 RX 特征并开启通知
const rxCharacteristic = await service.getCharacteristic(rxCharUUID);
await rxCharacteristic.startNotifications();
rxCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const decoder = new TextDecoder();
const text = decoder.decode(value);
console.log('收到:', text);
document.getElementById('receivedData').textContent += text;
});
// 示例:发送命令
const encoder = new TextEncoder();
const data = encoder.encode('GET_TEMP\n');
await txCharacteristic.writeValue(data);
}
执行流程分析:
1. 用户点击按钮调用 connectUARTDevice() ;
2. 浏览器弹出设备选择器,匹配前缀为 “NUS” 的设备;
3. 成功连接后获取 GATT 服务;
4. 配置 RX 特征监听变化事件;
5. 使用 TX 特征发送字符串指令。
此模型广泛应用于调试、远程配置和简易人机交互场景。
5.3 完整项目:网页端控制智能温湿度传感器
我们以 Arduino + BME280 + BLE 模块为例,构建一个完整的 Web 端传感器监控系统。
5.3.1 使用 DHT11/BME280 传感器并通过 Arduino 发送 BLE 数据
硬件连接:
- BME280 → I²C 接口(SDA/SCL)
- BLE 模块(如 HM-10 或 nRF8001)→ Serial/TTL
Arduino 代码片段(使用 Adafruit_BME280 库):
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <CurieBLE.h>
Adafruit_BME280 bme;
BLEPeripheral blePeripheral;
BLEService environmentalService("181A");
BLEFloatCharacteristic tempChar("2A6E", BLERead | BLENotify);
BLEFloatCharacteristic humidChar("2A6F", BLERead | BLENotify);
void setup() {
Serial.begin(9600);
if (!bme.begin(0x76)) {
while (1);
}
blePeripheral.setLocalName("WebSensor");
blePeripheral.setAdvertisedServiceUuid(environmentalService.uuid());
blePeripheral.addAttribute(environmentalService);
blePeripheral.addAttribute(tempChar);
blePeripheral.addAttribute(humidChar);
blePeripheral.begin();
}
void loop() {
float t = bme.readTemperature();
float h = bme.readHumidity();
tempChar.setValue(t);
humidChar.setValue(h);
blePeripheral.poll();
delay(2000);
}
该设备广播 GATT 服务 181A (Environmental Sensing),并暴露两个浮点型特征值用于传输温度与湿度。
5.3.2 网页端获取数据并绘制动态图表(Chart.js 集成)
引入 Chart.js:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
初始化折线图:
const ctx = document.getElementById('chart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [], // 时间戳
datasets: [
{
label: '温度 (°C)',
borderColor: 'rgb(255, 99, 132)',
data: []
},
{
label: '湿度 (%)',
borderColor: 'rgb(54, 162, 235)',
data: []
}
]
}
});
// 更新图表函数
function updateChart(temp, humi) {
const time = new Date().toLocaleTimeString();
chart.data.labels.push(time);
chart.data.datasets[0].data.push(temp);
chart.data.datasets[1].data.push(humi);
// 限制数据点数量
if (chart.data.labels.length > 10) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
chart.data.datasets[1].data.shift();
}
chart.update();
}
结合 BLE 监听逻辑:
await tempChar.startNotifications();
tempChar.addEventListener('characteristicvaluechanged', (e) => {
const temp = e.target.value.getFloat32(0, true); // 小端序
document.getElementById('temperature').textContent = temp.toFixed(2);
updateChart(temp, parseFloat(document.getElementById('humidity').textContent));
});
5.4 Web 蓝牙应用调试与测试方法
5.4.1 Chrome DevTools 中的 Bluetooth 面板使用技巧
Chrome 提供内置调试工具:
- 打开 DevTools → More Tools → Bluetooth
- 可查看当前已连接的 BLE 设备
- 查看 GATT 层结构(Services / Characteristics)
- 手动触发 read/write 操作进行验证
建议开启日志记录:
console.debug('Connection state:', device.gatt.connected);
5.4.2 利用 nRF Connect 对比验证通信行为
nRF Connect(Android/iOS)是一款强大的 BLE 调试工具。可用于:
- 验证设备是否正常广播;
- 检查服务 UUID 与特征值属性是否正确;
- 模拟通知发送,确认网页能否正确接收;
- 分析 MTU 大小与传输性能。
对比测试步骤:
1. 在网页中连接设备;
2. 使用 nRF Connect 同时连接同一设备;
3. 比较特征值数值一致性;
4. 若不一致,检查字节序或编码方式。
5.4.3 跨浏览器兼容性测试与安全权限异常排查
目前仅 Chromium 内核浏览器(Chrome、Edge、Opera)完全支持 Web Bluetooth API。
常见错误及解决方案:
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
SecurityError: Access to the feature is disallowed by user permission setting. | 用户未授权 | 确保 HTTPS、点击触发、提供说明文案 |
NotFoundError: No device selected. | 过滤条件无匹配 | 放宽 filters,添加 name , manufacturerData 条件 |
NetworkError: GATT operation failed for unknown reason. | 设备断开或忙 | 添加重连机制、延迟重试 |
DOMException: Characteristic value length is invalid. | ArrayBuffer 长度不符 | 检查 writeValue() 输入长度限制 |
推荐处理策略:
- 强制 HTTPS 部署;
- 所有 BLE 操作由用户手势(click)触发;
- 使用 try/catch 包裹异步操作;
- 显示友好的错误提示而非原始堆栈。
sequenceDiagram
participant User
participant WebPage
participant BluetoothStack
participant BLEDevice
User->>WebPage: 点击“扫描”
WebPage->>BluetoothStack: requestDevice()
BluetoothStack->>User: 显示设备选择器
User->>BluetoothStack: 选择设备
BluetoothStack->>BLEDevice: 发起连接
BLEDevice-->>BluetoothStack: 返回服务列表
BluetoothStack-->>WebPage: 返回 GATT Server
WebPage->>BLEDevice: getPrimaryService + startNotifications
loop 每2秒
BLEDevice->>WebPage: 推送温度/湿度数据
WebPage->>WebPage: 更新DOM与图表
end
简介:Web Bluetooth API是一项新兴的Web技术,允许网页应用直接与蓝牙低功耗(BLE)设备进行安全通信,无需依赖本地应用程序。该存储库“WebBluetoothAPI-main”集中存放了与Web Bluetooth API相关的代码、示例程序和开发工具,重点支持基于BLE UART协议的设备交互,适用于健康监测、智能家居和工业传感等场景。项目包含HTML用户界面与JavaScript核心逻辑,提供设备发现、连接、数据读写等完整流程的实现,帮助开发者快速构建可在浏览器中运行的蓝牙控制应用。
1055

被折叠的 条评论
为什么被折叠?



