ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

华为手环心率实时传输到UWP应用:手机中转+局域网推送方案

华为手环心率实时传输到UWP应用:手机中转+局域网推送方案 前阵子接手一个项目要在UWP应用里实时显示华为手环的心率数据。网上搜了一圈中文资料要么停在“用手机App看就行”的层面要么就是问了一句“能不能连PC”然后就没了下文。真正动手做才发现华为手环不像专业心率带那样公开标准蓝牙协议直接连PC这条路基本走不通。折腾了几天最后我用了一套“手机采集局域网推送UWP接收”的架构把整条链路打通了。这篇文章就把整个方案从架构设计、代码实现到遇到的坑都梳理一遍给你一套能照着复现的参考实现。如果你最近也在做类似的项目或者正打算把手环的实时心率数据接到Windows桌面端那这篇文章很适合你。我会先说清楚数据到底该怎么拿再讲Android端怎么采集和推送最后说UWP端怎么接收和展示中间穿插我实测中遇到的问题和解决办法。1. 先聊清楚华为手环的“实时心率”到底怎么拿1.1 手环数据的三条获取路径要拿到华为手环的心率数据从技术上看主要有三条路。第一条是走华为官方的Health Kit接口。华为运动健康App把用户授权过的心率、睡眠、运动等数据传到华为云端第三方应用通过Health Kit获取。这条路最稳官方维护不需要逆向任何协议但需要申请开发者权限而且数据要经过手机App中转。第二条是直接用蓝牙BLE连接手环读取GATT服务里的心率特征。这条路上很多做智能硬件的人第一反应就是它但对华为手环来说坑很深。华为手环对外基本不暴露标准心率服务数据走的是私有协议官方也没有公开文档。即使通过抓包或者社区逆向拿到了一部分协议手环固件一升级可能就废了。第三条是走系统级共享通道。部分华为手机和手环之间有私有数据通道华为运动健康App会将一些数据写入系统传感器其他应用通过系统API读取。这个方案限制很大只适用于部分华为设备对PC端UWP项目来说完全不现实。1.2 为什么直接拿BLE连PC最容易被卡住我一开始也抱着侥幸心理想着“蓝牙心率服务都是标准化的0x180D这个服务ID总该有吧”。实测结果很打脸华为手环在PC上能被扫描到但广播包里根本没有标准Heart Rate Service。强行用BluetoothLEDevice.FromIdAsync去连接也拿不到0x180D的服务句柄。就算有些型号能连接上手环也不会默认向外持续发送心率数据。手环的心率上报机制是“按需触发”的只有在华为运动健康App建立连接并且开启运动模式之后才会以较高频率持续上报。PC上的UWP应用没有华为的私有配对认证流程很难触发这个状态。另外Windows的BLE API本身就面向标准GATT协议设计对私有服务和加密特征的支持很有限。所以直接BLE连PC这个方案从稳定性和工作量角度来看都不划算。1.3 我的推荐架构手机做中转UWP做接收端既然直接连PC走不通那就换一条务实路线。最终我采用的是这样一条数据链路华为手环 → 华为运动健康App蓝牙连接→ 手机端Health Kit SDK读取 → 局域网Socket推送 → PC端UWP接收解析并展示这个架构的好处有几个。数据源头是华为官方通道心率数值的可信度有保障。不需要逆向蓝牙协议只要把Health Kit的权限申请下来就能用。手机端负责采集和转发PC端只做接收和业务展示职责分离清晰后续换设备或者加功能都比较方便。唯一的代价就是手机必须常驻后台而且手机和PC需要处在同一个局域网内。对于个人项目或者实验室Demo来说这个代价完全能接受。2. 数据源头手机端用华为Health Kit采集心率2.1 申请Health Kit权限与工程配置华为Health Kit不是开通就能用的需要在华为开发者联盟逐步申请。第一步注册开发者账号并且创建应用包名、签名证书的SHA256指纹必须和你最后打包用的证书一致。这一点很多人会忽略先用debug签名申请后面换成release证书又会遇到鉴权失败还得重新走审核流程。创建完应用之后在AppGallery Connect后台开通Health Kit服务。注意心率属于敏感健康数据申请权限时最好把使用场景写清楚比如“用于PC端实时心率监控demo”审核周期一般是1到3个工作日。我那次等了两天半所以项目排期一定要把这个时间算进去。工程配置方面把后台下载的agconnect-services.json放到Android工程的app目录下然后在根目录的build.gradle里配置华为Maven仓库在app/build.gradle里添加Health Kit依赖。不同版本SDK的依赖坐标略有差异大致是这样的写法dependencies { implementation com.huawei.hms:health:6.11.0.301 }2.2 授权登录与心率读取流程Health Kit的使用有个前置条件用户必须先用华为账号登录并且明确授权你的应用读取心率数据。这个授权流程在代码里分三步。第一步集成华为账号登录也就是Account Kit。用户点击登录按钮之后会跳转到华为账号授权页同意后返回Authorization Code。第二步用这个Code去换取Health Kit的访问权限也就是申请scope心率对应的scope类似https://www.huawei.com/health/kit/heartrate。第三步检查用户是否已经在华为运动健康App里绑定了手环并且开启了数据同步。这一步经常被漏掉如果运动健康App里没有设备Health Kit拿到的永远是空数据。授权通过之后就可以创建HealthDataCollector来读取心率了。我当时的伪代码大致是这个样子class HeartRateService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val healthDataCollector HealthDataCollector(this) healthDataCollector.setDataCollectorListener( object : DataCollectorListener { override fun onReceiveData( dataType: HiHealthDataType, samplePoints: ListSamplePoint ) { val heartRate samplePoints .firstOrNull() ?.getFieldValue(HiHealthFields.FIELD_HEART_RATE) heartRate?.let { sendToUwp(it) } } }, HiHealthDataType.CONTINUOUS_HEART_RATE ) return START_STICKY } }这里要提醒一句华为SDK的类名在不同版本里有过调整DataCollectorListener可能在新的SDK里改成了别的回调接口。写代码的时候以官方文档为准但整体思路不变注册一个针对连续心率数据类型的监听拿到SamplePoint之后提取心率字段。2.3 把心率数据推到PC端UWP手机端拿到心率之后接下来的任务就是把数据送到PC。我在这个环节选择了TCP长连接而不是HTTP轮询。原因是心率数据是持续产生的TCP长连接一次握手之后就能一直传数据延迟低很多UWP端的StreamSocketListener实现起来也不复杂。为了保证UWP端能正确切割数据帧我约定了以换行符\n作为每条JSON数据的结束标志。Android端发送心率的代码简化之后是这样的fun sendHeartRate(heartRate: Int, timestamp: Long) { Thread { try { val socket Socket(192.168.1.100, 9000) val json {\heartRate\:$heartRate,\timestamp\:$timestamp}\n socket.getOutputStream().write(json.toByteArray(Charsets.UTF_8)) socket.close() } catch (e: Exception) { Log.e(HeartRateSender, send failed, e) } }.start() }在实际工程里需要处理几个细节。一是连接断开后的自动重连我建议在手机端维护一个全局的Socket连接断线后每秒重试一次。二是发送频率不要过高心率数据每秒一次到两次就够用了否则手机耗电和PC端UI刷新压力都会变大。三是建议用前台服务来跑这个发送逻辑否则Android系统会在App退到后台几分钟后杀掉进程。2.4 提高实时性的几个小技巧Health Kit虽然能拿到数据但实时性取决于手环的上报策略。我实测发现手环在普通待机状态下心率上报频率很低可能在几十秒到几分钟才更新一次。要让数据更实时需要做几件事。第一让手环进入运动模式。华为手环在跑步、走路这类运动模式下心率的采样频率会明显提高基本能做到每秒甚至更密的上报。第二把华为运动健康App在手机后台锁定同时关闭系统省电策略对它的限制否则App一被杀数据链路就断了。第三如果SDK支持配置读取周期把周期设为尽可能短比如1秒读取一次。实测下来这样调整之后UWP端收到的数据延迟基本在1到3秒以内。对于“实时心率展示”这个需求来说这个延迟是完全可以接受的。3. UWP端工程搭建从收数据到图表展示3.1 UWP项目的基础配置UWP端我用的是标准空白应用模板目标版本设置成Windows 10 1809以上就可以。创建完项目之后有两件事必须第一时间做掉。第一件事是修改Package.appxmanifest声明网络和蓝牙能力。如果只走局域网Socket方案声明privateNetworkClientServer就够了如果后面还想尝试BLE直连还需要加上bluetooth设备能力。声明之后看起来像这样Capabilities Capability NameprivateNetworkClientServer / DeviceCapability Namebluetooth / /Capabilities第二件事是添加JSON解析库。微软官方的System.Text.Json在UWP上可以直接用但我个人更喜欢用Newtonsoft.JsonAPI熟悉UWP兼容性也稳定。这里有个容易踩的坑UWP默认的网络访问权限很严格如果忘了声明privateNetworkClientServer程序运行时会默默抛异常不会弹明显的错误提示。所以遇到收不到数据的问题时优先检查这个声明有没有写上。3.2 用StreamSocketListener实现TCP接收端UWP里搭建TCP接收端比较简单核心就两个类StreamSocketListener负责监听连接DataReader负责读数据。启动监听的代码大致是这样private StreamSocketListener _listener; private async void StartServer() { _listener new StreamSocketListener(); _listener.ConnectionReceived OnConnectionReceived; await _listener.BindServiceNameAsync(9000); }连接建立之后每次收到数据时触发OnConnectionReceived回调。这里需要注意TCP是流式协议Android端发送的多个JSON包可能粘在一起也可能发生半包。所以我写了一个简单的按行读取方法以\n作为分隔符切分完整帧private async Taskstring ReadLineAsync(StreamSocket socket) { var reader new DataReader(socket.InputStream); reader.InputStreamOptions InputStreamOptions.Partial; var builder new StringBuilder(); while (true) { var size await reader.LoadAsync(512); if (size 0) break; for (uint i 0; i size; i) { var ch (char)reader.ReadByte(); if (ch \n) return builder.ToString(); builder.Append(ch); } } return null; }这个方法的核心思路是用Partial模式每次先读一批字节然后逐个字符找换行符找到就返回一帧数据没找到就把当前内容缓存到StringBuilder里继续读。实测下来对心率这种小数据量的场景完全够用。3.3 JSON解析与界面实时刷新收到完整的一帧JSON字符串后就可以解析成对象了。我定义的数据结构很简单public class HeartRateData { public int HeartRate { get; set; } public long Timestamp { get; set; } }解析代码用Newtonsoft.Json一行就能搞定var data JsonConvert.DeserializeObjectHeartRateData(line);拿到数据之后剩下的事情就是更新界面。UWP中UI更新必须回到UI线程我用的是DispatcherQueue来实现。核心逻辑是这样private async void OnHeartRateReceived(HeartRateData data) { await DispatcherQueue.EnqueueAsync(() { CurrentHeartRateText.Text data.HeartRate.ToString(); LastUpdateTimeText.Text DateTimeOffset.FromUnixTimeMilliseconds(data.Timestamp).ToString(HH:mm:ss); }); }如果需要画实时曲线推荐用LiveCharts.Uwp把最近N秒的数据点放到ObservableCollection里曲线会自动刷新。我自己项目里还接了一个历史记录列表每来一条数据就往ListView里塞一条方便事后回看。3.4 顺带跑通的BLE直连备用方案虽然华为手环直接BLE连PC不现实但这套UWP代码我后来也没白写因为我把它用在了专业心率带上。如果你以后遇到支持标准心率服务的设备可以参考下面这段扫描和读取的流程。先用DeviceInformation.FindAllAsync扫描附近的BLE设备找到之后用BluetoothLEDevice.FromIdAsync连接再通过GetGattServiceAsync拿到心率服务订阅心率特征的通知事件var device await BluetoothLEDevice.FromIdAsync(deviceId); var service await device.GetGattServiceAsync(GattServiceUuids.HeartRate); var characteristic service.GetCharacteristics(GattCharacteristicUuids.HeartRateMeasurement)[0]; characteristic.ValueChanged OnHeartRateChanged; await characteristic.WriteClientCharacteristicDescriptorAsync( GattClientCharacteristicConfigurationDescriptorValue.Notify);OnHeartRateChanged事件里会收到字节数组心率值就在其中。这个流程对标准BLE心率设备非常可靠延迟几乎是毫秒级。所以如果你的项目对实时性要求极高与其死磕华为手环不如直接用专业心率带UWP端代码几乎不用改只是数据源换了一下。4. 常见问题与排查技巧实录4.1 Health Kit授权与数据为空这个问题我遇到过好几次而且表现很诡异应用能正常启动也没有报错但onReceiveData就是不回调或者回调里samplePoints始终是空的。排查时我建议按这个顺序来。先确认华为运动健康App里已经绑定了手环并且首页能看到当前心率。再做一遍Health Kit授权确认授权页面里心率权限是勾选状态。最后核对AGC后台配置包名、证书指纹、Health Kit服务状态是否全部一致。我那次就是证书指纹对不上重新配置之后数据就正常了。还有一个比较隐蔽的点如果手环和手机连接的是不同华为账号Health Kit里也读不到数据。手环绑定的账号必须和App登录的账号一致。4.2 手机端网络推送失败手机端Socket推不出去优先检查三件事。第一手机和PC是否在同一个局域网有些办公网络会做AP隔离设备之间互相ping不通。第二PC防火墙是否放行了UWP应用的入站连接Windows Defender防火墙默认会拦截UWP应用的入站请求需要手动添加规则。第三Android端Socket连接PC时PC的IP地址要写对而且建议先在PC上用其他工具验证一下9000端口确实在监听。另外提醒一点Android 9之后默认禁止明文HTTP和Socket连接如果出现Cleartext communication not permitted的报错需要给应用显式允许明文流量或者使用加密方案。4.3 UWP端收不到数据或粘包解析错误UWP端收不到数据我就干过一件蠢事代码逻辑完全正确但忘记在Package.appxmanifest里声明privateNetworkClientServer能力折腾了一下午。所以这个问题必须排在第一位排查。粘包问题在心率场景下不太明显但如果发送频率调高比如每秒多次还是会出现两个JSON挤在一个TCP包里的情况。解决办法就是我3.2节里提到的按行读取和\n分隔符这个方法实测能稳定解决粘包。还有一个编码坑Android端发送时如果用了getBytes()默认编码中文注释还好但JSON里的字段值如果包含特殊字符PC端解析就可能乱码。最好两端统一用UTF-8。4.4 实时性不达预期的处理思路如果你按上面的方案跑通之后发现数据更新频率还是太低大概率是手环没有处于运动模式。华为手环在普通待机状态下心率上报频率会被大幅降低Health Kit自然拿不到连续数据。解决办法是让用户在华为运动健康App里开启一个运动项目比如健走或者跑步心率上报频率马上就会上来。如果这样还是不能满足实时性要求那就说明华为手环本身的能力上限就到这了。消费级手环的心率传感器和算法本来就是为健康管理设计的不是为科研或者医疗场景准备的。真要做秒级甚至毫秒级的实时心率应用建议换支持标准BLE心率服务的专业心率带代码复用我3.4节的部分就行。5. 最后分享几点经验项目收尾之后我自己复盘了一下有几个经验值得单独说。第一个经验是先用假数据联调UWP端再做Android端到PC端的真实链路。我一开始就直接上了真设备结果Android端连不上PC、UWP端解析报错、PC防火墙拦截三个问题同时冒出来排错排到怀疑人生。后来改成先用一个简单的Android模拟器或者命令行工具往9000端口发假数据UWP端调通之后再去解决手机端的问题效率高了很多。第二个经验是华为Health Kit的权限审核周期比想象中长而且审核通过之后SDK配置还有各种细节。建议项目一启动就先把开发者账号、应用创建、权限申请这些前置流程走起来不要等到代码写完了再申请不然只能在等待审核中干瞪眼。第三个经验是架构的价值大于单个设备。这套手机采集加局域网转发的方案数据源只要换成苹果的HealthKit、小米手环的开放平台或者专业BLE心率带UWP端几乎不用动。所以如果你后续有接入其他穿戴设备的需求这套架构完全可以复用只需要新增一个适配器。最后再提一句华为手环的实时心率本质上是“消费级指标”它更适合做趋势展示、运动记录这类场景。如果甲方的需求里写着“实时”而且精度要求很高建议第一时间把预期拉回到合理范围或者直接建议换硬件。技术方案能解决的问题很多但硬件能力的天花板还是要尽早说清楚。
返回列表