蓝牙应用层协议GATT 文章目录一、GATT 是什么二、GATT 在 BLE 通信中的位置三、GATT 的关键角色客户端 vs. 服务器四、GATT 的数据结构层层嵌套1. **Profile配置文件**2. **Service服务**3. **Characteristic特征值**五、一个实际的通信流程以心率手环为例总结图表关键点解读总结Handle vs UUID核心要点详细解释与类比标准 UUID vs. 自定义 UUID总结一、GATT 是什么GATT的全称是Generic Attribute Profile即通用属性协议。简单来说它是低功耗蓝牙BLE设备之间进行通信的核心数据交换协议。它定义了两个 BLE 设备在建立连接后如何组织和传输数据。一个非常形象的比喻是GATT 就像是我们去图书馆借书所遵循的一整套规则。图书馆本身就是你的 BLE 设备。书架的分类比如“计算机类”、“文学类”就是Service服务。具体的每一本书比如《蓝牙开发入门》就是Characteristic特征值书里的内容就是特征值所包含的数据。图书馆的目录检索系统和借阅流程就是GATT协议本身。这套规则GATT规定了书如何摆放、如何查找、如何借阅和归还。没有这套规则你就无法有效地从图书馆获取信息。二、GATT 在 BLE 通信中的位置为了理解 GATT 的角色我们看一下简化的 BLE 通信栈------------------------------------ | 你的手机 App | -- 你交互的应用层 ------------------------------------ | GATT 客户端 | -- 发起请求的一方如手机 ------------------------------------ | ATT 协议层 | -- 负责数据封装的底层协议 ------------------------------------ | L2CAP 逻辑层 | ------------------------------------ | PHY 物理层 (无线电波) | ------------------------------------ATTAttribute Protocol 是 GATT 的基础它定义了数据的格式即“属性”。GATT 是在 ATT 之上构建的高级别规范它赋予了这些属性具体的含义和组织结构。GATT 依赖于连接 GATT 通信必须在两个设备建立 BLE 连接之后才能进行。它不像广播那样可以一对多。三、GATT 的关键角色客户端 vs. 服务器在 GATT 通信中有两个明确的角色这与我们熟悉的客户端-服务器模型如网页浏览非常相似角色GATT 服务器GATT 客户端比喻图书馆读者描述数据的持有者和提供者数据的请求者和使用者典型设备心率手环、智能门锁、传感器手机、平板、电脑职责存储数据并响应客户端的请求发现服务器上的数据并读写它们主动/被动被动等待命令主动发起命令一个 BLE 连接中一方是 GATT 服务器另一方是 GATT 客户端。手机 App 通常作为GATT 客户端而去读写手环等外设外设就是GATT 服务器。四、GATT 的数据结构层层嵌套这是 GATT 最核心的概念。数据是以一种层级化、嵌套的方式组织的如下图所示它清晰地展示了从配置文件Profile到特征值Characteristic的包含关系ProfileService 2Characteristic CValuePropertiesService 1Characteristic BValuePropertiesCharacteristic AValuePropertiesDescriptors e.g., CCCD下面我们来详细解释图中的每一层1.Profile配置文件这不是一个实际在总线上传输的数据实体而是一个功能规范文档。它定义了一个具体的应用场景需要哪些Service的组合。例如心率 Profile规定一个设备必须包含心率 Service并且该 Service 必须包含心率测量 Characteristic。2.Service服务服务是一个相关功能的数据集合。它像一个容器里面装着多个实现某种功能的 Characteristic。每个 Service 都有一个唯一的 128 位 UUID来标识。标准 Service如电池服务、设备信息服务使用蓝牙技术联盟定义的16位短UUID如0x180F代表电池服务。设备制造商也可以定义自己的自定义 Service使用 128 位的长 UUID。例子电池服务0x180F里包含了“电池电量”这个特征值。3.Characteristic特征值特征值是 GATT 数据库中的最小数据单元是你实际读写数据的地方。一个 Characteristic 包含三个部分Value值实际的数据本身例如心率数值、电池电量百分比。这就是你读取和写入的对象。Properties属性定义了可以对这个 Characteristic 进行什么操作。它是一个位掩码常见的有Broadcast广播Read可读Write Without Response无响应写Write带响应写Notify通知不需要客户端 ACK主动上报 Indicate指示需要客户端 ACK 确认Authenticated Signed Writes签名写Descriptors描述符描述 Characteristic 的元信息。最最重要的是CCCD客户端特征配置描述符当 Characteristic 的属性包含Notify或Indicate时必须存在。开启 Notify/Indicate 就是往这个描述符写值。0x0000关闭上报0x0001开启 Notify0x0002开启 IndicateCharacteristic User Description(0x2901)可读字符串特征的文字说明Characteristic Format(0x2904)说明 value 数据类型、单位五、一个实际的通信流程以心率手环为例手机客户端与手环服务器建立 BLE 连接。手机发起发现服务“嘿你有什么功能”。手环回复我这里有心率服务(0x180D)和电池服务(0x180F)。手机针对心率服务发现特征“你的心率服务里有什么数据”。手环回复我有心率测量特征(0x2A37)它的属性是Notify。手机向心率测量特征下面的CCCD 描述符写入0x0001“请在有新心率数据时通知我”。手环的心率数据发生变化时就会主动向手机发送一个包含新数据的“通知”包而无需手机再次询问。手机的 GATT 客户端回调函数如onCharacteristicChanged会收到这个数据并更新界面。具体层级关系如下Service: Heart Rate (0x180D) └─ Characteristic: Heart Rate Measurement (0x2A37) ├─ Value: [0x00, 0x4B] # 心率 75 bpm ├─ Properties: Notify └─ Descriptor: CCCD └─ 用于开启/关闭心率通知总结GATT是 BLE 通信的数据交互规则。它基于客户端-服务器模型。数据采用Profile - Service - Characteristic的层级结构组织。Characteristic是数据操作的基本单元通过Notify/Indicate属性实现服务器主动推送这是 BLE 应用中最常见的模式。理解了 GATT你就掌握了 BLE 应用开发的精髓。这张图清晰地展示了层级关系中每一个关键元素的标识符渲染错误:Flowchart 渲染失败: Cannot set properties of undefined (setting next)图表关键点解读服务层 (Service Level)服务声明 (Service Declaration):Handle0x0001: 这是整个服务的门牌号。UUID0x2800: 这是一个固定的类型表示这是一个主服务声明。Value UUID0x180D: 这是服务声明所包含的值它定义了这是一个什么样的服务。0x180D代表心率服务。特征层 (Characteristic Level)特征声明 (Characteristic Declaration):Handle0x0002: 特征声明的门牌号。UUID0x2803: 固定的类型表示这是一个特征声明。Value: 这个声明包含的值比较复杂它打包了三个信息该特征的操作属性、该特征值的句柄、该特征的UUID。特征值 (Characteristic Value):Handle0x0003:这是核心读/写/通知操作的实际数据对象。UUID0x2A37: 定义了这是什么数据这里是心率测量值。Properties: 定义了允许的操作这里是Notify和Read。描述符层 (Descriptor Level)CCCD 描述符:Handle0x0004: 描述符的门牌号。UUID0x2902: 固定的类型表示这是一个客户端特征配置描述符。总结Handle vs UUID通过这张图我们可以清晰地总结元素Handle (句柄)UUID (通用唯一识别码)是什么数据的内存地址数据的类型说明特点简短的数字在设备连接后动态发现较长的字符串用于静态定义功能作用定位: “数据在哪里?”识别: “这是什么数据?”类比图书馆的索书号书的ISBN号或分类通信过程当手机App想要读取身体传感器位置时底层发生的其实是应用层通过UUID 0x2A38请求。蓝牙栈查找之前发现的地图找到UUID 0x2A38对应的Handle是0x0006。蓝牙栈向设备发送一个ATT协议命令READ_REQ(handle0x0006)。设备回复READ_RSP(handle0x0006, value0x03)(0x03代表胸部)。希望这张综合了handle和UUID的图能让您对GATT数据库的结构有更透彻的理解是的特征值Characteristic绝对有 UUID而且这是它的核心身份标识在上面的图表中0x2A37和0x2A38就是特征值的 UUID。可能之前的解释有些绕让我们抛开复杂的结构直接聚焦于特征值本身。核心要点每一个特征值都有两个非常重要的 UUID用于“特征声明”的 UUID它的值是固定的0x2803。这个 UUID 就像一个大声的宣告“大家好这里是一个特征值的开始”。它所在的这个“声明”本身有一个句柄比如0x0002。特征值本身的 UUID这是真正定义这个特征值功能的 UUID。它告诉我们这个特征值是什么能干什么。它存储在特征声明所指向的特征值对象中这个对象有自己的句柄比如0x0003。为了更清晰地理解这一点下图直观地展示了这两个 UUID 如何共同定义一个特征值渲染错误:Flowchart 渲染失败: Cannot set properties of undefined (setting next)详细解释与类比让我们用公司职位来类比这更容易理解服务Service部门如销售部0x180D特征声明Declaration职位公告牌上面写着“这里是销售部的一个职位”牌子编号是0x0002特征值的 UUID具体的职位名称如“销售总监”0x2A37特征值的句柄该职位人员的工位位置如3号工位0x0003当你需要和“销售总监”沟通读写数据时你最终需要找到的是他的工位句柄而“销售总监”这个职位名称特征值UUID是帮助你找到正确工位的依据。标准 UUID vs. 自定义 UUID和服務一样特征值的 UUID 也分为标准值和自定义值类型示例 UUID示例功能说明标准特征值0x2A37心率测量由蓝牙技术联盟定义确保不同厂商设备互通。标准特征值0x2A19电池电量一个百分比0-100%的数字。自定义特征值XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX厂商特定功能由设备制造商自定义用于实现特殊功能。总结是的特征值有UUID而且至关重要特征声明 UUID (0x2803)是一个固定的信号标志着“这里有一个特征”。特征值 UUID (如0x2A37)是特征的核心身份定义了它的数据类型和功能。句柄 (如0x0003)是特征值在设备上的实际地址用于高效通信。在应用开发中你通过特征值的 UUID如0x2A37 来寻找和操作它底层蓝牙栈会帮你找到对应的句柄来完成实际的读写操作。