HarmonyOS NEXT 鸿蒙应用低功耗蓝牙BLE使用心得
前言在鸿蒙生态“18N”的万物互联蓝图中BLEBluetooth Low Energy低功耗蓝牙是连接海量IoT设备的关键短距通信技术。从智能手表同步运动数据到健康手环上传生理指标再到智能家居传感器上报环境状态BLE以其出色的节能特性和广泛的适用性成为低功耗设备互联的首选方案。本文将基于HarmonyOS NEXT的ohos.bluetooth.ble模块从开发者的实战视角系统梳理BLE应用开发的全流程。内容涵盖权限配置、蓝牙状态管理、设备扫描与过滤、GATT连接与服务发现、特征值读写与订阅通知、数据分包与队列发送、广播与扫描的功耗优化以及服务端GATT Server的实现要点。一、 权限声明与基础准备在HarmonyOS NEXT中使用BLE功能首先需要在module.json5中声明相关权限。根据功能需求需要申请不同级别的权限json{ module: { requestPermissions: [ { name: ohos.permission.USE_BLUETOOTH, reason: $string:bluetooth_reason, usedScene: { abilities: [EntryAbility] } }, { name: ohos.permission.DISCOVER_BLUETOOTH, reason: $string:bluetooth_reason, usedScene: { abilities: [EntryAbility] } }, { name: ohos.permission.APPROXIMATELY_LOCATION, reason: $string:location_reason, usedScene: { abilities: [EntryAbility] } } ] } }说明USE_BLUETOOTH查看蓝牙配置的权限。DISCOVER_BLUETOOTH发现远端设备并进行配对连接的权限。APPROXIMATELY_LOCATION进行BLE扫描通常需要位置权限Android及鸿蒙系统均要求。MANAGE_BLUETOOTH为system_basic级别如需系统级蓝牙管理如强制配对需配置对应签名或使用ACL使能。二、 蓝牙状态管理执行任何BLE操作前应先检查设备蓝牙是否开启并引导用户开启。typescriptimport { access } from kit.ConnectivityKit; // 判断蓝牙是否开启 isBluetoothEnabled(): boolean { const state: access.BluetoothState access.getState(); return state access.BluetoothState.STATE_ON || state access.BluetoothState.STATE_TURNING_ON; } // 检查并引导开启蓝牙 checkAndTurnOnBluetooth() { if (!this.isBluetoothEnabled()) { // 调用系统接口使能蓝牙 access.enableBluetooth(); } }监听蓝牙状态变化通过access.on(stateChange)可以实时监听全局蓝牙开关状态以便在蓝牙关闭时及时停止扫描并提示用户。三、 BLE 设备扫描客户端作为中心设备Central扫描是发现周边外设Peripheral的第一步。1. 启动扫描扫描时需设置ScanOptions扫描参数和ScanFilter扫描过滤器。为降低功耗建议使用低功耗模式(SCAN_MODE_LOW_POWER)并合理设置过滤条件。typescriptimport { ble } from kit.ConnectivityKit; // 构造扫描过滤器根据业务需求例如过滤特定服务UUID或制造商ID let scanFilter: ble.ScanFilter { serviceUuid: 0000180D-0000-1000-8000-00805F9B34FB // 例如心率服务 }; // 构造扫描参数平衡功耗与效率 let scanOptions: ble.ScanOptions { interval: 0, // 扫描间隔 dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER, // 低功耗模式 matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE // 匹配模式 }; try { // 开始扫描 ble.startBLEScan([scanFilter], scanOptions); } catch (err) { console.error(Scan start error: ${JSON.stringify(err)}); }2. 订阅扫描结果调用startBLEScan后需订阅BLEDeviceFind事件来接收发现的设备信息。typescriptprivate onBLEDeviceFind() { ble.on(BLEDeviceFind, (data: Arrayble.ScanResult) { if (data.length 0) { const device data[0]; // 去重处理避免列表重复添加同一设备 if (!this.deviceList.some(item item.deviceId device.deviceId)) { // 注意deviceId 是临时性MAC地址可能会变化不建议用作持久化存储[citation:3] this.deviceList.push(device); } } }); }3. 停止扫描在页面销毁或连接成功后应及时停止扫描以节省电量。typescriptstopScan() { try { ble.off(BLEDeviceFind); // 取消订阅 ble.stopBLEScan(); // 停止扫描 } catch (err) {} }四、 GATT 连接与服务交互当扫描到目标设备后需要通过GATT通用属性协议建立连接并进行数据交互。1. 建立连接使用createGattClientDevice创建客户端实例调用connect()发起连接并订阅连接状态变化。typescriptlet gattClient: ble.GattClientDevice; connectDevice(deviceId: string) { // 创建 GattClient 实例所有后续操作都依赖此实例 gattClient ble.createGattClientDevice(deviceId); // 订阅连接状态变化 gattClient.on(BLEConnectionStateChange, (state: ble.BLEConnectionChangeState) { if (state.state constant.ProfileConnectionState.STATE_CONNECTED) { console.info(BLE Connected Successfully); // 连接成功后进行服务发现 this.discoverServices(); } else if (state.state constant.ProfileConnectionState.STATE_DISCONNECTED) { console.info(BLE Disconnected); // 清理资源重置状态 } }); // 发起连接 gattClient.connect(); }2. 发现服务与特征值连接成功后调用getServices()获取设备提供的所有服务从中找到目标服务Service和特征值Characteristic。typescriptasync discoverServices() { try { // 获取服务列表 let services await gattClient.getServices(); // 遍历寻找目标服务例如自定义服务UUID: 0x9011 const targetService services.find(s s.serviceUuid 00009011-...); if (!targetService) return; // 在服务中寻找目标特征值 // 写入特征Write Characteristic const writeChar targetService.characteristics.find( c c.characteristicUuid 00009012-... (c.properties ble.CharacteristicProperty.WRITE) ! 0 ); // 通知特征Notify Characteristic const notifyChar targetService.characteristics.find( c c.characteristicUuid 00009013-... (c.properties ble.CharacteristicProperty.NOTIFY) ! 0 ); this.writeCharacteristic writeChar; this.notifyCharacteristic notifyChar; // 订阅通知特征以接收设备主动推送的数据 if (this.notifyCharacteristic) { await this.enableNotification(this.notifyCharacteristic); } } catch (err) { console.error(Discover services error: ${JSON.stringify(err)}); } }3. 开启通知Notification要接收设备主动发送的数据必须对支持NOTIFY或INDICATE属性的特征值调用setCharacteristicChangeNotification并订阅BLECharacteristicChange事件。注意监听事件的注册可以在连接之前但必须调用了setCharacteristicChangeNotification数据回调才会被触发。typescript// 启用通知 async enableNotification(characteristic: ble.BLECharacteristic) { // 1. 调用设置通知请求必须 await gattClient.setCharacteristicChangeNotification(characteristic, true); // 2. 订阅特征值变化事件可以提前注册 gattClient.on(BLECharacteristicChange, (data: ble.BLECharacteristic) { // 解析收到的数据注意 data.characteristicValue 是 ArrayBuffer const value new Uint8Array(data.characteristicValue); console.info(Received data: ${this.arrayBufferToHex(value)}); // 根据业务处理数据... }); }4. 向设备写入数据通过writeCharacteristicValue向写入特征发送数据。这里有两点需要特别注意写入模式GattWriteType.WRITE需要设备回复确认可靠性高但速度慢和GattWriteType.WRITE_NO_RESPONSE无需回复速度快但可靠性稍低。根据业务场景选择。分包发送BLE 4.0/4.2单次传输数据长度通常限制在20字节左右实际受MTU影响。若数据较长需自行分包发送并注意同一时间只能有一个写入操作在进行。typescriptasync writeData(data: Uint8Array) { if (!this.writeCharacteristic) { console.error(Write characteristic not found); return; } // 构造写入数据 const writeChar: ble.BLECharacteristic { serviceUuid: this.writeCharacteristic.serviceUuid, characteristicUuid: this.writeCharacteristic.characteristicUuid, characteristicValue: data.buffer, // 必须为 ArrayBuffer descriptors: this.writeCharacteristic.descriptors, properties: this.writeCharacteristic.properties }; try { await gattClient.writeCharacteristicValue(writeChar, ble.GattWriteType.WRITE_NO_RESPONSE); console.info(Write data success); } catch (err) { console.error(Write data error: ${JSON.stringify(err)}); } }5. 数据分包与队列发送实战核心针对大数据传输如OTA固件升级“发送队列 分包机制”是保证稳定性的关键。typescript// 消息队列 let messageQueue: Array{ data: Uint8Array, char: ble.BLECharacteristic } []; let isSending: boolean false; // 将数据加入发送队列自动分包 function pushDataToQueue(data: Uint8Array, mtuSize: number, char: ble.BLECharacteristic) { // mtuSize 可通过监听 BLEMtuChange 事件获取 // 减去协议头长度如6字节预留空间 const chunkSize mtuSize - 6; for (let i 0; i data.length; i chunkSize) { const chunk data.slice(i, Math.min(i chunkSize, data.length)); messageQueue.push({ data: chunk, char: char }); } // 触发发送 processQueue(); } async function processQueue() { if (isSending || messageQueue.length 0) return; isSending true; try { const item messageQueue.shift(); if (item) { // 构造并发送 await writeDataInternal(item.data, item.char); } } catch (err) { console.error(Send failed, maybe retry logic?); } finally { isSending false; // 递归发送下一个包 processQueue(); } }五、 BLE 广播服务端如果应用需要作为外设Peripheral被其他中心设备发现则需实现BLE广播功能。1. 构建广播数据广播数据包括AdvertiseSetting广播参数和AdvertiseData广播内容。需要填写服务UUID、设备名称等信息。typescriptimport { ble } from kit.ConnectivityKit; function startAdvertising() { // 1. 设置广播参数 let setting: ble.AdvertiseSetting { interval: 160, // 广播间隔单位 0.625ms txPower: 0, // 发射功率 connectable: true // 是否可连接 }; // 2. 构造广播数据 let advData: ble.AdvertiseData { serviceUuids: [0000180D-0000-1000-8000-00805F9B34FB], includeDeviceName: true, manufactureData: [{ manufactureId: 0x1234, manufactureValue: new Uint8Array([0x01, 0x02]).buffer }] }; // 3. 启动广播 try { ble.startAdvertising(setting, advData, null); console.info(Advertising started); } catch (err) { console.error(Start advertising error: ${JSON.stringify(err)}); } }2. 作为 GATT Server若需提供数据读写服务需要创建GattServer、添加服务并在收到客户端读写请求时响应。六、 性能优化与功耗考量在智能手表、健康监测设备等低功耗场景下BLE的功耗控制至关重要。合理设置扫描参数在非必要实时发现的场景下使用SCAN_MODE_LOW_POWER模式并延长扫描间隔。及时释放资源连接成功后或页面退出时务必调用stopBLEScan()、gattClient.disconnect()以及关闭对应的on监听避免后台持续耗电。动态调整广播间隔当设备处于待机或无数据更新时适当增大广播间隔如从160ms调整到1000ms可显著降低功耗。MTU协商连接成功后协商更大的MTU如512字节虽然单次传输功耗可能略增但能减少分包次数和交互次数整体上优化传输效率和功耗。七、 踩坑经验与常见问题BLECharacteristicChange不触发确认已调用setCharacteristicChangeNotification并传入true。该调用与注册监听的先后顺序无关但必须存在。检查硬件端是否确实在该特征值上执行了Notify操作。deviceId不可持久化扫描到的deviceId是系统临时分配的设备重启或系统重置后会变化。若需要重连应使用deviceId的临时缓存或通过广播数据中的自定义字段识别设备。并行写入失败同一时间只能进行一个写入操作。必须使用队列Queue机制串行化写入请求否则会触发系统错误。连接后立即操作失败在BLEConnectionStateChange回调中收到STATE_CONNECTED后建议稍作延迟如200ms再调用getServices因为底层协议栈可能尚未完全准备好。数据解析错误收发数据时注意字节序大端/小端以及ArrayBuffer与Uint8Array的正确转换。结语HarmonyOS NEXT 为 BLE 开发提供了完备且清晰的 API 体系。理解并熟练掌握从权限管理、设备扫描发现、GATT 连接与服务交互到数据收发队列管理的完整流程是构建稳定、省电的鸿蒙 BLE 应用的基础。希望本文的实战心得能帮助开发者们在鸿蒙万物互联的生态中高效地搭建起设备间的通信桥梁。