11.7. GATT-operationer

En karakteristik ligger bara i GATT-databasen som ett namngivet värde. Det som gör den användbar är den lilla, väldefinierade uppsättning operationer som en klient kan köra på den. Varje karakteristik deklarerar vilka operationer den stöder som en property bitmask – en server som inte har något att exponera kan publicera ett skrivskyddat värde, ett styrregister kan vara endast skrivbart, en sensor som strömmar uppdateringar skulle sätta notify-biten. Klienten upptäcker bitmasken under upptäckten och respekterar den.

De fem operationerna är read, write, write without response, notify och indicate. De delas in i två grupper – pull (klienten frågar) och push (servern skickar).

11.7.1. Pull: read och write

Dessa två är de enklaste och ser ut precis som funktionsanrop.

  • Read. Klienten ber om det aktuella värdet, servern skickar tillbaka det. En tur-och-retur, klienten får de byte som servern har satt för den karakteristiken, servern får inget om vem som läste.

  • Write. Klienten skickar nya byte, servern lagrar dem (och kör eventuellt applikationslogik på det nya värdet). Det finns två varianter:

    • Write with response – servern bekräftar och utlöser eventuellt applikationsfel vid en status skild från noll. Tillförlitlig, en tur-och-retur.

    • Write without response – servern lagrar byten tyst; klienten får ingen bekräftelse alls. Snabbare (ingen tur-och-retur som väntar på bekräftelsen) och användbar för strömning, till priset av att man får reda på fel endast via en sidokanals-återläsning.

I aioble döljer klientsidans API valet bakom en enda aioble.ClientCharacteristic.write()-metod med ett response-nyckelord (True / False / None för att automatiskt välja baserat på vad motparten annonserar).

11.7.2. Push: notify och indicate

Pull-modellen är fel för sensordata. En pulsband som telefonen måste fråga varje sekund skulle bränna batteri på hundra onödiga radiohändelser; ett som skickar ett värde endast när det har en ny avläsning är själva poängen med BLE från första början.

GATT löser detta med serverinitierade operationer. Klienten prenumererar på en karakteristik; från den punkten skickas det nya värdet över länken till klienten varje gång servern uppdaterar värdet. Två varianter:

  • Notify. Skicka-och-glöm. Servern köar en notifiering, länklagret överför den under nästa anslutningshändelse, klienten tar emot den. Det finns ingen bekräftelse på GATT-nivå; länklagrets normala återsändning hanterar förlust på radiosidan, men applikationen ser ingen bekräftelse på att värdet bearbetades.

  • Indicate. Servern skickar en notifiering och väntar på klientens bekräftelse på GATT-nivå innan den skickar nästa. En indikering åt gången. Används när servern behöver veta att klienten faktiskt såg värdet – en karakteristik för kritiska larm, en bekräftelse av konfiguration.

Två diagram sida vid sida av en server och en klient. Till vänster skickar klienten "read", servern svarar med värdet. Tre läsningar i rad, varje par av pilar. Till höger skickar klienten en enda "subscribe", sedan skickar servern tre "notify"-paket vid de tidpunkter den väljer, utan någon klientförfrågan emellan.

Pull (read) jämfört med push (notify). Med notifieringar prenumererar klienten en gång och servern skickar nya värden närhelst de ändras.

Prenumeration sker genom att skriva till en deskriptor som är kopplad till karakteristiken – Client Characteristic Configuration Descriptor (CCCD, 0x2902). Att skriva 0x0001 aktiverar notifieringar, 0x0002 aktiverar indikeringar, 0x0000 inaktiverar båda. Metoden aioble.ClientCharacteristic.subscribe() utför skrivningen åt dig, med nyckelordsflaggorna notify=True och indicate=True.

När prenumerationen är aktiv väntar klienten på inkommande pushar med notified() och indicated() – båda asynkrona korutiner som pausar tills nästa push anländer.

11.7.3. MTU:n styr nyttolastens storlek

Varje operation begränsas av den förhandlade MTU som anslutningen fastnade vid när länken kom upp. Standard-MTU:n är 23 byte, vilket lämnar 20 byte för karakteristikens värdebyte efter GATT-huvudet. Allt som är större än så måste antingen rymmas inom en större MTU (förhandlad upp via aioble.DeviceConnection.exchange_mtu(), upp till 512 byte på kameran) eller delas upp i flera karakteristiker eller flera notifieringar.

Klientinitierade läsningar och skrivningar av värden större än MTU:n hanteras av GATT:s long-procedurer i bakgrunden (Read Long / Prepare-Write + Execute-Write); aioble kör dessa transparent, så att anropa read() / write() med ett för stort värde kostar bara fler tur-och-retur-resor. Serverinitierade notifieringar och indikeringar fragmenteras inte – en push begränsas av MTU:n, och applikationen delar upp allt större i flera notifieringar eller lämnar GATT helt.

För verkligt stora överföringar – en infångad bildruta, en batch av mätningar, en blob av fast programvara – är det rätta svaret oftast att lämna GATT helt och i stället använda en L2CAP-kanal (se L2CAP-kanaler).

11.7.4. De två sidorna i korthet

De fem operationerna exponeras olika på var sida av anslutningen: