3. GATT服务与信号弱化的关联

GATT 服务,说白了就是蓝牙设备之间怎么交换数据的规矩。我刚开始接触 BLE 时,总觉得 GATT 就是个简单的读写接口。后来在项目中吃过亏才明白——信号一弱,这套规矩就变得特别脆弱。

咱们先搞清楚 GATT 的结构,再聊信号弱化怎么影响它。

3.1 GATT Profile 结构

GATT Profile 是建立在 ATT(Attribute Protocol)之上的高层框架。它定义了数据怎么组织、怎么访问。我个人习惯把 GATT 结构想象成一棵树:

核心层级关系:

  • Profile —— 最顶层,定义设备支持哪些服务
  • Service —— 功能模块,比如电池服务、心率服务
  • Characteristic —— 具体数据点,比如电池电量值
  • Descriptor —— 特性的附加信息,比如配置、描述

你想想看,这就像文件系统。Profile 是磁盘,Service 是文件夹,Characteristic 是文件,Descriptor 是文件的属性。层级清晰,查找方便。

GATT Profile 层级结构 Profile Service (UUID + 包含的 Characteristics) Characteristic (声明 + Value + Descriptors) Descriptor (CCC、User Description、Format 等) 每个层级都有对应的 UUID 和属性权限 信号弱化时,层级越深的数据越容易丢失

3.2 Service 与 Characteristic 定义

咱们拆开来看。Service 是一组相关数据的集合。比如电池服务(0x180F)包含电池电量这个 Characteristic。每个 Service 和 Characteristic 都有唯一的 UUID 标识。

Characteristic 的定义稍微复杂点。它由三部分组成:

组成部分 作用 长度
Characteristic 声明 描述属性、Value Handle、UUID 5 字节 + UUID 长度
Characteristic Value 实际数据存放位置 可变(通常 1-512 字节)
Characteristic Descriptor 附加配置信息 可变

我记得有一次调试心率设备,发现手机端读到的数据总是少几个字节。查了半天,原来是信号弱导致 Characteristic Value 的 MTU 协商失败,数据被截断了。嗯,这里要注意——MTU 大小直接影响你能一次传输多少数据。

我的经验:定义 Characteristic 时,尽量把 Value 长度控制在 20 字节以内。为什么?因为 BLE 的默认 MTU 是 23 字节,减去 3 字节的 L2CAP 头,留给 ATT 的只有 20 字节。超过这个长度,底层会自动分包。信号一弱,分包就容易丢。

3.3 信号弱化对 GATT 通信的影响

好,重点来了。信号弱化到底怎么影响 GATT?我把它归纳成三个层面:

3.3.1 属性协议(ATT)层的影响

GATT 依赖 ATT 进行数据交换。ATT 的每个操作(Read、Write、Notify、Indicate)都需要在 LL 层完成一次或多次数据包交换。信号弱化会导致:

  • 重传次数增加 —— 链路层收不到 ACK 就会重传,重传多了就超时
  • ATT 超时 —— 默认 ATT 超时是 30 秒,信号差时很容易触发
  • 事务中断 —— 长数据被拆成多个 ATT PDU,中间丢一个就全废

我曾经遇到一个 case:设备在 10 米外连接正常,但走到 15 米时,Notify 数据就收不到了。抓包一看,LL 层重传了 8 次才成功,但 ATT 层已经超时断连了。

3.3.2 Service Discovery 的影响

设备连接后,GATT Client 会发起 Service Discovery,遍历所有 Service 和 Characteristic。这个过程需要多次 ATT Read By Group Type 和 Read By Type 请求。信号弱化时:

  1. 发现过程变慢,每次请求都要等重传
  2. 部分 Service 可能发现不全,导致功能缺失
  3. 极端情况下,Discovery 超时,连接直接断开

避坑指南:我曾经在项目中遇到一个坑——手机端只发现了 3 个 Service,但设备端注册了 5 个。原因是信号弱导致第 4 个 Service 的响应包丢了,手机以为 Discovery 结束了。后来我加了个机制:Discovery 完成后主动校验 Service 数量,不够就重试。

3.3.3 Notify/Indicate 的影响

Notify 和 Indicate 是 GATT 最常用的数据推送方式。信号弱化对它们的影响不同:

方式 是否需要 ACK 信号弱化时的表现
Notify 不需要 数据可能丢失,发送端不知道
Indicate 需要 重传导致延迟,但数据可靠

说白了,Notify 快但不靠谱,Indicate 慢但可靠。信号好时差别不大,信号一弱就体现出来了。我建议关键数据(比如报警、状态变更)用 Indicate,普通数据(比如温度读数)用 Notify。

3.4 信号弱化下的 GATT 通信流程图

下面这张图展示了信号正常和信号弱化时,GATT 通信的差异:

信号弱化对 GATT 通信的影响对比 信号正常 Client 发送 Read Request Server 回复 Read Response 一次完成,无重传 信号弱化 Client 发送 Read Request LL 层重传(可能多次) ATT 超时或断连 信号弱化时,LL 层重传次数增加,ATT 层响应延迟变大 当重传超过 ATT 超时阈值(默认 30s),连接被强制断开 核心结论:信号弱化 → 重传增加 → 延迟增大 → 超时断连 GATT 层本身没有重传机制,重传依赖 LL 层 所以信号弱化时,GATT 通信的可靠性完全取决于底层链路质量

3.5 我的建议

做 GATT 服务设计时,一定要把信号弱化考虑进去。我总结了几个实用原则:

  • Service 数量别太多 —— 超过 10 个 Service,Discovery 时间会明显增加
  • Characteristic 尽量精简 —— 不必要的 Descriptor 能省就省
  • 关键数据用 Indicate —— 虽然慢,但至少不会丢
  • 加个重试机制 —— GATT 操作失败后,等 1-2 秒再试一次

嗯,这些经验都是我在实际项目中一点点踩坑踩出来的。信号弱化不是 Bug,是物理规律。我们能做的,就是在软件层面尽量适应它。