11.6. Services en kenmerken

Zodra GAP twee apparaten in een open verbinding heeft gebracht, moet de laag erboven – het Generic Attribute Profile, GATT – betekenis geven aan de bytes die door die verbinding stromen. De keuze van BLE is hier ongebruikelijk. Waar TCP een ruwe bytestroom blootstelt en het aan de applicatie overlaat om zijn eigen framing te bedenken, stelt GATT een kleine sleutel/waarde-database bloot die de ene kant host en de andere leest, schrijft of waarop deze zich abonneert.

Die database is waar applicatieontwerpers het grootste deel van hun BLE-tijd over nadenken. Wat de camera naar een telefoon publiceert, wat het op een sensor op afstand in de gaten houdt, hoe een Bluetooth-toetsenbord zijn host vertelt welke toets is ingedrukt – het zijn allemaal kenmerkwaarden in een of andere GATT-database ergens.

11.6.1. Twee rol-assen, niet één

Een veelvoorkomende bron van verwarring: peripheral / central en server / client zijn twee onafhankelijke assen, geen synoniemen.

  • Peripheral en central zijn GAP-rollen, ingesteld tijdens de verbinding. De peripheral adverteert en wordt verbonden; de central scant en initieert de verbinding. Dit ligt vast op het moment dat de verbinding tot stand komt en verandert niet meer.

  • Server en client zijn GATT-rollen, ingesteld per kenmerkbewerking. De server host het kenmerk; de client leest, schrijft of abonneert zich erop.

De twee assen zijn door de specificatie ontkoppeld. Een peripheral is meestal de server (een hartslagband publiceert zijn metingen) en een central is meestal de client (een telefoon leest ze), maar BLE staat elke combinatie toe – een peripheral kan een kenmerk ontdekken op de central waarmee deze net is verbonden, of één enkele verbinding kan services aan beide kanten tegelijk hosten.

De meeste cameratoepassingen houden zich aan de conventionele combinatie (peripheral + server, of central + client), dus de rest van dit hoofdstuk behandelt ze als één as wanneer het conventionele geval wordt beschreven. Wanneer het onderscheid van belang is, worden beide termen expliciet uitgeschreven.

11.6.2. Binnen de database

Een GATT-database is een boom. De bladeren dragen de daadwerkelijke bytes. De takken groeperen verwante bladeren in voor mensen betekenisvolle eenheden.

Een boom met een bovenste knooppunt met het label "GATT database". Daaronder drie Service-knooppunten met de labels "Generic Access (0x1800)", "Battery (0x180F)" en "Environmental Sensing (0x181A)". Elke Service heeft onderliggende Characteristic-knooppunten; de Battery-service heeft "Battery Level (0x2A19)" met een onderliggende Descriptor "CCCD". De Environmental Sensing-service heeft "Temperature (0x2A6E)" en "Humidity (0x2A6F)".

Een GATT-database. Services groeperen kenmerken; kenmerken dragen de bytes van de applicatie; descriptors dragen metadata over het kenmerk.

Er zijn drie soorten knooppunten:

  • Een service is een logische groep van verwante waarden. De Bluetooth SIG publiceert standaard servicedefinities voor veelvoorkomende toepassingen – Battery Service voor het batterijniveau, Environmental Sensing voor temperatuur / luchtvochtigheid / druk, Heart Rate voor hartslagmeters – zodat een generieke app op een telefoon een service kan herkennen die deze nog nooit eerder heeft gezien. Een applicatie staat het ook vrij om zijn eigen services te definiëren voor zaken die de SIG niet heeft gestandaardiseerd.

  • Een kenmerk is één benoemde waarde binnen een service. De Battery-service heeft één enkel kenmerk – Battery Level, een percentage van één byte. Environmental Sensing heeft afzonderlijke kenmerken voor temperatuur, luchtvochtigheid, druk, enzovoort. Een kenmerk is de eenheid van GATT-bewerkingen – je leest een kenmerk, je schrijft een kenmerk, je abonneert je op een kenmerk.

  • Een descriptor is metadata die aan een kenmerk is gekoppeld. Sommige descriptors zijn gestandaardiseerd – de Client Characteristic Configuration Descriptor (CCCD) is de beroemde, omdat het schrijven ernaar de manier is waarop een client de server vertelt “stuur me notificaties over dit kenmerk”. Andere zijn door de gebruiker gedefinieerd en dragen zaken als presentatieformaat of uitgebreide eigenschappen.

