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 CCCD0x00000x00010x0002当前客户端的订阅配置
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。

参考资料