Skip to content

GATT:BLE 应用开发的核心 ​

1. GATT 到底是什么 ​

GATT(Generic Attribute Profile)定义了如何用属性表示服务、特征值和描述符,并规定发现、读取、写入、通知、指示等操作。

GATT Server 内部维护一张属性表。表中的每个 Attribute 都具有:

  • Handle:当前数据库中的 16 位索引,连接期间用于快速访问;
  • Type:通常由 UUID 标识;
  • Value:属性内容;
  • Permissions:是否允许读取或写入,是否要求加密、认证或授权。

应用开发时通常使用 SDK 抽象出的 Service、Characteristic 和 Descriptor,不需要手工构造每一条底层 Attribute,但理解属性表有助于排查 Handle、权限和缓存问题。

2. Service、Characteristic、Descriptor ​

2.1 Service ​

Service 是一组相关功能的集合。标准服务具有 Bluetooth SIG 分配的 UUID,例如 Device Information Service;厂商业务通常定义 128 位自定义 UUID。

Primary Service 表示设备的主要功能,Secondary Service 通常被其他服务引用。绝大多数自定义产品服务使用 Primary Service。

2.2 Characteristic ​

Characteristic 是最常接触的业务数据单元。它包含一个值,以及描述这个值能如何被访问的 Properties。

2.3 Descriptor ​

Descriptor 用于补充描述特征值。其中最常见的是 CCCD(Client Characteristic Configuration Descriptor,UUID 0x2902),客户端通过写它来开启或关闭 Notify/Indicate。

常见误区

Characteristic 声明支持 Notify,并不代表连接后会自动上报。客户端必须先订阅;协议层实际表现通常是客户端写入 CCCD。不同连接的订阅状态应分别管理。

3. GATT 数据结构的真实样子 ​

从应用层看,GATT 数据库是一棵 Service → Characteristic → Descriptor 的树;从 ATT 层看,它实际是一张按 Handle 排列的 Attribute 表。

一个 Characteristic 并不是属性表中的单独一行。它至少由 Characteristic Declaration 和 Characteristic Value 两个 Attribute 组成;如果支持 Notify 或 Indicate,后面还会有 CCCD。

下面是一张简化属性表:

HandleAttribute TypeAttribute Value含义
0x00200x2800 Primary Service自定义 Service UUID一个服务从这里开始
0x00210x2803 Characteristic DeclarationProperties + Value Handle 0x0022 + Characteristic UUID描述下一个特征值
0x0022自定义 Characteristic UUID当前传感器数值真正被 Read 或 Notify 的值
0x00230x2902 CCCD0x0000、0x0001 或 0x0002当前客户端的订阅配置
0x00240x2803 Characteristic DeclarationProperties + Value Handle 0x0025 + Characteristic UUID下一个特征值声明
0x0025自定义 Characteristic UUID当前配置值真正被 Read/Write 的值

客户端执行服务发现,本质上就是通过 ATT 查询这张表:先找到 Service 的 Handle 范围,再找其中的 Characteristic Declaration,最后根据需要查找 Descriptor。之后的读写和通知使用发现到的 Value Handle。

3.1 CCCD 的值 ​

CCCD 通常使用以下值:

值含义
0x0000关闭 Notify 和 Indicate
0x0001开启 Notify
0x0002开启 Indicate

CCCD 表示客户端对当前连接的配置,不是设备全局开关。多连接设备需要分别保存各连接的订阅状态。

4. UUID 和 Handle 不要混用 ​

UUID ​

UUID 表示“这是什么”。标准对象常用 16 位 UUID,自定义业务一般使用 128 位 UUID。

Handle ​

Handle 表示“它在这张属性表的哪个位置”。Handle 由 Server 的 GATT 数据库布局决定,不是跨固件版本稳定的业务标识。

因此 App 应通过 UUID 发现目标特征值,并把本次发现得到的对象或 Handle 交给系统 API。不要在协议文档里把某个测试时看到的 Handle 写死成永久接口。

5. Characteristic Properties 与 Permissions ​

Properties 描述支持哪些 GATT 操作,Permissions 决定执行操作需要什么访问条件。两者缺一不可。

