4. GATT服务与特征:Service/UUID定义、Characteristic属性、CCC配置、Handle机制

好,咱们进入正题。GATT,全称是通用属性协议(Generic Attribute Profile)。说白了,它就是蓝牙设备之间怎么交换数据的规矩。你想想看,血糖仪测出个数值,总得告诉手机吧?这个“告诉”的过程,就是GATT在管。

我个人习惯,把GATT想象成一个文件柜。柜子里有抽屉(Service),抽屉里有文件夹(Characteristic),文件夹里装着具体的数据。嗯,这个比喻虽然简单,但很管用。

4.1 Service与UUID定义

Service,就是服务。一个蓝牙设备可以提供多个服务。比如血糖仪,可能有“血糖测量服务”、“设备信息服务”、“电池服务”等等。

每个Service都有一个唯一的ID,叫UUID(通用唯一标识符)。UUID有16位的,也有128位的。16位的是蓝牙联盟定义的标准服务,比如血糖服务就是0x1808。128位的是厂商自定义的,比如你开发个私有协议,就得自己生成一个。

我在项目中遇到过,有些工程师图省事,随便写个“0001”当UUID。结果呢?跟别的设备冲突了,数据乱套。所以,自定义服务一定要用128位UUID,别偷懒。

标准血糖服务UUID: 0x1808
标准设备信息服务UUID: 0x180A
自定义服务示例: 0000XXXX-0000-1000-8000-00805F9B34FB

定义Service的时候,要注意层级关系。一个Service可以包含多个Characteristic,但Service之间是平级的,不能嵌套。

4.2 Characteristic属性

Characteristic,就是特征。它是实际存放数据的地方。每个Characteristic也有自己的UUID,也有自己的属性。

属性有哪些呢?我列一下常用的:

  • Read(读):手机可以读取这个特征的值。比如读取血糖仪的序列号。
  • Write(写):手机可以写入数据。比如设置血糖仪的时间。
  • Notify(通知):设备主动给手机发数据。比如血糖测量完成,自动把结果推给手机。
  • Indicate(指示):跟Notify类似,但需要手机确认收到。可靠性更高,但速度慢一点。

你想想看,血糖仪测完血糖,数值是自动上报的,还是手机去读的?当然是自动上报。所以血糖测量特征,必须要有Notify属性。

我的经验: 如果数据很重要,比如血糖值,用Indicate更安全。但如果你追求实时性,比如连续血糖监测,Notify更合适。我曾经在一个项目里,因为用了Indicate导致数据延迟,被用户投诉了。后来改成Notify,问题解决。

Characteristic还有权限控制。比如有些特征只能读不能写,有些需要配对后才能访问。这些都是在定义时设置好的。

4.3 CCC配置

CCC,全称是Client Characteristic Configuration,客户端特征配置。它是个描述符(Descriptor),专门用来开关Notify和Indicate的。

为什么需要CCC?因为设备不能随便给手机发通知。手机得先告诉设备:“嘿,你可以给我发通知了。”这个“告诉”的过程,就是写CCC。

CCC的值很简单:

  • 0x0000:关闭通知和指示
  • 0x0001:开启通知
  • 0x0002:开启指示

我记得刚开始做蓝牙开发时,忘了配置CCC,结果手机死活收不到数据。查了两天,才发现是CCC没写。嗯,这个坑我替你们踩过了。

注意: 每次设备断开重连后,CCC会重置为0x0000。所以手机重连后,必须重新配置CCC。很多开发者会忽略这一点,导致重连后收不到数据。

CCC的Handle是固定的。每个支持Notify或Indicate的Characteristic,都会自动生成一个CCC描述符。它的Handle通常是Characteristic的Handle+1或+2,具体看协议栈实现。

4.4 Handle机制

Handle,就是句柄。它是GATT协议里每个属性(Service、Characteristic、Descriptor)的编号。从0x0001开始,依次递增。

手机跟设备通信时,不靠名字,靠Handle。比如手机说:“我要读Handle 0x0010的数据。”设备就知道,哦,这是要读血糖值。

Handle是固定的吗?是的。一旦设备启动,所有属性的Handle就确定了。但不同品牌的芯片,Handle分配方式可能不同。比如Nordic的芯片,Service的Handle从0x0001开始,Characteristic的Handle紧随其后。

我举个例子,一个简单的血糖仪GATT表:

Handle 类型 UUID 说明
0x0001 Service 0x1808 血糖服务
0x0002 Characteristic 0x2A18 血糖测量特征
0x0003 Value 0x2A18 血糖值数据
0x0004 Descriptor 0x2902 CCC配置

你看,Handle是连续的。手机要读血糖值,先通过Handle 0x0002找到特征,再读Handle 0x0003的数据。要开启通知,就写Handle 0x0004的CCC。

为什么不用UUID直接访问?因为UUID太长,128位呢,每次通信都带UUID太浪费带宽。Handle只有16位,效率高得多。

核心要点:
  • Service是服务的容器,用UUID标识
  • Characteristic是数据的载体,有读写通知等属性
  • CCC是通知/指示的开关,重连后必须重新配置
  • Handle是属性的编号,通信时用Handle代替UUID

好了,这一章的内容就这些。说白了,GATT就是一套规则,告诉你怎么组织数据、怎么访问数据。你只要把Service、Characteristic、CCC、Handle这四个概念搞明白,蓝牙数据传输这块就通了八成。

下一章,咱们聊聊实际的数据格式。血糖值怎么编码?时间戳怎么传?嗯,那才是真正动手的地方。