2. BLE通信角色:Central与Peripheral、Broadcaster与Observer、GATT角色与关系
好,咱们接着聊。上一章我们把BLE协议栈的骨架搭起来了,这一章要深入一个非常核心的概念——角色。
你想想看,两个人要聊天,总得有人先开口,有人回应吧?BLE通信也是一样。设备之间要建立连接、交换数据,首先得搞清楚各自扮演什么角色。我刚开始做蓝牙项目时,就曾因为角色没选对,折腾了好几天才发现问题——嗯,这坑我先帮你踩了。
2.1 四大基础角色:谁在说话,谁在听?
BLE规范定义了四种基础角色。说白了,就是设备在通信中处于什么位置。我个人习惯把它们分成两组:
- 连接相关的角色:Central(中心设备)和 Peripheral(外围设备)
- 广播相关的角色:Broadcaster(广播者)和 Observer(观察者)
这两组角色可以同时存在。一个设备可以既是Peripheral又是Broadcaster,这很常见。
2.1.1 Central 与 Peripheral
这是最常用的一对角色。我打个比方:
- Central(中心设备):就像手机。它主动扫描、发起连接、管理多个连接。说白了,它是“老大”。
- Peripheral(外围设备):就像你的智能牙刷。它广播自己的存在,等待被连接。它是“小弟”。
在智能牙刷这个场景里:
- 手机App是Central,负责扫描、连接、接收数据、下发指令。
- 牙刷是Peripheral,负责广播、等待连接、上报刷牙数据。
关键点:一个Central可以连接多个Peripheral,但一个Peripheral通常只连接一个Central。我在项目中遇到过用户想用一把牙刷同时连两个手机——嗯,这不符合BLE的设计逻辑,除非你用多角色方案。
2.1.2 Broadcaster 与 Observer
这对角色不建立连接,只做单向通信。
- Broadcaster(广播者):只管往外发数据包,不关心谁在收。比如蓝牙信标(Beacon)。
- Observer(观察者):只管监听广播包,不主动连接。比如一些低功耗传感器网络。
你可能会问:“那智能牙刷用得上吗?”
当然用得上。比如牙刷在低电量时,可以只广播一个“电量低”的提示,而不建立完整连接。Observer(比如一个专用充电底座)收到后,可以点亮指示灯。这样省电又高效。
我的经验:在智能牙刷的待机模式下,我建议让牙刷同时扮演Peripheral和Broadcaster。Peripheral负责等待App连接,Broadcaster负责发送一些简单的状态信息(比如电量、是否在充电)。这样既保证了连接能力,又降低了功耗。
2.2 GATT角色:数据怎么交换?
好,角色分清楚了,那数据怎么传?这就轮到GATT(通用属性协议)登场了。
GATT定义了两种角色:
- GATT Server(服务端):提供数据的一方。它拥有属性(Attributes),比如电池电量、刷牙时长等。
- GATT Client(客户端):请求数据的一方。它向Server发送读写请求。
在智能牙刷里:
- 牙刷是GATT Server。它存储了各种数据:电量、刷牙模式、历史记录等。
- 手机App是GATT Client。它读取这些数据,或者写入一些配置(比如设置刷牙时长)。
注意:GATT角色和前面说的Central/Peripheral角色是独立的。一个Peripheral设备可以是GATT Server,也可以是GATT Client。我曾经见过一个新手把这两个概念搞混,结果代码里角色配置全错了——调试了三天才发现。
2.3 角色关系总结:一张表说清楚
为了让你看得更清楚,我整理了一张表:
| 角色类型 | 角色名称 | 智能牙刷中的例子 | 主要职责 |
|---|---|---|---|
| 连接角色 | Central | 手机App | 扫描、发起连接、管理多个连接 |
| Peripheral | 智能牙刷 | 广播、等待连接、响应请求 | |
| 广播角色 | Broadcaster | 牙刷(低电量广播) | 单向发送广播包 |
| Observer | 充电底座 | 监听广播包,不建立连接 | |
| GATT角色 | GATT Server | 智能牙刷 | 存储数据、响应读写请求 |
| GATT Client | 手机App | 发起读写请求、接收通知 |
2.4 实际项目中的角色配置
好了,理论说完了,咱们来点实际的。在智能牙刷的固件里,角色配置通常长这样:
// 伪代码示例:智能牙刷的角色初始化
void ble_init(void) {
// 1. 设置为 Peripheral 角色
ble_set_role(BLE_ROLE_PERIPHERAL);
// 2. 同时开启广播(Broadcaster 功能)
ble_advertising_start();
// 3. 注册 GATT Server 服务
// 比如电池服务、设备信息服务、自定义刷牙服务
gatt_server_register_service(BATTERY_SERVICE);
gatt_server_register_service(DEVICE_INFO_SERVICE);
gatt_server_register_service(TOOTHBRUSH_SERVICE);
// 4. 等待 Central 连接
// 连接建立后,GATT Client 就可以读写数据了
}
这段代码虽然简单,但包含了所有关键角色。你想想看:
- 牙刷是Peripheral,所以它等待连接。
- 它同时也在广播,所以它也是Broadcaster。
- 它注册了GATT Server,所以它是数据提供方。
避坑指南:我曾经在项目中遇到一个问题——牙刷广播了,但手机就是扫不到。后来发现是广播间隔设得太长(为了省电),导致手机错过广播包。解决办法是:在连接建立前用较短的广播间隔(比如100ms),连接后切换到长间隔(比如1秒)来省电。嗯,这个细节很重要。
2.5 角色切换:一个设备可以变脸
你可能觉得一个设备只能固定一个角色。其实不是。BLE允许设备在运行时切换角色。
举个例子:
- 智能牙刷平时是Peripheral,等待手机连接。
- 但如果牙刷需要和另一个牙刷交换数据(比如同步刷牙记录),它可以临时切换成Central,去连接另一个Peripheral。
这种灵活性在实际项目中非常有用。不过要注意:角色切换会增加代码复杂度,也容易引入bug。我的建议是:除非必要,否则保持角色单一。
总结一下:
- Central/Peripheral 决定谁连接谁。
- Broadcaster/Observer 决定谁广播谁监听。
- GATT Server/Client 决定谁提供数据谁消费数据。
- 这三个维度是正交的,可以自由组合。
好了,这一章就到这里。下一章我们会深入GATT的Profile和Service结构——说白了,就是数据到底怎么组织,才能让手机和牙刷互相理解。到时候见。