ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

HarmonyOS 6.0 蓝牙BLE设备通信实战——智能手环/蓝牙秤接入全流程

2026/8/10 12:03:52 拓冰建站 浏览量
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 后无服务未调用 getServicesconnect 后必须 getServices
readCharacteristicValue 报错特征不支持读检查 properties.read 是否为 true
通知收不到未 setNotifyCharacteristicChanged先 setNotify(true) 再等推送
断开后重连失败未释放 GattClientdisconnect 后重新 createGattClientDevice
RSSI 值跳变大信号不稳定多次扫描取平均值
写入后设备无响应写入类型不对区分 WRITE/WRITE_NO_RESPONSE
连接超时设备距离远或广播间隔大靠近设备,增大扫描时间
多设备同时连接卡顿GATT 操作串行避免并发操作,排队执行