第1章:GATT——蓝牙通信的“普通话”
各位同学,咱们今天聊聊GATT。
说实话,我刚入行那会儿,看到GATT这三个字母,第一反应是“这又是什么高大上的协议层?”后来做共享单车项目,被坑了几次,才真正搞明白——GATT就是蓝牙设备之间说“普通话”的规范。
你想想看,两个蓝牙设备要交换数据,总得有个统一的规矩吧?谁当服务器,谁当客户端,数据怎么组织,怎么读写——这些就是GATT要解决的问题。
4.1 Server/Client模型:谁说了算?
GATT采用典型的Server/Client模型。这个模型其实不复杂:
- Server(服务器):持有数据的一方。比如共享单车的车锁,它存着车辆状态、锁控指令这些信息。
- Client(客户端):发起请求的一方。比如你的手机App,它去读取车锁的状态,或者发送开锁指令。
我习惯这么理解:Server是“被访问者”,Client是“访问者”。一个Server可以同时被多个Client连接,但一个Client同一时间只能连一个Server。
关键点:在共享单车场景里,车锁永远是Server,手机永远是Client。这个角色分配是固定的,不能反过来。
为什么?因为车锁资源有限,它不可能主动去扫描手机、发起连接。手机作为客户端,主动去发现、连接车锁,这才是合理的架构。
4.2 Service/Characteristic/Descriptor结构:数据的三层包装
GATT把数据组织成三层结构,我管它叫“俄罗斯套娃”:
- Service(服务):最外层,代表一类功能。比如“锁控服务”、“电量信息服务”。
- Characteristic(特征):中间层,代表具体的数据项。比如“开锁指令特征”、“电量百分比特征”。
- Descriptor(描述符):最内层,描述特征的附加信息。比如“客户端特征配置描述符”(CCCD),用来开启通知功能。
举个例子,共享单车的“锁控服务”大概长这样:
Service: 锁控服务 (UUID: 0xFFE0)
├── Characteristic: 开锁指令 (UUID: 0xFFE1)
│ └── Descriptor: CCCD (UUID: 0x2902)
├── Characteristic: 车辆状态 (UUID: 0xFFE2)
│ └── Descriptor: CCCD (UUID: 0x2902)
└── Characteristic: 电量信息 (UUID: 0xFFE3)
└── Descriptor: 电量单位描述 (UUID: 0x2901)
你看,一个Service下面可以挂多个Characteristic,每个Characteristic又可以挂多个Descriptor。这种结构的好处是层次清晰,扩展方便。
我的经验:设计Service时,别把太多功能塞到一个Service里。我见过一个项目,把所有特征都塞到一个Service里,结果代码维护起来特别痛苦。建议按功能模块拆分,比如锁控一个Service,电量一个Service,定位一个Service。
4.3 UUID分配规则:给每个数据一个身份证
UUID(通用唯一标识符)就是每个Service、Characteristic、Descriptor的“身份证号”。GATT规范里,UUID分两种:
| 类型 | 长度 | 用途 | 示例 |
|---|---|---|---|
| 标准UUID | 16位 | 蓝牙SIG定义的标准服务/特征 | 0x1800(设备信息服务) |
| 自定义UUID | 128位 | 厂商自定义的服务/特征 | 0000FFE1-0000-1000-8000-00805F9B34FB |
标准UUID是蓝牙技术联盟(SIG)定好的,比如电池服务(0x180F)、设备信息服务(0x180A)。这些是通用的,谁都能用。
但共享单车这种定制化产品,肯定要用自定义UUID。我建议的分配规则是:
- 基础UUID:固定一个128位前缀,比如
0000XXXX-0000-1000-8000-00805F9B34FB - XXXX部分:按功能分配,比如:
- 0xFFE0:锁控服务
- 0xFFE1:开锁指令特征
- 0xFFE2:车辆状态特征
- 0xFFE3:电量信息特征
注意:自定义UUID的XXXX部分,建议避开0x1800-0xFFFF这个范围,因为这是SIG保留的标准区。我曾经见过有人用了0x1801,结果跟标准服务冲突,调试了整整两天才发现问题。
嗯,这里还要提一嘴Descriptor的UUID。标准Descriptor的UUID是固定的,比如:
- 0x2901:特征用户描述(Characteristic User Description)
- 0x2902:客户端特征配置(Client Characteristic Configuration)
- 0x2904:特征表示格式(Characteristic Presentation Format)
这些标准Descriptor,能直接用就别自己造轮子。尤其是0x2902(CCCD),它是开启通知/指示功能的关键,几乎所有需要实时数据更新的特征都得配上它。
4.4 实战中的坑与建议
最后,分享几个我在共享单车项目里踩过的坑:
- 坑一:Service数量别太多。有些同学喜欢把每个小功能都拆成一个Service,结果一个设备上挂了十几个Service。这会导致手机端扫描时间变长,用户体验很差。我建议控制在3-5个以内。
- 坑二:Characteristic的权限要设对。开锁指令应该是“可写”的,车辆状态应该是“可读+通知”的。权限设反了,手机写不进去数据,或者读不到状态,调试起来很头疼。
- 坑三:Descriptor别漏了CCCD。如果你想让手机实时收到车锁的状态变化(比如锁开了、锁关了),就必须在对应的Characteristic下加上CCCD描述符,并且手机端要正确配置它。
好了,这一章的内容就这些。GATT的结构其实不复杂,但细节很多。下一章咱们聊聊蓝牙广播与扫描——车锁怎么让手机找到自己,这可是共享单车的第一步。
一句话总结:GATT就是蓝牙通信的“数据组织规范”,Server/Client定角色,Service/Characteristic/Descriptor定结构,UUID定身份。搞懂了这三层,蓝牙通信的骨架就搭起来了。