Heart Rate Service(HRS,0x180D)是 BLE 里最经典、结构最简单的"传感器持续上报一个值"的 Profile,健身手环、心率带、运动手表都在用。它非常适合作为理解 GATT Profile 组织方式的入门模板——一个 Notify 特性 + 几个辅助特性就搞定。
一、角色
- Heart Rate Sensor(心率传感器):GATT Server、Peripheral。
- Collector(手机/码表):GATT Client、Central。
二、HRS(0x180D)特性
| 特性 |
UUID |
属性 |
作用 |
| Heart Rate Measurement |
0x2A37 |
Notify |
心率值(核心) |
| Body Sensor Location |
0x2A38 |
Read |
佩戴位置(胸/腕/手指…) |
| Heart Rate Control Point |
0x2A39 |
Write |
目前只支持"清零累计能量" |
三、Heart Rate Measurement(0x2A37,Notify)
一帧结构由首字节 Flags 决定:
- Flags(1 字节):
- bit0:心率值格式。0 = UINT8(1 字节),1 = UINT16(2 字节,小端)。
- bit1:是否含 Energy Expended 字段(0 = 否,1 = 是)。
- bit2~3:RR-Interval 个数(00 = 0 个,01 = 1 个,10 = 2 个,11 = 3 个…)。
- bit4~7:保留。
- Heart Rate Value:1 或 2 字节,取决于 bit0。单位 bpm。
- Energy Expended(可选,UINT16,单位 kJ):若 bit1 = 1 才存在,从会话开始累计。
- RR-Interval(可选,每个 UINT16):若 bit2~3 > 0 才跟在后面。单位是 1/1024 秒,表示两次 R 波(心跳)的时间间隔,用于心率变异性(HRV)分析。
解析时永远先看 Flags:心率值是 1 字节还是 2 字节、后面有没有能量和 RR,全由 Flags 决定,不能硬按固定长度读。
四、Body Sensor Location(0x2A38,Read)
告诉主机传感器戴在哪,枚举值:0 = Other,1 = Chest(胸口),2 = Wrist(手腕),3 = Finger,4 = Hand,5 = Ear Lobe,6 = Foot。App 据此显示"腕式心率"之类提示。可选,但建议实现。
五、Heart Rate Control Point(0x2A39,Write)
目前规范只定义一个操作:写入 0x01 = Reset Energy Expended,设备把累计能量清零。写其他值应被拒绝。没必要支持 Notify/Indicate。
六、典型流程
- 连接。
- (可选)读 Body Sensor Location(0x2A38)显示佩戴位置。
- 订阅 Heart Rate Measurement(0x2A37)的 Notify(开 CCCD 0x2902)。
- 持续收到心率帧,按 Flags 解析 bpm / 能量 / RR。
- 需要重置累计能量时,写 0x01 到 Control Point(0x2A39)。
七、常搭配的其他服务
- Battery Service(0x180F):电量显示。
- Device Information Service(0x180A):厂商/型号/版本。
- 运动场景常和 Running Speed and Cadence(0x1814)、Cycling Speed and Cadence(0x1816) 一起出现,组成完整运动档案。
八、实战坑提醒
- 心率值字节数看 Flags bit0:写死按 1 字节读会丢掉 >255 bpm 的高字节(虽少见但规范允许 UINT16)。
- RR-Interval 个数看 bit2~3,不要硬读固定数量,否则会越界或错位。
- RR 单位是 1/1024 秒,不是毫秒,换算要乘 1000/1024。
- Energy Expended 只在 bit1=1 时存在,且一旦支持就从连接起持续累加,只发一次带值、后续帧沿用。
- Control Point 只认 0x01,其余 opcode 应返回错误,别当通用命令通道。
- Notify 要开 CCCD(0x2902),否则收不到心率。
- UINT16 字段小端(Heart Rate 的 UINT16 形式、Energy、RR 都是),别读反。
- 首字节是 Flags 不是心率值,解析从第二字节才开始,新手常把 Flags 当成了 bpm。
一句话总结:HRS 是"一个 Notify 特性搞定主业"的典范——0x2A37 按 Flags 承载心率/能量/RR,0x2A38 报位置,0x2A39 只负责清零能量;吃透它的 Flags 驱动结构,就掌握了绝大多数"传感器类"Profile 的套路。