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结构——说白了,就是数据到底怎么组织,才能让手机和牙刷互相理解。到时候见。