11.7. Operacje GATT¶
Charakterystyka po prostu znajduje się w bazie danych GATT jako nazwana wartość. Użyteczną czyni ją niewielki, dobrze zdefiniowany zestaw operacji, jakie może na niej wykonać klient. Każda charakterystyka deklaruje, które operacje obsługuje, w postaci maski bitowej właściwości (property bitmask) – serwer, który nie ma nic do udostępnienia, może opublikować wartość tylko do odczytu, rejestr sterujący może być tylko do zapisu, a sensor strumieniujący aktualizacje ustawi bit powiadomień. Klient odkrywa tę maskę bitową podczas wykrywania i jej przestrzega.
Pięć operacji to odczyt (read), zapis (write), zapis bez odpowiedzi (write without response), powiadomienie (notify) i wskazanie (indicate). Dzielą się na dwie grupy – pobieranie (klient pyta) i wypychanie (serwer wysyła).
11.7.1. Pobieranie: odczyt i zapis¶
Te dwie operacje są najprostsze i wyglądają dokładnie jak wywołania funkcji.
Odczyt. Klient prosi o bieżącą wartość, serwer ją odsyła. Jedna pełna wymiana, klient otrzymuje bajty, jakie serwer ustawił dla tej charakterystyki, a serwer nie dowiaduje się niczego o tym, kto dokonał odczytu.
Zapis. Klient wysyła nowe bajty, serwer je przechowuje (i opcjonalnie uruchamia logikę aplikacji na nowej wartości). Istnieją dwie odmiany:
Zapis z odpowiedzią – serwer potwierdza, zgłaszając ewentualny błąd aplikacji przy niezerowym statusie. Niezawodny, jedna pełna wymiana.
Zapis bez odpowiedzi – serwer po cichu przechowuje bajty; klient nie otrzymuje żadnego potwierdzenia. Szybszy (brak pełnej wymiany oczekującej na potwierdzenie) i przydatny przy strumieniowaniu, kosztem tego, że o błędach można się dowiedzieć tylko poprzez odczyt zwrotny innym kanałem.
W aioble API po stronie klienta ukrywa ten wybór za jedną metodą aioble.ClientCharacteristic.write() ze słowem kluczowym response (True / False / None w celu automatycznego wyboru na podstawie tego, co rozgłasza druga strona).
11.7.2. Wypychanie: powiadomienie i wskazanie¶
Model pobierania jest niewłaściwy dla danych z sensorów. Pasek do pomiaru tętna, który telefon musiałby odpytywać co sekundę, marnowałby baterię na sto niepotrzebnych zdarzeń radiowych; pasek, który wypycha wartość tylko wtedy, gdy ma nowy odczyt, jest tym, o co w BLE chodzi w pierwszej kolejności.
GATT rozwiązuje to za pomocą operacji inicjowanych przez serwer. Klient subskrybuje charakterystykę; od tego momentu za każdym razem, gdy serwer aktualizuje wartość, nowa wartość jest wypychana przez łącze do klienta. Dwie odmiany:
Powiadomienie. Wyślij i zapomnij. Serwer kolejkuje powiadomienie, warstwa łącza transmituje je podczas kolejnego zdarzenia połączenia, a klient je odbiera. Na poziomie GATT nie ma potwierdzenia; normalna retransmisja warstwy łącza obsługuje utratę pakietów po stronie radia, ale aplikacja nie widzi żadnego potwierdzenia, że wartość została przetworzona.
Wskazanie. Serwer wysyła powiadomienie oraz czeka na potwierdzenie klienta na poziomie GATT, zanim wyśle następne. Jedno wskazanie naraz. Stosowane, gdy serwer musi wiedzieć, że klient faktycznie zobaczył wartość – charakterystyka alarmu krytycznego, potwierdzenie konfiguracji.
Pobieranie (read) kontra wypychanie (notify). Przy powiadomieniach klient subskrybuje raz, a serwer wypycha nowe wartości za każdym razem, gdy ulegają zmianie.¶
Subskrybowanie odbywa się przez zapis do deskryptora dołączonego do charakterystyki – Client Characteristic Configuration Descriptor (CCCD, 0x2902). Zapisanie 0x0001 włącza powiadomienia, 0x0002 włącza wskazania, a 0x0000 wyłącza oba. Metoda aioble.ClientCharacteristic.subscribe() wykonuje ten zapis za Ciebie, korzystając z flag słów kluczowych notify=True i indicate=True.
Po subskrypcji klient oczekuje na przychodzące wypchnięcia za pomocą notified() oraz indicated() – obie to korutyny asynchroniczne, które wstrzymują się do nadejścia kolejnego wypchnięcia.
11.7.3. MTU rządzi rozmiarem ładunku¶
Każda operacja jest ograniczona przez wynegocjowane MTU, na którym połączenie ustaliło się w momencie nawiązania łącza. Domyślne MTU to 23 bajty, co po nagłówku GATT pozostawia 20 bajtów na bajty wartości charakterystyki. Cokolwiek większego musi albo zmieścić się w większym MTU (wynegocjowanym w górę przez aioble.DeviceConnection.exchange_mtu(), do 512 bajtów na kamerze), albo zostać podzielone na wiele charakterystyk lub wiele powiadomień.
Inicjowane przez klienta odczyty i zapisy wartości większych niż MTU są obsługiwane w tle przez długie procedury GATT (Read Long / Prepare-Write + Execute-Write); aioble uruchamia je w sposób przezroczysty, więc wywołanie read() / write() z nadmiarową wartością kosztuje jedynie więcej pełnych wymian. Inicjowane przez serwer powiadomienia i wskazania nie są fragmentowane – jedno wypchnięcie jest ograniczone przez MTU, a aplikacja sama dzieli cokolwiek większego na wiele powiadomień albo całkowicie rezygnuje z GATT.
W przypadku naprawdę dużych transferów – przechwyconej ramki, partii pomiarów, blobu oprogramowania układowego – właściwym rozwiązaniem jest zwykle całkowite zejście z GATT i użycie zamiast tego kanału L2CAP (zobacz Kanały L2CAP).
11.7.4. Obie strony w skrócie¶
Pięć operacji udostępnia się inaczej po każdej ze stron połączenia:
Po stronie serwera (urządzenie peryferyjne w typowym układzie):
aioble.Characteristic.read()– odczyt bieżącej lokalnej wartości z bazy danych GATT (strona serwera odpowiadająca na pytanie „co zobaczyłby klient”).aioble.Characteristic.write()– aktualizacja lokalnej wartości, opcjonalnie wypychając aktualizację do każdego subskrybującego klienta.aioble.Characteristic.notify()/indicate()– wysłanie wypchnięcia do jednego konkretnego klienta.aioble.Characteristic.written()– oczekiwanie na kolejny przychodzący zapis od dowolnego klienta.aioble.Characteristic.on_read()– wywołanie zwrotne uruchamiane synchronicznie, gdy klient dokonuje odczytu, przydatne do obliczania wartości na żądanie.
Po stronie klienta (urządzenie centralne w typowym układzie):
aioble.ClientCharacteristic.read()– prośba do serwera o bieżącą wartość.aioble.ClientCharacteristic.write()– wysłanie nowej wartości, z odpowiedzią lub bez.aioble.ClientCharacteristic.subscribe()– włączenie / wyłączenie powiadomień i wskazań.aioble.ClientCharacteristic.notified()/indicated()– oczekiwanie na kolejne wypchnięcie.