11.14. Podsumowanie¶
Przeszliśmy przez Bluetooth Low Energy od warstwy radiowej aż po API Pythona używane do jego obsługi:
Motywacja – BLE to odpowiedź, gdy kamera chce porozmawiać z czymś znajdującym się w pobliżu bez żadnej infrastruktury pomiędzy nimi. Telefon w tym samym pomieszczeniu, urządzenie noszone na nadgarstku, beacon na ścianie. Krótki zasięg, brak sieci do dołączenia, niemal zerowy pobór mocy.
Radio – 2,4 GHz, 40 kanałów: trzy do rozgłaszania, 37 dla danych połączenia, przeskakiwanych według pseudolosowej sekwencji z adaptacyjnym unikaniem zaszumionych kanałów. Krótkie pakiety, radia przeważnie uśpione.
Warstwa łącza – ramkowanie pakietów, adresowanie, harmonogramowanie połączeń, retransmisja i szyfrowanie warstwy łącza. Nic z tego nie jest konfigurowane z poziomu Pythona; wszystko to przejawia się w parametrach połączenia i w MTU.
Generic Access Profile (GAP) – wykrywanie i zarządzanie połączeniami. Cztery role: peripheral i broadcaster (rozgłaszają), central i observer (skanują). Ładunki rozgłoszeniowe niosą lokalną nazwę, UUID-y usług, appearance oraz dane specyficzne dla producenta – 31 bajtów plus opcjonalna 31-bajtowa odpowiedź na skan. Interwał połączenia, opóźnienie urządzenia peryferyjnego oraz limit czasu nadzoru określają, jak zachowuje się otwarte połączenie.
Generic Attribute Profile (GATT) – drzewo usług, z których każda przechowuje charakterystyki, a każda z nich opcjonalnie deskryptory, identyfikowane przez UUID-y (16-bitowe dla standardów Bluetooth-SIG, 128-bitowe dla niestandardowych). Pięć operacji: read i write (pobieranie, inicjowane przez klienta), notify i indicate (wypychanie, inicjowane przez serwer, subskrybowane przez Client Characteristic Configuration Descriptor). Rozmiar ładunku jest ograniczony przez wynegocjowane MTU.
API Pythona –
aioblezamienia każdy wzorzec BLE w korutynę asyncio. Urządzenie peryferyjne toaioble.advertise()w pętli po połączeniach, z obiektamiService/Characteristicbudowanymi raz i zatwierdzanymi przezaioble.register_services(). Urządzenie centralne toaioble.scan(), by znaleźć peera,connect(), by otworzyć łącze,service()icharacteristic(), by przejść po zdalnym drzewie GATT, a następnieread()/write()/subscribe()/notified()do faktycznych danych. Rozłączenia ujawniają się jakoaioble.DeviceDisconnectedErrorwewnątrz korutyny, która oczekiwała.Kanały L2CAP – furtka awaryjna dla masowych strumieni bajtów, które nie pasują do modelu klucz/wartość GATT.
aioble.DeviceConnection.l2cap_accept()/l2cap_connect()otwierają kanał per aplikacja na bazie połączenia GAP, z wysyłaniem / odbieraniem sterowanym przepływem kredytowym i większym MTU, niż GATT jest w stanie przenieść.Parowanie i szyfrowanie – łącza BLE są domyślnie publiczne.
aioble.DeviceConnection.pair()inicjuje wymianę kluczy, która tworzy zaszyfrowane łącze;bond=True(wartość domyślna) utrwala klucze, dzięki czemu kolejne połączenia pomijają uzgadnianie. Bezmitm=Truei użytecznej zdolności IO szyfrowanie chroni przed pasywnymi podsłuchującymi, ale nie przed aktywnym przekierowaniem podczas pierwotnego parowania.
To wystarczy, aby pisać aplikacje kamery, które publikują status jako urządzenie peryferyjne, odczytują dane z sensorów jako urządzenie centralne, wypychają wartości na żywo do telefonu przez BLE, zabezpieczają łącze krokiem parowania i wiązania oraz – w rzadkim przypadku transferu masowego – schodzą z GATT do kanału L2CAP.
11.14.1. Rozwiązywanie problemów¶
Awarie BLE to przeważnie niezgodności między tym, czego oczekują obie strony, a inspektor po stronie telefonu to najszybszy sposób, by zobaczyć, czyje oczekiwania są błędne. Standardowym narzędziem jest nRF Connect for Mobile (Nordic Semiconductor, darmowe na Androida i iOS): skanuje, łączy się, przechodzi po bazie danych GATT, odczytuje i zapisuje charakterystyki oraz subskrybuje powiadomienia – dzięki czemu zachowanie po stronie kamery można testować w izolacji, bez pisania jakiejkolwiek aplikacji towarzyszącej.
Typowe tryby awarii:
„Moje urządzenie pojawia się w skanerze, ale nie chce się połączyć.” Najczęściej pakiet rozgłoszeniowy ma
connectable=False(tryb broadcaster) albo poprzednie połączenie jest nadal otwarte, a kamera jest już zaaioble.advertise(). Dodaj instrukcje print wokół wywołania advertise, aby to potwierdzić.„exchange_mtu(512) wykonało się, ale moje powiadomienia nadal są ograniczone do 20 bajtów.” Wynegocjowane MTU to
min(local, peer)– telefon lub biblioteka centralna mogły nie zażądać większego MTU po swojej stronie, w którym to przypadku połączenie pozostaje na 23. Sprawdźmtupo zwróceniu przezexchange_mtu(). Zwróć też uwagę, żeexchange_mtu()działa tylko raz na połączenie; wywołaj je przed pierwszą dużą operacją.„Parowanie kończy się ogólnym błędem.” Dwaj zwykli winowajcy: niezgodność zdolności IO (żądanie
mitm=Truena kamerze deklarującejio=3/ brak wejścia, brak wyjścia – nie ma sposobu na potwierdzenie kodu numerycznego, więc silnik parowania rezygnuje) oraz drastycznie błędny czas zegara ściennego na kamerze, gdy peer go wymaga. Ustaw zegar za pomocąntptime.settime()przed pierwszą próbą parowania.„Powiadomienia nigdy nie docierają do klienta.” Dwie rzeczy do sprawdzenia, po kolei: (a) czy charakterystyka została zadeklarowana z
notify=True? – bit właściwości musi być ustawiony po stronie serwera; (b) czy klient wywołałsubscribe()? – bez zapisania Client Characteristic Configuration Descriptor (CCCD) serwerowi przekazywana jest informacja, że żaden klient nie chce powiadomień, i po cichu je odrzuca.„Rozgłaszana nazwa jest obcięta lub jej brak.” Ładunek rozgłoszeniowy ma 31 bajtów, a pola flags + service-UUID + appearance zabierają po kilka bajtów z góry. Długie
name=plus kilka UUID-ów usług powoduje przepełnienie. Albo skróć nazwę, albo użyj aktywnego skanowania, aby odpowiedź na skan (kolejne 31 bajtów) przeniosła nadmiar. nRF Connect pokazuje obie połówki osobno, co sprawia, że podział jest oczywisty.„Połączenie L2CAP natychmiast zgłasza wyjątek.” Zwykle niezgodność PSM – obie strony muszą uzgodnić ten sam numer PSM poza pasmem.
L2CAPConnectionErrorniesie kod statusu Bluetooth jako swój pierwszy argument; status2(„PSM not supported”) to wyraźna wskazówka.„Związane połączenia nadal wyzwalają pełne uzgadnianie parowania przy każdym ponownym połączeniu.”
aioble.security.load_secrets()nie zostało wywołane przy starcie. Bez niego zapisane klucze są w pamięci flash, ale nigdy nie są wczytywane do pamięci, więc tożsamość peera jest nieznana i parowanie za każdym razem przebiega od zera.
Gdy wszystko inne zawiedzie, niskopoziomowy moduł bluetooth udostępnia wywołanie zwrotne IRQ, które uruchamia się dla każdego zdarzenia bazowego; krótkie zasubskrybowanie go i wypisywanie zdarzeń to odpowiednik śledzenia Wireshark po stronie kamery.
11.14.2. Korzystanie z tej dokumentacji referencyjnej w przyszłości¶
Traktuj rozdziały o Bluetooth jako materiał referencyjny; powracanie po dokładny układ ładunku rozgłoszeniowego urządzenia peryferyjnego lub po przebieg skanowania i subskrybowania po stronie urządzenia centralnego jest zamierzonym sposobem użycia. Strony referencyjne aioble — Asynchroniczne BLE i bluetooth — niskopoziomowa obsługa Bluetooth wymieniają każdą metodę, flagę i stałą w jednym miejscu, gdy pytanie brzmi po prostu „jaka jest dokładna nazwa tego wywołania”.