GATT专题6之通知功能的使用
客户端向服务端句柄写入 0x01 启动通知功能,服务端在有数据变化时,通知客户端
是怎么向句柄写入0x01的?本质上写到哪里去了 ?
蓝牙GATT中,服务,特征,CCCD,描述符的功能区别是什么?分别用在什么场景?
蓝牙GATT核心组件:服务、特征、CCCD、描述符 功能与场景全解析
我用最直白、嵌入式开发最实用的方式,把这4个核心组件的区别、关系、使用场景讲透,完全贴合你做蓝牙手表开发的实际需求。
一、先给核心关系定调(一句话记住层级)
GATT数据库是一个树形结构,层级严格固定:
服务(Service) → 特征(Characteristic) → 描述符(Descriptor)
(其中 CCCD 是最常用的「标准描述符」,属于描述符的一个子类)
- 服务是容器,特征是数据单元,描述符是特征的元数据/配置项
- CCCD 是专门用来控制通知/指示开关的标准描述符,是描述符的一种,不是独立层级
二、逐个拆解:功能、本质、场景、对应手表实例
1. 服务(Service)
核心定义
服务是GATT中最大的功能单元,是一组相关特征的集合,相当于一个「功能模块」。
- 用服务UUID唯一标识(标准UUID由SIG定义,自定义UUID由厂商定义)
- 每个服务有一个「主服务声明」属性(UUID
0x2800),作为服务的入口 - 服务可以包含多个特征,也可以包含其他子服务(包含服务)
核心功能
- 给一组相关功能做逻辑分组,让客户端能快速识别设备的能力
- 作为特征的「容器」,规范特征的归属
- 实现功能的模块化,方便协议栈管理和客户端发现
典型使用场景
- 设备的核心功能模块:心率服务、电池服务、设备信息服务、时间同步服务
- 厂商自定义功能:运动服务、消息通知服务、固件升级服务
- 通用基础服务:GAP服务(设备信息)、GATT服务(服务变更)
蓝牙手表实例
- 心率服务(UUID
0x180D):包含「心率测量」「传感器位置」「心率控制点」3个特征 - 电池服务(UUID
0x180F):包含「电池电量」特征 - 设备信息服务(UUID
0x180A):包含「厂商名」「型号」「固件版本」等特征
2. 特征(Characteristic)
核心定义
特征是GATT中最小的「数据交互单元」,是服务里真正存储数据、实现读写/通知的核心。
- 用特征UUID唯一标识,每个特征对应一个具体的数据项
- 每个特征由两部分组成:
- 特征声明(UUID
0x2803):描述特征的属性(可读/可写/可通知)、权限、值句柄 - 特征值(Characteristic Value):真正存储数据的属性,是读写/通知的操作对象
- 特征声明(UUID
核心功能
- 存储具体的业务数据(如心率值、电量百分比、时间戳)
- 定义数据的访问方式:读、写、通知、指示
- 作为客户端和服务端数据交互的「直接入口」
典型使用场景
- 实时数据上报:心率测量、血氧值、步数(用通知)
- 状态读取:电池电量、设备型号、传感器位置(用读)
- 控制命令下发:时间同步、固件升级、消息推送(用写)
- 配置参数读写:运动目标、闹钟设置、勿扰模式(用读写)
蓝牙手表实例
- 心率测量特征(UUID
0x2A37):存储实时心率值,手表通过通知主动推送给手机 - 电池电量特征(UUID
0x2A19):存储电池百分比,手机主动读取 - 心率控制点特征(UUID
0x2A39):接收手机写入的控制命令(如重置能量消耗)
3. 描述符(Descriptor)
核心定义
描述符是「特征的附加属性/元数据」,用来描述特征值的额外信息或配置,属于特征的子属性。
- 用描述符UUID标识,分为标准描述符(SIG定义)和自定义描述符
- 必须依附于某个特征,不能独立存在
- 最常用的标准描述符就是CCCD(客户端特征值配置描述符),还有特征用户描述、格式描述符等
核心功能
- 补充特征的元信息:如数据格式、单位、描述文本
- 配置特征的行为:如开启/关闭通知、设置广播权限
- 扩展特征的能力,满足不同业务需求
典型使用场景
- 控制通知/指示开关(CCCD,最常用)
- 描述特征值的格式:如心率值的单位、数据类型(格式描述符
0x2904) - 给特征添加可读的描述文本(用户描述符
0x2901) - 自定义配置项:如传感器的采样率、上报周期
蓝牙手表实例
- CCCD(UUID
0x2902):心率测量特征的CCCD,用来开启/关闭心率通知 - 特征用户描述符(UUID
0x2901):给「心率测量」特征添加描述文本「实时心率数据」 - 格式描述符(UUID
0x2904):描述心率值的格式为「uint8,单位bpm」
4. CCCD(客户端特征值配置描述符,Client Characteristic Configuration Descriptor)
核心定义
CCCD是GATT中最特殊、最常用的标准描述符,专门用来控制特征的「通知(Notify)」和「指示(Indicate)」开关。
- 固定UUID:
0x2902,所有支持通知/指示的特征都必须包含这个描述符 - 是一个16位的位域值:
0x0000:默认值,关闭通知和指示0x0001:开启通知(Notify,无应答)0x0002:开启指示(Indicate,带应答)
- 每个连接独立存储,不同客户端的CCCD值互不影响
核心功能
- 让客户端(手机)自主控制是否接收特征的通知/指示
- 是实现「实时数据主动上报」的核心开关(如心率、步数、消息通知)
- 区分通知和指示两种上报模式,满足不同可靠性需求
典型使用场景
- 所有需要主动上报实时数据的特征:心率、血氧、步数、来电通知
- 低延迟、高频率的数据上报(用通知,无应答,效率高)
- 高可靠性的关键数据上报(用指示,带应答,确保送达)
蓝牙手表实例
- 心率测量特征的CCCD:手机写入
0x0001,手表就会持续推送实时心率;写入0x0000,停止推送 - 消息通知特征的CCCD:手机开启后,手表收到来电/短信就主动推送给手机
- 步数统计特征的CCCD:手机开启后,手表实时同步步数变化
三、四者核心区别对比表(一目了然)
| 组件 | 层级 | 本质 | 核心作用 | 标识 | 生命周期 | 典型操作 |
|---|---|---|---|---|---|---|
| 服务(Service) | 最高层 | 功能模块容器 | 分组相关特征,标识设备能力 | 服务UUID | 服务启动时创建,全局唯一 | 服务发现、服务使能 |
| 特征(Characteristic) | 服务子层 | 数据交互单元 | 存储业务数据,实现读写/通知 | 特征UUID | 服务初始化时创建,服务内唯一 | 读、写、通知、指示 |
| 描述符(Descriptor) | 特征子层 | 特征元数据 | 补充特征信息、配置特征行为 | 描述符UUID | 特征初始化时创建,特征内唯一 | 读、写(配置) |
| CCCD | 描述符子类 | 通知控制开关 | 控制特征的通知/指示开启/关闭 | 固定UUID 0x2902 |
特征初始化时创建,连接内独立 | 写(开关)、读(状态) |
四、完整层级关系+交互流程(以手表心率服务为例)
1. 服务端(手表)的GATT数据库结构
GATT数据库
├─ 服务:心率服务(UUID 0x180D,句柄0x0010)
│ ├─ 特征:心率测量(UUID 0x2A37,句柄0x0012)
│ │ ├─ 特征声明(句柄0x0011)
│ │ ├─ 特征值(句柄0x0012,存储心率数据)
│ │ └─ 描述符:CCCD(UUID 0x2902,句柄0x0013,控制通知开关)
│ ├─ 特征:身体传感器位置(UUID 0x2A38,句柄0x0015)
│ │ ├─ 特征声明(句柄0x0014)
│ │ └─ 特征值(句柄0x0015,存储佩戴位置)
│ └─ 特征:心率控制点(UUID 0x2A39,句柄0x0017)
│ ├─ 特征声明(句柄0x0016)
│ └─ 特征值(句柄0x0017,接收控制命令)
└─ 服务:电池服务(UUID 0x180F,句柄0x0018)
└─ 特征:电池电量(UUID 0x2A19,句柄0x0020)
├─ 特征声明(句柄0x0019)
└─ 特征值(句柄0x0020,存储电量百分比)
2. 客户端(手机)的完整交互流程
- 服务发现:手机通过服务UUID
0x180D,找到手表的心率服务 - 特征发现:手机在心率服务下,找到「心率测量」特征(UUID
0x2A37),获取其特征值句柄0x0012和CCCD句柄0x0013 - 开启通知:手机向CCCD句柄
0x0013写入0x0001,开启心率通知 - 数据上报:手表采集到心率数据,通过特征值句柄
0x0012向手机发送通知 - 读取位置:手机向「身体传感器位置」特征值句柄
0x0015发起读请求,获取佩戴位置 - 写入控制:手机向「心率控制点」特征值句柄
0x0017写入重置命令,手表执行对应操作
五、开发中最容易踩的坑&设计原则
1. 常见误区
- ❌ 把CCCD当成独立层级:CCCD是描述符的一种,必须依附于特征
- ❌ 给不需要通知的特征加CCCD:浪费资源,协议栈规范不要求
- ❌ 服务和特征UUID重复:必须保证全局唯一,否则客户端无法识别
- ❌ 权限配置错误:CCCD必须配置
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,否则手机无法开启通知
2. 设计原则(手表开发必看)
- 服务按功能分组:把相关的特征放在同一个服务里,方便客户端发现
- 特征按数据类型设计:一个特征只对应一个数据项,避免大而全的特征
- 描述符按需添加:只加必要的标准描述符(如CCCD、格式描述符),减少冗余
- CCCD必加场景:所有需要主动上报的特征(心率、步数、消息),必须添加CCCD
- 权限严格匹配:公开数据(电量、型号)用无加密权限,隐私数据(步数、序列号)用加密权限
六、一句话终极总结
- 服务 = 功能模块,用来分组
- 特征 = 数据单元,用来交互
- 描述符 = 特征的元数据,用来补充信息/配置
- CCCD = 通知开关,用来控制实时数据上报
要不要我给你一份完整的心率服务GATT属性定义代码(NuttX/Zephyr协议栈),包含服务、特征、CCCD、描述符的完整实现,你可以直接用在手表项目里?
浙公网安备 33010602011771号