4. BLE服务定义:自定义血压服务UUID、血压测量特征、中间血压特征、血压特征属性配置
好,咱们进入正题。这一章要聊的是BLE服务定义,说白了就是给血压计在蓝牙世界里办个“身份证”。你想想看,手机App怎么知道对面连的是血压计,而不是手环或温度计?靠的就是咱们自定义的服务UUID和特征。
我个人习惯,在设计BLE服务时,先把整个框架画在纸上。别急着敲代码,先想清楚:我要暴露哪些数据?谁读谁写?需不需要通知?这些想明白了,后面写代码就是填空。
4.1 自定义血压服务UUID
蓝牙SIG(Special Interest Group)其实已经定义了一套标准的血压服务(Blood Pressure Service),UUID是0x1810。但为什么我还要讲自定义?因为在实际项目中,标准服务往往不够用。
我记得有一次做医疗级血压计,客户要求增加设备序列号、校准参数、固件版本等私有数据。标准服务根本塞不下这些。所以,咱们得自己定义一套服务UUID。
自定义血压服务UUID定义:
服务UUID:0000FFF0-0000-1000-8000-00805F9B34FB
说明:使用128位UUID,避免与标准服务冲突。FFF0是我习惯用的厂商代码段,你可以换成自己的。
为什么用128位?因为16位UUID是SIG保留的,咱们自己用容易撞车。你想想看,万一你的血压计和别人的温度计用了同一个16位UUID,那App就傻眼了——不知道该解析成血压数据还是温度数据。
4.2 血压测量特征(Blood Pressure Measurement)
这是核心特征,用来传输血压测量结果。包括收缩压、舒张压、平均动脉压、心率、时间戳等。
嗯,这里要注意:数据格式必须严格定义。我见过太多项目因为字节序搞错,导致App显示血压2000多,把用户吓一跳。
4.2.1 数据格式定义
| 字段 | 字节数 | 说明 |
|---|---|---|
| Flags | 1 | 标志位,指示后续字段是否存在 |
| Systolic | 2 | 收缩压,单位mmHg,小端格式 |
| Diastolic | 2 | 舒张压,单位mmHg,小端格式 |
| MAP | 2 | 平均动脉压,单位mmHg,小端格式 |
| Heart Rate | 2 | 心率,单位bpm,小端格式 |
| Timestamp | 7 | 时间戳,年(2)+月(1)+日(1)+时(1)+分(1)+秒(1) |
我曾经在项目中踩过一个坑:时间戳的年份字段用了1字节,结果2024年之后数据就溢出了。后来改成2字节,才解决问题。所以你看,细节决定成败。
4.2.2 特征属性配置
血压测量特征需要配置以下属性:
- Read:允许App读取最后一次测量结果
- Notify:测量完成后主动通知App
- Indicate:可选,带确认的通知,更可靠但速度慢
我的建议:用Notify就够了。Indicate虽然可靠,但每次都要等App回ACK,测量数据多的时候会卡顿。除非是医疗认证要求,否则别用Indicate。
4.3 中间血压特征(Intermediate Cuff Pressure)
这个特征很多人会忽略,但我觉得它特别重要。它用来实时传输袖带压力值,让App可以显示测量过程中的压力曲线。
你想想看,用户看着App上的压力曲线从0慢慢升到180,再慢慢降下来,是不是感觉更专业?而且对医生来说,压力曲线能帮助判断测量是否有效。
4.3.1 数据格式
// 中间血压特征数据格式
typedef struct {
uint8_t flags; // 0x01: 压力正在上升, 0x02: 压力正在下降
uint16_t pressure; // 袖带压力,单位mmHg,小端格式
uint8_t status; // 0x00: 测量中, 0x01: 测量完成
} __attribute__((packed)) intermediate_cuff_pressure_t;
这里有个小技巧:flags字段可以告诉App当前是充气阶段还是放气阶段。App可以根据这个字段动态更新UI,比如充气时显示“请勿移动手臂”,放气时显示“正在测量”。
4.3.2 特征属性配置
- Notify:必须的,压力数据要实时推送
- Read:可选,一般不需要
- Write:不需要,App不应该修改压力数据
注意:中间血压特征的更新频率不要太高。我曾经把更新间隔设成10ms,结果手机端蓝牙缓冲区直接爆了。建议50-100ms更新一次,既流畅又稳定。
4.4 血压特征属性配置(CCC)
CCC(Client Characteristic Configuration)描述符,是BLE里一个很关键的东西。它决定了App要不要接收通知。
说白了,CCC就是一个开关。App往CCC里写0x0001,表示“我要收通知”;写0x0000,表示“别烦我”。
4.4.1 CCC配置示例
// 血压测量特征的CCC描述符
static const ble_uuid128_t ccc_uuid =
BLE_UUID128_INIT(0x2902, 0x0000, 0x1000, 0x8000, 0x00805F9B34FB);
// 使能通知
void enable_blood_pressure_notification(uint16_t conn_handle) {
uint8_t value[2] = {0x01, 0x00}; // 0x0001 = 使能通知
sd_ble_gatts_write(conn_handle,
bp_measurement_ccc_handle,
sizeof(value),
value);
}
嗯,这里要注意:CCC描述符的句柄(handle)一定要保存好。我刚开始做的时候,忘了保存句柄,结果App怎么都收不到通知。查了两天才发现是句柄没对上。
4.4.2 多连接场景下的CCC管理
如果你的血压计支持同时连接多个设备(比如手机和手表),那每个连接都要独立管理CCC状态。
| 连接句柄 | CCC状态 | 说明 |
|---|---|---|
| 0x0001 | 0x0001 | 手机已使能通知 |
| 0x0002 | 0x0000 | 手表未使能通知 |
我曾经遇到一个bug:手机使能了通知,但手表没使能。结果血压测量完成后,数据只发给了手机,手表那边干等着。后来我在代码里加了个循环,遍历所有连接,只给使能了通知的连接发数据。
4.5 完整服务定义代码示例
最后,给一个完整的服务定义框架。你可以直接拿去用,改改UUID就行。
// 自定义血压服务定义
static void ble_bp_service_init(void) {
uint32_t err_code;
// 1. 添加服务
ble_uuid128_t service_uuid =
BLE_UUID128_INIT(0xF0, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80,
0x5F, 0x9B, 0x34, 0xFB);
ble_uuid_t ble_uuid;
ble_uuid.type = BLE_UUID_TYPE_VENDOR_BEGIN;
ble_uuid.uuid = 0xFFF0;
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&ble_uuid,
&service_handle);
APP_ERROR_CHECK(err_code);
// 2. 添加血压测量特征
// ... (特征添加代码)
// 3. 添加中间血压特征
// ... (特征添加代码)
// 4. 添加CCC描述符
// ... (CCC添加代码)
}
好了,这一章的内容就这些。总结一下:自定义UUID要128位,血压测量特征要配Notify,中间血压特征要控制更新频率,CCC要管理好多连接场景。把这些搞定了,你的血压计蓝牙通信就稳了八成。