11.6. Tjänster och karakteristika¶
När GAP väl har fått två enheter att upprätta en öppen anslutning måste lagret ovanför – Generic Attribute Profile, GATT – ge mening åt de byte som flödar genom anslutningen. BLE:s val här är ovanligt. Där TCP exponerar en rå byteström och låter applikationen själv hitta på sin egen ramning, exponerar GATT en liten nyckel/värde-databas som den ena sidan är värd för och den andra läser från, skriver till eller prenumererar på.
Den databasen är vad applikationsdesigners ägnar större delen av sin BLE-tid åt att fundera på. Vad kameran publicerar till en telefon, vad den bevakar på en fjärrsensor, hur ett Bluetooth-tangentbord talar om för sin värd vilken tangent som trycktes ned – allt är karakteristikvärden i någon GATT-databas någonstans.
11.6.1. Två rollaxlar, inte en¶
En vanlig källa till förvirring: peripheral / central och server / client är två oberoende axlar, inte synonymer.
Peripheral och central är GAP-roller, fastställda under anslutningen. Den perifera enheten annonserar och blir ansluten till; den centrala enheten skannar och initierar anslutningen. Detta avgörs i samma stund som länken upprättas och ändras inte.
Server och client är GATT-roller, fastställda per karakteristikoperation. Servern är värd för karakteristikan; klienten läser, skriver eller prenumererar på den.
De två axlarna är frikopplade i specifikationen. En perifer enhet är vanligtvis servern (ett pulsband publicerar sina mätvärden) och en central enhet är vanligtvis klienten (en telefon läser dem), men BLE tillåter vilken kombination som helst – en perifer enhet kan upptäcka en karakteristik på den centrala enhet den just blivit ansluten till, eller en enda anslutning kan vara värd för tjänster på båda sidor samtidigt.
De flesta kameraapplikationer håller sig till den konventionella parningen (peripheral + server, eller central + client), så resten av detta avsnitt behandlar dem som en enda axel när det är det konventionella fallet som beskrivs. När distinktionen spelar roll skrivs båda termerna ut explicit.
11.6.2. Inuti databasen¶
En GATT-databas är ett träd. Löven bär de faktiska byten. Grenarna grupperar relaterade löv i mänskligt meningsfulla enheter.
En GATT-databas. Tjänster grupperar karakteristika; karakteristika bär applikationens byte; deskriptorer bär metadata om karakteristikan.¶
Det finns tre sorters noder:
En tjänst är en logisk grupp av relaterade värden. Bluetooth SIG publicerar standarddefinitioner för tjänster för vanliga användningsfall – Battery Service för batterinivå, Environmental Sensing för temperatur / luftfuktighet / tryck, Heart Rate för pulsmätare – så att en generisk app på en telefon kan känna igen en tjänst den aldrig sett förut. En applikation är också fri att definiera sina egna tjänster för saker som SIG inte har standardiserat.
En karakteristik är ett namngivet värde inuti en tjänst. Battery-tjänsten har en enda karakteristik – Battery Level, en enbytes procentandel. Environmental Sensing har separata karakteristika för temperatur, luftfuktighet, tryck och så vidare. En karakteristik är enheten för GATT-operationer – du läser en karakteristik, du skriver en karakteristik, du prenumererar på en karakteristik.
En deskriptor är metadata kopplad till en karakteristik. Vissa deskriptorer är standardiserade – Client Characteristic Configuration Descriptor (CCCD) är den mest kända, eftersom det är genom att skriva till den som en klient talar om för servern ”skicka mig notifieringar på den här karakteristikan”. Andra är användardefinierade och bär saker som presentationsformat eller utökade egenskaper.
En GATT-server (vanligtvis den perifera enheten) deklarerar sin databas en gång vid uppstart och databasen ändras inte under körning. En GATT-klient (vanligtvis den centrala enheten) upptäcker vad som finns i databasen efter anslutning – den vandrar genom trädet, läser UUID:erna för de tjänster den hittar och sedan karakteristikorna inuti varje.
11.6.3. UUID:er¶
Varje tjänst, karakteristik och deskriptor har ett UUID (Universally Unique IDentifier) som identifierar vilken sorts sak det är. UUID:er finns i tre bredder:
16-bitars. Reserverat för standarder definierade av Bluetooth SIG. Battery Service är
0x180F. Battery Level (en karakteristik) är0x2A19. Den fullständiga listan publiceras på Bluetooth SIG:s webbplats för tilldelade nummer på https://www.bluetooth.com/specifications/assigned-numbers/.32-bitars. En sällan använd mellanväg.
128-bitars. Det alla andra använder – en tillverkare eller applikation genererar ett slumpmässigt och använder det för sin anpassade tjänst eller karakteristik. Kameror som definierar sitt eget protokoll hör hemma här.
Klassen bluetooth.UUID accepterar någon av de tre bredderna:
import bluetooth
BATTERY_SERVICE = bluetooth.UUID(0x180F)
CUSTOM_SERVICE = bluetooth.UUID("12345678-1234-5678-9abc-def012345678")
Ett 16-bitars UUID kodas in i en liten annonseringsnyttolast, vilket är ett skäl till att standardtjänster är att föredra när en sådan finns – ett pulsband som annonserar 0x180D (Heart Rate) kostar två byte; ett anpassat UUID kostar sexton. För applikationer som inte behöver standardinteroperabilitet är ett genererat 128-bitars UUID rätt svar.
11.6.4. Vad de SIG-standardiserade tjänsterna ger dig¶
Argumentet för att använda en standardtjänst är enkelt: befintliga appar vet redan hur de ska prata med den. En enhet som annonserar Heart Rate-tjänsten (0x180D) och exponerar karakteristikan Heart Rate Measurement (0x2A37) fungerar med varenda träningsapp på planeten utan att någon behöver skriva ny kod. En enhet som återimplementerar samma data med anpassade UUID:er behöver sin egen följeslagarapp och sitt eget protokolldokument.
Standarderna kommer dock med en kostnad. Bytelayouterna inuti varje karakteristik är specificerade – SIG bestämde att Heart Rate Measurement är ett enbytes flaggfält följt av antingen ett 8-bitars eller 16-bitars pulsvärde, valfritt följt av R-R-intervall – och en enhet som följer standarden måste hålla sig till de layouterna. Anpassade tjänster är fria från den begränsningen.
Det pragmatiska svaret för kameror: använd standardtjänsten när det finns en för den sorts data du har (Battery Service, Environmental Sensing), och definiera en anpassad med ett 128-bitars UUID för allt som är specifikt för din applikation.
11.6.5. Objekt på serversidan och klientsidan¶
För samma konceptuella byggstenar (tjänst, karakteristik, deskriptor) exponerar varje GATT-bibliotek två parallella uppsättningar objekt:
Serversideobjekt – vad den perifera enheten deklarerar att den är värd för. I
aioble:aioble.Service,aioble.Characteristic,aioble.Descriptor. Dessa konstrueras innan annonseringen börjar och registreras medaioble.register_services().Klientsideobjekt – vad den centrala enheten upptäcker på motparten efter anslutning. I
aioble:aioble.ClientService,aioble.ClientCharacteristic,aioble.ClientDescriptor. Dessa vandras igenom viaaioble.DeviceConnection.service()/services()efter anslutning.