11.5. 连接

一旦一个中央设备从广播流中选定某个外围设备并向其发送 连接请求(connect request),双方就会退出广播 / 扫描模式,进入 连接(connection) 状态。此时无线电会在链路层的数据信道上调度其活动,按照连接建立时约定的序列在这些信道之间伪随机跳频。链路层之上的一切——GATT、安全、L2CAP——都运行在这里建立的连接之上。

11.5.1. 连接事件

处于 BLE 连接中的两台设备并不会持续不断地传输数据。它们会约定一个 连接间隔(connection interval),在每个间隔点上双方都唤醒无线电、交换排队待发的数据包、确认已收到的内容,然后重新进入睡眠。每一次这样的交换都称为一个 连接事件(connection event)

一条时间线,中央设备在上方轨道、 外围设备在下方轨道。在每个连接间隔的 边界处,两条轨道都显示一个标有 "TX/RX" 的 短脉冲。脉冲之间的间隙标记为 "connection interval"; 每个脉冲的持续时长标记为 "connection event"。 外围设备有几个被跳过的事件,标记为 "peripheral latency: peripheral may skip if idle"。

每一侧的无线电只在短暂的连接事件期间处于唤醒状态,其余时间都在睡眠。在 外围设备延迟(peripheral latency) 的允许下,外围设备可以跳过某些事件。

支配这一过程的各项数值在连接建立时协商确定,并由链路层强制执行。它们对应用程序既以请求侧旋钮的形式可见,也以回报的最终生效值的形式可见。

  • 连接间隔。 7.5 ms 到 4 s,步长为 1.25 ms。除非请求不合理,中央设备会采用外围设备所要求的值。较短的间隔以更频繁的无线电活动为代价,以更低的延迟传递数据;较长的间隔则节省功耗,但会使每次往返都变慢。

  • 外围设备延迟。 一个非负整数 N。允许外围设备在没有内容可发送时跳过最多 N 个连接事件,重新进入睡眠,而不必为一次空交换唤醒无线电。这对于每秒醒来上报一次、但又希望为偶发的即时消息保留一个 响应灵敏的 连接间隔的传感器很有用。

  • 监督超时。 100 ms 到 32 s。如果任意一侧在这段时间内都没有收到对方的任何消息,链路即被判定丢失,双方都回到广播 / 扫描状态。该超时必须长于 connection_interval * (1 + peripheral_latency)——链路层会拒绝违反此条件的值。

aioble.Device.connect() 接受 min_conn_interval_usmax_conn_interval_us 参数,使中央设备可以请求一个特定范围;无线电最终敲定的实际值可在连接建立后通过链路层配置读回。

11.5.2. 连接内部的“中央”与“外围”含义

在广播阶段设定的角色在连接建立之后依然保持:

  • 中央设备 驱动时序——它掌控跳频序列的主控侧以及各个连接事件。

  • 外围设备 是被动响应方。它可以请求更改连接参数(比如换用更慢的间隔以节省功耗),但是否接受由中央设备决定。

这些角色与谁托管 GATT 数据库无关,后者是一个独立的维度。按照惯例,外围设备同时也是 GATT 服务端,中央设备是 GATT 客户端,但 BLE 允许任一侧托管 GATT 服务。摄像头几乎总是遵循惯例:对于“摄像头发布数据”类应用采用外围设备 + 服务端,对于“摄像头从传感器读取数据”类应用采用中央设备 + 客户端。

11.5.3. 最大传输单元(MTU)

链路层默认承载的数据包很短——27 字节的有效载荷,其中只有 23 字节可供 GATT 使用。这对于一个小读数或一条短命令足够了,但相对于任何多字节内容来说都太小了。双方可以将其向上协商,直至无线电固件所支持的上限(在现代控制器上通常为几百字节)。

aioble API 通过 aioble.DeviceConnection.exchange_mtu() 驱动这一协商,结果可在 mtu 属性上获取。更大的 MTU 意味着对于任何大于约 20 字节的值都能减少往返次数,代价是少量的缓冲区内存开销。

11.5.4. 生命周期

连接会一直持续,直到以下情况之一发生:

  • 任意一侧调用 disconnect()

  • 监督超时触发(超出范围、无线电关闭、对端崩溃),或者

  • 出现明确的链路层故障(加密不匹配、配对被拒绝)。

当连接断开时,其上排队或在途的每个 GATT 操作都会引发 aioble.DeviceDisconnectedError,应用程序所处的任何 async with connection 块都会干净地退出。外围设备通常的响应是回到 aioble.advertise() 并等待下一个中央设备;中央设备的响应则是重新扫描,或将断开事件上报给应用程序。

生命周期正是 aioble 之所以有用的原因之一。同步的 BLE API 不得不暴露断开回调和事件掩码;而 asyncio 版本则将断开转化为正在等待该操作的协程内部的异常,这恰恰是 async with 清理机制所擅长处理的。