界面如下 :
第一节,蓝牙连接按键学习
html
<body>
<div class="main">
<h3>电子墨水屏蓝牙控制器</h3>
<fieldset>
<legend>蓝牙连接</legend>
<div class="flex-container">
<div class="flex-group">
<button id="connectbutton" type="button" class="primary" onclick="preConnect()">连接</button>
<button id="reconnectbutton" type="button" class="secondary" onclick="reConnect()">重连</button>
<button type="button" class="secondary" onclick="clearLog()">清空日志</button>
</div>
preConnect()函数定义如下 :该函数定义在main.js中
javascript
async function preConnect() {
if (gattServer != null && gattServer.connected) {
if (bleDevice != null && bleDevice.gatt.connected) {
bleDevice.gatt.disconnect();
}
}
else {
resetVariables();
try {
bleDevice = await navigator.bluetooth.requestDevice({
optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'],
acceptAllDevices: true
});
} catch (e) {
console.error(e);
if (e.message) addLog("requestDevice: " + e.message);
addLog("请检查蓝牙是否已开启,且使用的浏览器支持蓝牙!建议使用以下浏览器:");
addLog("• 电脑: Chrome/Edge");
addLog("• Android: Chrome/Edge");
addLog("• iOS: Bluefy 浏览器");
return;
}
await bleDevice.addEventListener('gattserverdisconnected', disconnect);
setTimeout(async function () { await connect(); }, 300);
}
}
这段代码是一个使用 Web Bluetooth API 的异步函数,用于处理蓝牙设备的预连接流程。下面我将逐行解释其功能和逻辑:
async function preConnect() {
// 检查当前是否已有连接的 GATT 服务器
if (gattServer != null && gattServer.connected) {
// 检查 BLE 设备是否已连接
if (bleDevice != null && bleDevice.gatt.connected) {
// 如果已连接,则断开当前连接
bleDevice.gatt.disconnect();
}
}
else {
// 如果没有活动连接,则重置相关变量
resetVariables();
try {
// 请求用户选择蓝牙设备
bleDevice = await navigator.bluetooth.requestDevice({
// 指定需要访问的服务 UUID(这里是一个自定义服务)
optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'],
// 允许选择所有设备(无论是否包含指定服务)
acceptAllDevices: true
});
} catch (e) {
// 处理设备选择过程中的错误
console.error(e);
if (e.message) addLog("requestDevice: " + e.message);
// 提示用户检查蓝牙和浏览器兼容性
addLog("请检查蓝牙是否已开启,且使用的浏览器支持蓝牙!建议使用以下浏览器:");
addLog("• 电脑: Chrome/Edge");
addLog("• Android: Chrome/Edge");
addLog("• iOS: Bluefy 浏览器");
return; // 发生错误时退出函数
}
// 添加事件监听器,当 GATT 服务器断开连接时调用 disconnect() 函数
await bleDevice.addEventListener('gattserverdisconnected', disconnect);
// 延迟 300ms 后调用 connect() 函数建立实际连接
setTimeout(async function () { await connect(); }, 300);
}
}
核心功能与设计逻辑
-
连接管理
- 函数首先检查是否存在已连接的 GATT 服务器,如果是则断开当前连接
- 这种设计确保在建立新连接前清理旧连接,避免资源冲突
-
设备选择流程
- 使用
navigator.bluetooth.requestDevice()
触发浏览器的设备选择对话框 optionalServices
参数指定需要访问的蓝牙服务 UUID(用于 OTA 升级的服务)acceptAllDevices: true
允许用户选择任何蓝牙设备,无论是否包含指定服务
- 使用
-
错误处理机制
- 捕获设备选择过程中的异常(如用户取消选择、蓝牙未开启等)
- 提供详细的错误提示,包括浏览器兼容性建议(Web Bluetooth 在不同浏览器支持差异较大)
-
事件监听与异步连接
- 添加
gattserverdisconnected
事件监听器,确保设备断开时能及时响应 - 使用
setTimeout
延迟调用connect()
函数,为设备准备提供缓冲时间
- 添加
与 OTA 升级的关联
此函数是蓝牙 OTA 升级流程的前期准备阶段,主要完成:
- 设备发现与选择
- 基础连接建立
- 连接状态管理
后续的 OTA 数据传输通常会在 connect()
函数中实现,包括:
- 连接到指定服务和特征
- 发送固件数据
- 接收设备状态反馈
- 验证升级结果
潜在优化建议
-
增加连接超时处理
// 在 setTimeout 中添加超时逻辑 const connectTimeout = setTimeout(() => { addLog("连接超时,请重试"); }, 5000); // 5秒超时 await connect(); clearTimeout(connectTimeout); // 连接成功后清除超时
-
优化错误提示信息
- 根据不同错误类型提供更具体的解决方案
- 例如区分 "用户取消" 和 "蓝牙未开启" 等不同错误场景
-
增强连接稳定性
- 添加重连机制(如自动重试 2-3 次)
- 实现连接状态监控(定期检查连接状态)
这个预连接函数为后续的 OTA 升级操作奠定了基础,确保设备连接的可靠性和稳定性。
GATT 服务器是什么?蓝牙通信的核心概念解析
一、GATT 基础定义
GATT(Generic Attribute Profile) 是蓝牙低功耗(BLE)技术中的核心通信协议,定义了设备间如何组织和交换数据。GATT 服务器是该协议中的核心角色,负责:
- 存储数据(以特征 和服务的形式组织)
- 响应客户端的数据请求
- 主动向客户端发送数据更新
二、GATT 服务器的核心组件
GATT 服务器的结构采用分层模型,从抽象到具体依次为:
-
服务(Service)
- 服务是相关功能的集合,例如 "电池信息服务"、"心率监测服务" 或 "OTA 升级服务"。
- 每个服务有唯一的 UUID(Universally Unique Identifier),例如:
- 电池服务 UUID:
0x180F
(标准服务) - OTA 服务 UUID:
62750001-d828-918d-fb46-b6c11c675aec
(自定义服务)
- 电池服务 UUID:
-
特征(Characteristic)
- 特征是服务中的具体数据项,例如 "电池电量百分比"、"心率值"。
- 每个特征包含:
- 值(Value):实际数据(如电池电量 85%)。
- 属性(Properties):定义如何访问该特征(如可读、可写、可通知)。
- 描述符(Descriptor):提供关于特征的额外元数据(如单位、范围)。
-
描述符(Descriptor)
- 描述符是特征的元数据,例如 "测量单位"、"数据格式"。
- 示例:心率特征的描述符可能标明 "单位:次 / 分钟"。
三、GATT 客户端与服务器的交互模式
在蓝牙通信中,客户端(Client) 和服务器(Server) 角色通常由设备功能决定:
- 服务器 :通常是被连接的设备(如智能手表、耳机),持有数据。
- 客户端 :通常是发起连接的设备(如手机、电脑),请求或接收数据。
典型交互流程:
- 连接建立:客户端通过 BLE 物理链路连接到服务器。
- 服务发现:客户端请求服务器列出所有可用服务和特征。
- 数据交换 :客户端通过以下方式与服务器交互:
- 读取(Read):获取特征的当前值(如读取电池电量)。
- 写入(Write):修改特征的值(如设置设备名称)。
- 通知 / 指示(Notify/Indicate):服务器主动向客户端发送数据更新(如心率变化)。
四、在 OTA 升级中的应用
在蓝牙 OTA 升级场景中,GATT 服务器的角色尤为关键:
-
OTA 服务设计
- 设备(如耳机)作为 GATT 服务器,暴露一个自定义 OTA 服务。
- 该服务包含多个特征,例如:
- 固件接收特征:客户端(手机)写入固件数据。
- 状态反馈特征:服务器发送升级进度(如 "已接收 50%")。
- 控制命令特征:用于启动 / 暂停 / 确认升级。
-
数据传输流程
- 客户端将固件包拆分为小片段(通常 ≤256 字节)。
- 客户端通过写入请求将片段发送到服务器的固件接收特征。
- 服务器接收数据,验证完整性,并通过状态反馈特征返回进度。
-
安全机制
- GATT 层支持加密和认证,确保固件传输过程不被篡改。
- 服务器可能要求客户端提供认证密钥或数字签名。
五、GATT 服务器的实现示例
以下是一个简化的伪代码示例,展示如何在设备端实现一个 OTA 服务的 GATT 服务器:
// 伪代码:设备端实现 OTA 服务的 GATT 服务器
async function setupOTAService() {
// 创建 GATT 服务器
const server = await navigator.bluetooth.requestLEScan();
// 创建 OTA 服务
const otaService = await server.getPrimaryService('62750001-d828-918d-fb46-b6c11c675aec');
// 创建固件接收特征(可写)
const firmwareCharacteristic = await otaService.createCharacteristic(
'62750002-d828-918d-fb46-b6c11c675aec',
{ properties: { write: true } }
);
// 创建状态反馈特征(可通知)
const statusCharacteristic = await otaService.createCharacteristic(
'62750003-d828-918d-fb46-b6c11c675aec',
{ properties: { notify: true } }
);
// 监听固件数据写入
firmwareCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
const firmwareData = event.target.value;
// 处理接收到的固件数据片段
processFirmwareChunk(firmwareData);
// 更新并发送升级进度
const progress = calculateProgress();
statusCharacteristic.setValue(progress);
});
return server;
}
六、总结
GATT 服务器是蓝牙设备通信的核心组件,通过服务、特征和描述符的层次结构组织数据。在 OTA 升级中,GATT 服务器负责接收固件数据、执行升级过程并反馈状态,确保整个过程的可靠性和安全性。理解 GATT 服务器的工作原理对于开发蓝牙应用和实现 OTA 功能至关重要。