Property含义常见用途
Read客户端主动读取版本、当前配置、静态信息
Write写入并等待协议层响应控制命令、重要配置
Write Without Response写入时不等待 ATT 响应可容忍丢失或自带确认的高频数据
NotifyServer 主动发送,无 ATT 确认传感数据、状态变化、数据流
IndicateServer 主动发送,有 ATT 确认需要协议层确认的重要事件

典型错误是特征值只配置了 Write Without Response,而 App 调用 Write with Response;或只配置 Notify,却没有 CCCD。此时 API 往往报告 property not supported。

6. Read、Write、Notify、Indicate 如何选择 ​

ATT 层有响应不等于业务执行成功。例如“开锁命令写入成功”只能说明 Server 接收了写请求,不能证明锁已经打开。重要命令应定义应用层响应:

text
App -> Device: SET_STATE, request_id=42, target=ON
Device -> App: RESPONSE, request_id=42, result=OK, actual=ON

7. 一个实用的 GATT 设计示例 ​

假设设计一台带传感器和 OTA 的设备:

标准服务 ​

  • Device Information Service:厂商、型号、序列号、硬件和固件版本;
  • Battery Service:电量百分比(电池产品可考虑)。

自定义控制服务 ​

特征值Properties方向用途
CommandWriteApp → Device可靠下发命令
ResponseNotifyDevice → App命令执行结果
TelemetryNotifyDevice → App传感数据与异步事件
ConfigurationRead + Write双向少量静态配置

自定义 OTA 服务 ​

特征值Properties用途
OTA ControlWrite + Notify开始、结束、校验和结果
OTA DataWrite Without Response固件数据块

把高频数据和低频控制拆开,可以分别设置队列、权限和流控策略。不要为了省 UUID 把所有数据都塞进一个特征值而不给包定义。

8. MTU 与单次有效载荷 ​

ATT_MTU 表示 ATT PDU 的最大尺寸。对常见的 Write、Notify 等操作,应用单包可用数据长度通常可按下式估算:

text
单次特征值有效载荷 ≈ ATT_MTU - 3

默认 ATT_MTU 常见为 23,对应 20 字节特征值数据。连接后双方可交换 MTU,但最终值受两端协议栈和系统限制,业务层不能假定协商一定达到期望值。

还要区分:

  • ATT_MTU;
  • Link Layer Data Length;
  • PHY 速率;
  • 应用层每包数据长度。

它们都会影响吞吐量,但不是同一个参数。即使 MTU 很大,SDK 缓冲区、连接间隔、每个连接事件允许的包数以及手机调度仍可能成为瓶颈。

9. 服务发现与 GATT 缓存 ​

客户端连接后通常执行服务发现。手机系统可能缓存 GATT 数据库以加速重连。如果固件升级后修改了服务、特征值或 Handle 布局,旧缓存可能让 App 看到过时结构。

产品设计时应:

  • 尽早稳定 GATT 接口;
  • 正确实现 Generic Attribute Service 中的 Service Changed 机制及相关缓存能力;
  • 在开发阶段遇到结构不一致时,尝试清除绑定、系统缓存或重新安装 App;
  • 不要把“重启蓝牙”当作产品级缓存方案。

10. 回调里应该做什么 ​

协议栈回调通常运行在受限制的线程或任务中。推荐只完成快速工作:

  1. 复制收到的数据;
  2. 校验基本长度;
  3. 投递到业务队列;
  4. 尽快返回。

不要在回调中长时间阻塞、写 Flash、执行复杂业务或等待另一个 BLE 操作。否则可能造成协议栈超时、队列拥堵甚至看门狗复位。

11. 本篇小结 ​

GATT Server 对外呈现 Service、Characteristic 和 Descriptor,内部则是一张由 Handle 索引的 Attribute 表。UUID 定义对象身份,Handle 定位本次数据库中的属性;Properties 定义操作能力,Permissions 定义访问条件;Notify/Indicate 需要客户端通过 CCCD 订阅。理解这张表以后,服务发现、读写和通知就不再是几组孤立的 API。

参考资料 ​