HarmonyOS 6.0 蓝牙BLE设备通信实战——智能手环/蓝牙秤接入全流程
健康类 APP 连智能手环读心率、连蓝牙秤读体重、连血糖仪读数据——BLE(低功耗蓝牙)是这些 IoT 场景的通信基础。HarmonyOS NEXT 提供了完整的 BLE API:扫描发现、GATT 连接、服务发现、特征值读写、通知订阅。这篇从零到一讲清楚 BLE 通信全流程。
BLE 通信概览
BLE 通信核心概念:
- Peripheral(外设)——手环、体重秤等硬件设备,广播自己的存在
- Central(中心)——手机 APP,扫描并连接外设
- GATT——通用属性协议,定义数据组织方式(Service → Characteristic)
- Service——一组相关特征的集合(如"心率服务"0x180D)
- Characteristic——具体的数据项(如"心率测量"0x2A37),可读/写/通知
import{ble}from'@kit.ConnectivityKit'import{access}from'@kit.ConnectivityKit'权限配置
BLE 需要蓝牙权限,在 module.json5 中声明。
{ "requestPermissions": [ { "name": "ohos.permission.ACCESS_BLUETOOTH" } ] }注意:ACCESS_BLUETOOTH 是 user_grant 权限,需要动态申请。
BLE 扫描
扫描是 BLE 通信的第一步——发现周围的蓝牙设备。
interfaceBleDevice{id:stringname:stringrssi:numberisConnected:boolean}@Entry@Componentstruct BleScanDemo{@StatedeviceList:BleDevice[]=[]@StateisScanning:boolean=false@StatestatusMsg:string=''build(){Column({space:16}){Text('BLE 扫描').fontSize(22).fontWeight(FontWeight.Bold).width('100%')Row({space:8}){Button(this.isScanning?'扫描中...':'开始扫描').enabled(!this.isScanning).onClick(()=>this.startScan())Button('停止扫描').enabled(this.isScanning).onClick(()=>this.stopScan())}ForEach(this.deviceList,(device:BleDevice)=>{Row({space:12}){Column({space:2}){Text(device.name||'未知设备').fontSize(15).fontWeight(FontWeight.Medium)Text(device.id).fontSize(11).fontColor('#999999')}.layoutWeight(1)Text(`RSSI:${device.rssi}`).fontSize(12).fontColor(device.rssi>-50?'#4CAF50':'#FF9800')}.width('100%').padding(12).borderRadius(8).backgroundColor('#FAFAFA')},(device:BleDevice)=>device.id)Text(this.statusMsg).fontSize(13).fontColor('#999999')}.width('100%').padding(20)}privatestartScan():void{this.isScanning=truethis.statusMsg='扫描中...(需真机+蓝牙权限)'// 真实场景:ble.startBLEScan()setTimeout(()=>{this.isScanning=false},5000)}privatestopScan():void{this.isScanning=false// 真实场景:ble.stopBLEScan()}}真实代码:
ble.startBLEScan(null)ble.on('BLEDeviceFind',(result:Array<ble.ScanResult>)=>{for(leti:number=0;i<result.length;i++){letdevice:ble.ScanResult=result[i]// device.deviceId, device.deviceName, device.rssi}})要点:startBLEScan 的参数可以传 ScanOptions 过滤特定设备(按 ServiceUUID、设备名等)。扫描到足够设备后及时 stopBLEScan 省电。
GATT 连接
扫描到目标设备后,通过 GATT 连接并发现服务。
// 连接设备letgattClient:ble.GattClientDevice=ble.createGattClientDevice(deviceId)awaitgattClient.connect()// 发现服务letservices:Array<ble.GattService>=awaitgattClient.getServices()for(leti:number=0;i<services.length;i++){letservice:ble.GattService=services[i]letchars:Array<ble.GattCharacteristic>=service.characteristics// service.serviceUuid, characteristics[].characteristicUuid}// 断开连接gattClient.disconnect()关键区别:connect() 只是建立连接,getServices() 才能获取设备支持的服务和特征值。必须先发现服务才能读写。
特征值读写
每个 Characteristic 支持三种操作:读、写、通知。
读取特征值
// 读取心率值letcharacteristic:ble.GattCharacteristic={serviceUuid:'0000180d-0000-1000-8000-00805f9b34fb',characteristicUuid:'00002a37-0000-1000-8000-00805f9b34fb',characteristicValue:newArrayBuffer(0)}letresult:ble.GattCharacteristic=awaitgattClient.readCharacteristicValue(characteristic)// result.characteristicValue 包含心率数据写入特征值
letwriteValue:ble.GattCharacteristic={serviceUuid:'0000180d-0000-1000-8000-00805f9b34fb',characteristicUuid:'00002a39-0000-1000-8000-00805f9b34fb',characteristicValue:buffer// 要写入的数据}gattClient.writeCharacteristicValue(writeValue,ble.GattWriteType.WRITE_NO_RESPONSE)订阅通知
心率、步数等实时数据用通知模式——设备主动推,APP 被动收。
gattClient.on('BLECharacteristicChange',(char:ble.GattCharacteristic)=>{// char.characteristicValue 包含最新数据// 解析心率/步数等})// 开启通知letnotifyChar:ble.GattCharacteristic={serviceUuid:'0000180d-0000-1000-8000-00805f9b34fb',characteristicUuid:'00002a37-0000-1000-8000-00805f9b34fb',characteristicValue:newArrayBuffer(0)}awaitgattClient.setNotifyCharacteristicChanged(notifyChar,true)要点:setNotifyCharacteristicChanged 开启通知后,设备会在数据变化时主动推送,不需要 APP 反复读取。
BLE 通信全流程 Demo
模拟完整的 BLE 通信流程——扫描→连接→发现服务→读写特征值。
interfaceServiceInfo{id:stringuuid:stringcharacteristics:string[]}@Entry@Componentstruct BleFullDemo{@StatedeviceList:BleDevice[]=[]@StateconnectionStatus:string='未连接'@StateserviceList:ServiceInfo[]=[]@StatereadValue:string=''@StateisScanning:boolean=falsebuild(){Column({space:16}){Text('BLE 通信全流程').fontSize(22).fontWeight(FontWeight.Bold).width('100%')// 扫描区Row({space:8}){Button(this.isScanning?'扫描中':'扫描').onClick(()=>this.startScan())Button('模拟设备').onClick(()=>{this.deviceList.push({id:`AA:BB:CC:DD:EE:${(this.deviceList.length+1).toString().padStart(2,'0')}`,name:`智能设备-${this.deviceList.length+1}`,rssi:-35-this.deviceList.length*10,isConnected:false})})}// 设备列表ForEach(this.deviceList,(device:BleDevice)=>{Row(){Text(`${device.name}(${device.rssi}dBm)`).fontSize(14).layoutWeight(1)if(device.isConnected){Text('已连接').fontSize(12).fontColor('#4CAF50')}else{Button('连接').fontSize(12).height(28).onClick(()=>this.connectDevice(device.id))}}.padding(8).borderRadius(6).backgroundColor('#FAFAFA')},(device:BleDevice)=>device.id)// 服务列表if(this.serviceList.length>0){Text('GATT 服务:').fontSize(16).fontWeight(FontWeight.Medium).width('100%')ForEach(this.serviceList,(service:ServiceInfo)=>{Column({space:4}){Text(`服务:${service.uuid}`).fontSize(13).fontColor('#1a73e8')ForEach(service.characteristics,(char:string)=>{Row({space:8}){Text(`特征:${char}`).fontSize(12).layoutWeight(1)Button('读').fontSize(11).height(24).padding({left:6,right:6}).onClick(()=>{this.readValue='72 bpm'})}},(char:string)=>char)}.padding(8).borderRadius(6).backgroundColor('#F5F5F5')},(service:ServiceInfo)=>service.id)}if(this.readValue){Text(`读取结果:${this.readValue}`).fontSize(15).fontColor('#4CAF50').padding(12).borderRadius(8).backgroundColor('#E8F5E9')}Text(this.connectionStatus).fontSize(13).fontColor('#999999')}.width('100%').padding(20)}privatestartScan():void{this.isScanning=truesetTimeout(()=>{this.isScanning=false},3000)}privateconnectDevice(deviceId:string):void{this.connectionStatus='连接中...'setTimeout(()=>{this.connectionStatus='已连接'this.serviceList=[{id:'1',uuid:'0x180D (心率)',characteristics:['0x2A37 (心率测量)','0x2A38 (传感器位置)']},{id:'2',uuid:'0x180A (设备信息)',characteristics:['0x2A29 (制造商)','0x2A24 (型号)']}]},1500)}}常见 BLE Service UUID
| UUID | 名称 | 典型特征 |
|---|---|---|
| 0x180D | 心率服务 | 0x2A37心率测量、0x2A38传感器位置 |
| 0x181A | 环境感知 | 温度、湿度、气压 |
| 0x180F | 电池服务 | 0x2A19电量百分比 |
| 0x180A | 设备信息 | 制造商、型号、固件版本 |
| 0x1810 | 血压服务 | 收缩压、舒张压、脉率 |
| 0x1818 | 自行车速度 | 速度、踏频 |
踩坑清单
| 问题 | 原因 | 解决 |
|---|---|---|
| startBLEScan 报错 | 未申请蓝牙权限 | module.json5 声明 + 动态申请 |
| 扫描不到设备 | 蓝牙未开启或定位未开 | 检查蓝牙和定位开关 |
| connect 后无服务 | 未调用 getServices | connect 后必须 getServices |
| readCharacteristicValue 报错 | 特征不支持读 | 检查 properties.read 是否为 true |
| 通知收不到 | 未 setNotifyCharacteristicChanged | 先 setNotify(true) 再等推送 |
| 断开后重连失败 | 未释放 GattClient | disconnect 后重新 createGattClientDevice |
| RSSI 值跳变大 | 信号不稳定 | 多次扫描取平均值 |
| 写入后设备无响应 | 写入类型不对 | 区分 WRITE/WRITE_NO_RESPONSE |
| 连接超时 | 设备距离远或广播间隔大 | 靠近设备,增大扫描时间 |
| 多设备同时连接卡顿 | GATT 操作串行 | 避免并发操作,排队执行 |