Een GATT-server (doorgaans de peripheral) declareert zijn database eenmalig bij het opstarten en de database verandert niet tijdens het draaien. Een GATT-client (doorgaans de central) ontdekt wat zich in de database bevindt na het verbinden – door de boom te doorlopen, de UUID’s te lezen van de services die deze vindt, en vervolgens de kenmerken binnen elke service.

11.6.3. UUID’s

Elke service, elk kenmerk en elke descriptor heeft een UUID (Universally Unique IDentifier) die identificeert wat voor soort ding het is. UUID’s komen in drie breedtes:

  • 16-bit. Gereserveerd voor standaarden gedefinieerd door de Bluetooth SIG. Battery Service is 0x180F. Battery Level (een kenmerk) is 0x2A19. De volledige lijst is gepubliceerd op de assigned-numbers-site van de Bluetooth SIG op https://www.bluetooth.com/specifications/assigned-numbers/.

  • 32-bit. Een zelden gebruikte tussenweg.

  • 128-bit. Wat iedereen anders gebruikt – een leverancier of applicatie genereert er willekeurig een en gebruikt deze voor zijn aangepaste service of kenmerk. Camera’s die hun eigen protocol definiëren, vallen hieronder.

De klasse bluetooth.UUID accepteert elk van de drie breedtes:

import bluetooth

BATTERY_SERVICE = bluetooth.UUID(0x180F)
CUSTOM_SERVICE = bluetooth.UUID("12345678-1234-5678-9abc-def012345678")

Een 16-bit-UUID codeert in een kleine advertentie-payload, wat een reden is waarom standaardservices de voorkeur verdienen wanneer er een bestaat – een hartslagband die 0x180D (Heart Rate) adverteert kost twee bytes; een aangepaste UUID kost er zestien. Voor applicaties die geen standaard-interoperabiliteit nodig hebben, is een gegenereerde 128-bit-UUID het juiste antwoord.

11.6.4. Wat de door de SIG gestandaardiseerde services je opleveren

De reden om een standaardservice te gebruiken is eenvoudig: bestaande apps weten al hoe ze ermee moeten communiceren. Een apparaat dat de Heart Rate-service (0x180D) adverteert en het kenmerk Heart Rate Measurement (0x2A37) blootstelt, werkt met elke fitness-app ter wereld zonder dat iemand nieuwe code hoeft te schrijven. Een apparaat dat dezelfde gegevens opnieuw implementeert met aangepaste UUID’s heeft zijn eigen begeleidende app en zijn eigen protocoldocument nodig.

De standaarden hebben wel een prijs. De byte-indelingen binnen elk kenmerk zijn voorgeschreven – de SIG besloot dat Heart Rate Measurement een veld met vlaggen van één byte is, gevolgd door een 8-bit- of 16-bit-hartslagwaarde, optioneel gevolgd door R-R-intervallen – en een conform apparaat moet die indelingen volgen. Aangepaste services zijn vrij van die beperking.

Het pragmatische antwoord voor camera’s: gebruik de standaardservice wanneer er een bestaat voor het soort gegevens dat je hebt (Battery Service, Environmental Sensing), en definieer een aangepaste service met een 128-bit-UUID voor alles wat specifiek is voor je applicatie.

11.6.5. Objecten aan de server- en clientzijde

Voor dezelfde conceptuele bouwstenen (service, kenmerk, descriptor) stelt elke GATT-bibliotheek twee parallelle sets objecten bloot: