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唯一标识,每个特征对应一个具体的数据项
  • 每个特征由两部分组成:
    1. 特征声明(UUID 0x2803:描述特征的属性(可读/可写/可通知)、权限、值句柄
    2. 特征值(Characteristic Value):真正存储数据的属性,是读写/通知的操作对象

核心功能

  • 存储具体的业务数据(如心率值、电量百分比、时间戳)
  • 定义数据的访问方式:读、写、通知、指示
  • 作为客户端和服务端数据交互的「直接入口」

典型使用场景

  • 实时数据上报:心率测量、血氧值、步数(用通知)
  • 状态读取:电池电量、设备型号、传感器位置(用读)
  • 控制命令下发:时间同步、固件升级、消息推送(用写)
  • 配置参数读写:运动目标、闹钟设置、勿扰模式(用读写)

蓝牙手表实例

  • 心率测量特征(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. 客户端(手机)的完整交互流程

  1. 服务发现:手机通过服务UUID 0x180D,找到手表的心率服务
  2. 特征发现:手机在心率服务下,找到「心率测量」特征(UUID 0x2A37),获取其特征值句柄0x0012和CCCD句柄0x0013
  3. 开启通知:手机向CCCD句柄0x0013写入0x0001,开启心率通知
  4. 数据上报:手表采集到心率数据,通过特征值句柄0x0012向手机发送通知
  5. 读取位置:手机向「身体传感器位置」特征值句柄0x0015发起读请求,获取佩戴位置
  6. 写入控制:手机向「心率控制点」特征值句柄0x0017写入重置命令,手表执行对应操作

五、开发中最容易踩的坑&设计原则

1. 常见误区

  • ❌ 把CCCD当成独立层级:CCCD是描述符的一种,必须依附于特征
  • ❌ 给不需要通知的特征加CCCD:浪费资源,协议栈规范不要求
  • ❌ 服务和特征UUID重复:必须保证全局唯一,否则客户端无法识别
  • ❌ 权限配置错误:CCCD必须配置BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,否则手机无法开启通知

2. 设计原则(手表开发必看)

  • 服务按功能分组:把相关的特征放在同一个服务里,方便客户端发现
  • 特征按数据类型设计:一个特征只对应一个数据项,避免大而全的特征
  • 描述符按需添加:只加必要的标准描述符(如CCCD、格式描述符),减少冗余
  • CCCD必加场景:所有需要主动上报的特征(心率、步数、消息),必须添加CCCD
  • 权限严格匹配:公开数据(电量、型号)用无加密权限,隐私数据(步数、序列号)用加密权限

六、一句话终极总结

  • 服务 = 功能模块,用来分组
  • 特征 = 数据单元,用来交互
  • 描述符 = 特征的元数据,用来补充信息/配置
  • CCCD = 通知开关,用来控制实时数据上报

要不要我给你一份完整的心率服务GATT属性定义代码(NuttX/Zephyr协议栈),包含服务、特征、CCCD、描述符的完整实现,你可以直接用在手表项目里?

posted @ 2026-04-09 01:43  wzm888  阅读(0)  评论(0)    收藏  举报