11.2. BLE 协议栈

与网络类似,低功耗蓝牙(Bluetooth Low Energy)被构建为一个分层堆栈,每一层解决单一问题,并向上层暴露一个清晰的抽象。下面的名称直接来自蓝牙核心规范,并出现在 aioblebluetooth API 中。

A vertical stack of five labelled boxes. From bottom to top: Physical (radio), Link Layer, GAP, GATT, Application. An arrow on the right points up the stack labelled "what we build". An arrow on the left points down the stack labelled "what is provided".

低功耗蓝牙协议栈。每一层向上一层递交一个更清晰的抽象,与网络协议栈采用的模式相同。

物理层。 在两个设备之间通过 2.4 GHz 无线电搬运比特。信道选择、调制、发射功率。摄像头的任务就是保持通电;其余的交由芯片完成。

链路层。 在两个同意相互通信的设备之间搬运数据包。它添加设备地址,使每个数据包能够瞄准某一个邻居;调度构成一次连接的周期性无线电事件;并处理接收方未确认的任何数据包的重传。

通用访问规范(GAP)。 发现与连接层。它定义了四种角色——广播者、观察者、外围设备、中心设备——以及让两个设备最初得以相互发现的广播 / 扫描协议,外加在它们之间打开和关闭连接的流程。地址、广播负载、连接参数和配对都存在于这一层。

通用属性规范(GATT)。 数据层。它位于一条已打开的 GAP 连接之上,暴露一个小型键/值数据库:一方承载一棵由称为特征的命名值组成的树,另一方对它们进行读取、写入或订阅。实际的应用字节就在这一层流动。

应用层。 摄像头与对端约定这些字节所代表的任何含义。蓝牙 SIG 发布了一些标准规范文件(profile)——心率、电池电量、环境感测——它们定义了常用特征,使互不相关的设备能够互操作,但任何应用程序也可以自由定义自己的规范文件。

11.2.1. 各层在运行时如何堆叠

这一模式与网络协议栈相吻合:

  • 应用程序的字节进入一个特征值

  • GATT 用一个标识这些字节属于哪个特征的报头将其包装起来。

  • GAP 保持一条已打开的连接处于运行状态,以便 GATT 有地方发送数据。

  • 链路层将这一切包装进一个数据包中,寻址到对端的设备地址,并调度一次无线电事件来发送它。

  • 物理层将该数据包转换为一段短暂的 2.4 GHz 无线电突发。

物理层和链路层从 Python 角度看几乎是不可见的——芯片和无线电固件处理它们。从 GAP 往上,摄像头的 Python 代码就有了更多发言权。

11.2.2. 面向用户的 API 悄然跳过的两个层

蓝牙规范还命名了另外两个位于链路层与 GAP/GATT 之间的层:主机控制器接口(HCI)——主机 CPU 用来驱动独立无线电芯片的协议——以及 L2CAP——将一条链路层连接拆分为多条逻辑信道的多路复用器。

二者在 aioble API 中都不可见,但二者都没有消失。HCI 位于 BLE 移植层内部,除非用到自定义构建的 MicroPython,否则它是不可见的;而 L2CAP 是 GATT 在其之上运行的承载层。想要原始字节流的应用程序可以申请自己的 L2CAP 信道(L2CAP 通道)。