11.7. Operace GATT¶
Charakteristika prostě jen sedí v databázi GATT jako pojmenovaná hodnota. Užitečnou ji činí malá, dobře definovaná sada operací, které na ní může klient provádět. Každá charakteristika deklaruje, které operace podporuje, jako bitovou masku vlastností – server, který nemá co vystavit, může publikovat hodnotu pouze pro čtení, řídicí registr může být pouze pro zápis, senzor streamující aktualizace by nastavil bit notify. Klient bitovou masku objeví během discovery a respektuje ji.
Pět operací je read, write, write without response, notify a indicate. Dělí se do dvou skupin – pull (klient se ptá) a push (server odesílá).
11.7.1. Pull: read a write¶
Tyto dvě jsou nejjednodušší a vypadají přesně jako volání funkcí.
Read. Klient požádá o aktuální hodnotu, server ji pošle zpět. Jeden obousměrný přenos, klient dostane bajty, které server pro danou charakteristiku nastavil, server se nedozví nic o tom, kdo četl.
Write. Klient pošle nové bajty, server je uloží (a volitelně nad novou hodnotou spustí aplikační logiku). Existují dvě varianty:
Write with response – server potvrdí a při nenulovém stavu vyvolá případnou aplikační chybu. Spolehlivé, jeden obousměrný přenos.
Write without response – server uloží bajty potichu; klient nedostane vůbec žádné potvrzení. Rychlejší (žádné čekání na potvrzení v rámci obousměrného přenosu) a užitečné pro streamování, za cenu zjištění chyb pouze prostřednictvím zpětného čtení postranním kanálem.
V aioble skrývá klientské API tuto volbu za jedinou metodu aioble.ClientCharacteristic.write() s klíčovým slovem response (True / False / None pro automatický výběr podle toho, co protějšek inzeruje).
11.7.2. Push: notify a indicate¶
Model pull je pro senzorová data nesprávný. Pás na měření srdečního tepu, který by telefon musel každou sekundu dotazovat, by spaloval baterii na stovkách zbytečných rádiových událostí; takový, který posílá hodnotu jen tehdy, když má nové měření, je vlastně celým smyslem BLE.
GATT to řeší pomocí operací iniciovaných serverem. Klient se k charakteristice přihlásí k odběru (subscribe); od toho okamžiku se při každé aktualizaci hodnoty serverem nová hodnota odešle přes linku klientovi. Dvě varianty:
Notify. Odešli a zapomeň. Server zařadí notifikaci do fronty, linková vrstva ji přenese během následující události připojení, klient ji přijme. Na úrovni GATT není žádné potvrzení; běžné opětovné vysílání linkové vrstvy řeší ztráty na straně rádia, ale aplikace nevidí žádné potvrzení, že hodnota byla zpracována.
Indicate. Server odešle notifikaci a před odesláním další čeká na potvrzení klienta na úrovni GATT. Jedna indikace v jednom okamžiku. Používá se, když server potřebuje vědět, že klient hodnotu skutečně viděl – charakteristika kritického alarmu, potvrzení konfigurace.
Pull (read) versus push (notify). U notifikací se klient přihlásí k odběru jednou a server posílá nové hodnoty pokaždé, když se změní.¶
Přihlášení k odběru probíhá zápisem do deskriptoru připojeného k charakteristice – Client Characteristic Configuration Descriptor (CCCD, 0x2902). Zápis 0x0001 povolí notifikace, 0x0002 povolí indikace, 0x0000 zakáže obojí. Metoda aioble.ClientCharacteristic.subscribe() provede zápis za vás pomocí klíčových příznaků notify=True a indicate=True.
Po přihlášení k odběru klient čeká na příchozí push pomocí notified() a indicated() – obojí jsou asynchronní korutiny, které se pozastaví, dokud nedorazí další push.
11.7.3. MTU řídí velikost užitečného zatížení¶
Každá operace je omezena vyjednaným MTU, na kterém se připojení ustálilo v okamžiku navázání linky. Výchozí MTU je 23 bajtů, což po hlavičce GATT ponechává 20 bajtů pro bajty hodnoty charakteristiky. Cokoli většího se musí buď vejít do většího MTU (vyjednaného nahoru pomocí aioble.DeviceConnection.exchange_mtu(), až 512 bajtů na kameře), nebo být rozděleno do více charakteristik či více notifikací.
Čtení a zápisy hodnot větších než MTU iniciované klientem zpracovávají na pozadí dlouhé procedury GATT (Read Long / Prepare-Write + Execute-Write); aioble je provádí transparentně, takže volání read() / write() s nadměrnou hodnotou jen stojí více obousměrných přenosů. Notifikace a indikace iniciované serverem se nefragmentují – jeden push je omezen MTU a aplikace rozdělí cokoli většího do více notifikací nebo z GATT zcela vystoupí.
Pro skutečně velké přenosy – zachycený snímek, dávku měření, blob s firmwarem – je správnou odpovědí obvykle z GATT zcela vystoupit a použít místo něj kanál L2CAP (viz Kanály L2CAP).
11.7.4. Obě strany na první pohled¶
Pět operací se na každé straně připojení projevuje odlišně:
Na serveru (periferie v běžném uspořádání):
aioble.Characteristic.read()– přečte aktuální lokální hodnotu z databáze GATT (serverová strana toho, „co by klient viděl“).aioble.Characteristic.write()– aktualizuje lokální hodnotu, volitelně odešle aktualizaci každému přihlášenému klientovi.aioble.Characteristic.notify()/indicate()– odešle push jednomu konkrétnímu klientovi.aioble.Characteristic.written()– čeká na další příchozí zápis od libovolného klienta.aioble.Characteristic.on_read()– callback vyvolaný synchronně, když klient čte, užitečný pro výpočet hodnoty na vyžádání.
Na klientovi (centrální zařízení v běžném uspořádání):
aioble.ClientCharacteristic.read()– požádá server o aktuální hodnotu.aioble.ClientCharacteristic.write()– odešle novou hodnotu, s odpovědí nebo bez ní.aioble.ClientCharacteristic.subscribe()– povolí / zakáže notifikace a indikace.aioble.ClientCharacteristic.notified()/indicated()– čeká na další push.