11.5. Połączenia

Gdy urządzenie centralne wybierze urządzenie peryferyjne ze strumienia rozgłaszania i wyśle do niego żądanie połączenia (connect request), obie strony wychodzą z trybu rozgłaszania / skanowania i przechodzą w połączenie (connection). Radio planuje teraz swoją aktywność na kanałach danych warstwy łącza, przeskakując między nimi pseudolosowo zgodnie z sekwencją uzgodnioną w momencie nawiązania połączenia. Wszystko powyżej warstwy łącza – GATT, bezpieczeństwo, L2CAP – działa na bazie połączenia ustanawianego w tym miejscu.

11.5.1. Zdarzenie połączenia

Dwa urządzenia w połączeniu BLE nie transmitują w sposób ciągły. Uzgadniają one interwał połączenia (connection interval) i przy każdym interwale obie strony wybudzają radio, wymieniają zakolejkowane pakiety, potwierdzają to, co odebrały, i wracają do uśpienia. Każdą z tych wymian nazywamy zdarzeniem połączenia (connection event).

Oś czasu z urządzeniem centralnym na górnej ścieżce oraz urządzeniem peryferyjnym na dolnej ścieżce. Na każdej granicy interwału połączenia obie ścieżki pokazują krótki impuls opisany "TX/RX". Odstęp między impulsami jest opisany jako "connection interval"; czas trwania każdego impulsu to "connection event". Urządzenie peryferyjne ma kilka pominiętych zdarzeń opisanych "peripheral latency: peripheral may skip if idle".

Radio po każdej ze stron jest wybudzone tylko podczas krótkich zdarzeń połączenia, a wszystko inne pozostaje uśpione. Urządzenie peryferyjne może pomijać zdarzenia w ramach opóźnienia urządzenia peryferyjnego (peripheral latency).

Wartości, które tym sterują, są negocjowane w momencie nawiązania połączenia, a warstwa łącza je wymusza. Są widoczne dla aplikacji zarówno jako pokrętła po stronie żądania, jak i jako wynikowe wartości zwracane z powrotem.

  • Interwał połączenia. Od 7,5 ms do 4 s, w krokach co 1,25 ms. Urządzenie centralne przyjmuje wartość, o jaką prosi urządzenie peryferyjne, chyba że żądanie jest nieuzasadnione. Krótsze interwały dostarczają dane z mniejszym opóźnieniem kosztem większej aktywności radia; dłuższe oszczędzają energię, ale spowalniają każdą pełną wymianę.

  • Opóźnienie urządzenia peryferyjnego. Nieujemna liczba całkowita N. Urządzenie peryferyjne może pominąć do N zdarzeń połączenia, gdy nie ma nic do wysłania, wracając do uśpienia zamiast wybudzać radio dla pustej wymiany. Przydatne dla sensorów, które wybudzają się, by raz na sekundę zgłosić odczyt, ale chcą zachować niski, responsywny interwał połączenia na potrzeby rzadkiej, natychmiastowej wiadomości.

  • Limit czasu nadzoru (supervision timeout). Od 100 ms do 32 s. Jeśli któraś ze stron nie słyszy niczego od drugiej przez ten czas, łącze zostaje uznane za utracone i obie strony wracają do rozgłaszania / skanowania. Limit czasu musi być dłuższy niż connection_interval * (1 + peripheral_latency) – warstwa łącza odrzuca wartości naruszające tę zależność.

aioble.Device.connect() przyjmuje min_conn_interval_us oraz max_conn_interval_us, dzięki czemu urządzenie centralne może zażądać konkretnego zakresu; rzeczywistą wartość, na której ustaliło się radio, można odczytać poprzez konfigurację warstwy łącza po nawiązaniu połączenia.

11.5.2. Co „centralne” i „peryferyjne” oznaczają wewnątrz połączenia

Role ustalone w czasie rozgłaszania pozostają w mocy po nawiązaniu połączenia:

  • Urządzenie centralne steruje czasem – jest właścicielem strony nadrzędnej (master) sekwencji przeskoków oraz zdarzeń połączenia.

  • Urządzenie peryferyjne jest reaktywne. Może zażądać zmiany parametrów połączenia (np. wolniejszego interwału w celu oszczędzania energii), ale to urządzenie centralne decyduje, czy ją zaakceptować.

Role są niezależne od tego, kto hostuje bazę danych GATT, co stanowi osobną oś. Zgodnie z konwencją urządzenie peryferyjne jest jednocześnie serwerem GATT, a urządzenie centralne jest klientem GATT, jednak BLE pozwala, by usługi GATT hostowała dowolna ze stron. Kamery niemal zawsze trzymają się konwencji: peryferyjne + serwer dla zastosowań typu „kamera publikuje dane” oraz centralne + klient dla zastosowań typu „kamera odczytuje z sensora”.

11.5.3. Maksymalna jednostka transmisji (MTU)

Warstwa łącza domyślnie przenosi krótkie pakiety – 27 bajtów ładunku, z czego tylko 23 są dostępne dla GATT. To wystarcza na mały odczyt lub krótkie polecenie, ale jest niewiele wobec czegokolwiek wielobajtowego. Obie strony mogą wynegocjować zwiększenie tej wartości, aż do limitu obsługiwanego przez oprogramowanie układowe radia (zwykle kilkaset bajtów na nowoczesnych kontrolerach).

API aioble steruje negocjacją poprzez aioble.DeviceConnection.exchange_mtu(), a wynik staje się dostępny w atrybucie mtu. Większe MTU oznacza mniej pełnych wymian dla każdej wartości większej niż ~20 bajtów, niewielkim kosztem pamięci buforów.

11.5.4. Czas życia

Połączenie trwa do momentu, gdy zajdzie jedno z poniższych:

  • któraś ze stron wywoła disconnect(),

  • zadziała limit czasu nadzoru (urządzenie poza zasięgiem, radio wyłączone, awaria drugiej strony) lub

  • wystąpi jawna awaria warstwy łącza (niezgodność szyfrowania, odrzucenie parowania).

Gdy połączenie zostaje zerwane, każda operacja GATT zakolejkowana lub będąca w toku zgłasza wyjątek aioble.DeviceDisconnectedError, a wszelkie bloki async with connection, w których znajduje się aplikacja, kończą się w sposób uporządkowany. Urządzenie peryferyjne zwykle reaguje powrotem do aioble.advertise() i oczekiwaniem na kolejne urządzenie centralne; urządzenie centralne reaguje, ponawiając skanowanie albo zgłaszając rozłączenie do aplikacji.

Czas życia jest jednym z powodów, dla których aioble jest użyteczny. Synchroniczne API BLE musiałoby udostępniać wywołania zwrotne rozłączeń i maski zdarzeń; wersja oparta na asyncio zamienia rozłączenia na wyjątki wewnątrz korutyny, która oczekiwała na operację, a do tego właśnie służy sprzątanie async with.