11.5. 連線¶
當中央裝置(central)從廣播資料流中挑選出一個周邊裝置並向它送出 連線請求(connect request) 後,雙方就會脫離廣播/掃描模式而進入 連線(connection) 狀態。此時無線電會在連結層(link layer)的資料通道上排程其活動,並依照連線時雙方議定的順序在這些通道間做虛擬隨機跳頻。連結層以上的所有機制——GATT、安全性、L2CAP——都建立在此處所建立的連線之上。
11.5.1. 連線事件¶
BLE 連線中的兩個裝置並不會持續不斷地傳輸。它們會議定一個 連線間隔(connection interval),並在每個間隔時雙方都喚醒無線電、交換已排入佇列的封包、確認彼此所收到的內容,然後再回到睡眠狀態。每一次這樣的交換都稱為一個 連線事件(connection event)。
每一側的無線電只在短暫的連線事件期間醒著,其餘時間皆處於睡眠狀態。周邊裝置在 周邊延遲(peripheral latency) 下可以略過事件。¶
支配這一切的數值是在連線時協商出來的,並由連結層強制執行。它們對應用程式而言既以請求端的可調參數呈現,也以回報回來的最終結果值呈現。
連線間隔。 7.5 ms 到 4 s,以 1.25 ms 為步進。除非請求不合理,否則中央裝置會採用周邊裝置所要求的值。較短的間隔以較高的無線電活動為代價換取較低延遲的資料傳輸;較長的間隔可節省電力,但會使每次往返變慢。
周邊延遲。 一個非負整數 N。當周邊裝置沒有任何資料要傳送時,最多可以略過 N 個連線事件,回到睡眠而非為了一次空交換而喚醒無線電。這對於每秒醒來回報一次、但又希望在偶發的即時訊息時保有低 回應性 連線間隔的感測器很有用。
監督逾時(supervision timeout)。 100 ms 到 32 s。若任一側在這段時間內都未聽到對方的任何訊息,連結即被判定為遺失,雙方都回到廣播/掃描狀態。此逾時值必須大於
connection_interval * (1 + peripheral_latency)——連結層會拒絕違反此條件的值。
aioble.Device.connect() 接受 min_conn_interval_us 與 max_conn_interval_us,讓中央裝置可以請求一個特定範圍;無線電最終所採用的實際值,可在連線建立後透過連結層設定讀回。
11.5.2. 「中央」與「周邊」在連線中的意義¶
在廣播時所設定的角色,在連線建立之後依然維持不變:
中央裝置 主導時序——它掌管跳頻順序與連線事件的主控側。
周邊裝置 則是被動回應的一方。它可以請求變更連線參數(例如改用較慢的間隔以節省電力),但是否接受由中央裝置決定。
這些角色與誰承載 GATT 資料庫無關,那是另一個獨立的面向。依慣例,周邊裝置同時也是 GATT 伺服器(server),中央裝置則是 GATT 用戶端(client),但 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 清理機制所為之而生的用途。