11.6. Services und Characteristics

Sobald GAP zwei Geräte in eine offene Verbindung gebracht hat, muss die darüber liegende Schicht – das Generic Attribute Profile, GATT – den durch diese Verbindung fließenden Bytes eine Bedeutung geben. BLE trifft hier eine ungewöhnliche Wahl. Während TCP einen rohen Bytestrom bereitstellt und es der Anwendung überlässt, ihre eigene Rahmung zu erfinden, stellt GATT eine kleine Schlüssel/Wert-Datenbank bereit, die die eine Seite hostet und die andere liest, beschreibt oder abonniert.

Über diese Datenbank denken Anwendungsentwickler die meiste ihrer BLE-Zeit nach. Was die Kamera an ein Telefon veröffentlicht, was sie an einem entfernten Sensor beobachtet, wie eine Bluetooth-Tastatur ihrem Host mitteilt, welche Taste gedrückt wurde – all das sind Characteristic-Werte in irgendeiner GATT-Datenbank.

11.6.1. Zwei Rollenachsen, nicht eine

Eine häufige Verwirrungsquelle: Peripheral / Central und Server / Client sind zwei unabhängige Achsen, keine Synonyme.

  • Peripheral und Central sind GAP-Rollen, die während der Verbindung festgelegt werden. Das Peripheral sendet Werbung aus und wird verbunden; das Central scannt und initiiert die Verbindung. Dies wird in dem Moment festgelegt, in dem die Verbindung zustande kommt, und ändert sich nicht.

  • Server und Client sind GATT-Rollen, die pro Characteristic-Operation festgelegt werden. Der Server hostet die Characteristic; der Client liest, beschreibt oder abonniert sie.

Die beiden Achsen sind durch die Spezifikation entkoppelt. Ein Peripheral ist meist der Server (ein Herzfrequenzgurt veröffentlicht seine Messwerte) und ein Central ist meist der Client (ein Telefon liest sie), aber BLE erlaubt jede Kombination – ein Peripheral kann eine Characteristic auf dem Central entdecken, mit dem es gerade verbunden wurde, oder eine einzelne Verbindung kann Services auf beiden Seiten gleichzeitig hosten.

Die meisten Kameraanwendungen halten sich an die konventionelle Paarung (Peripheral + Server oder Central + Client), daher behandelt der Rest dieses Abschnitts sie als eine Achse, wenn der konventionelle Fall beschrieben wird. Wenn die Unterscheidung wichtig ist, werden beide Begriffe ausdrücklich ausgeschrieben.

11.6.2. Innerhalb der Datenbank

Eine GATT-Datenbank ist ein Baum. Die Blätter tragen die eigentlichen Bytes. Die Äste gruppieren verwandte Blätter zu für Menschen sinnvollen Einheiten.

Ein Baum mit einem obersten Knoten, der mit "GATT database" beschriftet ist. Darunter drei Service-Knoten, beschriftet mit "Generic Access (0x1800)", "Battery (0x180F)" und "Environmental Sensing (0x181A)". Jeder Service hat untergeordnete Characteristic-Knoten; der Battery-Service hat "Battery Level (0x2A19)" mit einem untergeordneten Descriptor "CCCD". Der Environmental-Sensing-Service hat "Temperature (0x2A6E)" und "Humidity (0x2A6F)".

Eine GATT-Datenbank. Services gruppieren Characteristics; Characteristics tragen die Bytes der Anwendung; Descriptors tragen Metadaten über die Characteristic.

Es gibt drei Arten von Knoten:

  • Ein Service ist eine logische Gruppe verwandter Werte. Die Bluetooth SIG veröffentlicht Standard-Service-Definitionen für häufige Anwendungsfälle – Battery Service für den Batteriestand, Environmental Sensing für Temperatur / Feuchtigkeit / Druck, Heart Rate für Herzfrequenzmonitore – sodass eine generische App auf einem Telefon einen Service erkennen kann, den sie noch nie gesehen hat. Eine Anwendung kann auch eigene Services für Dinge definieren, die die SIG nicht standardisiert hat.

  • Eine Characteristic ist ein benannter Wert innerhalb eines Services. Der Battery-Service hat eine einzige Characteristic – Battery Level, eine Prozentzahl in einem Byte. Environmental Sensing hat separate Characteristics für Temperatur, Feuchtigkeit, Druck und so weiter. Eine Characteristic ist die Einheit von GATT-Operationen – man liest eine Characteristic, man beschreibt eine Characteristic, man abonniert eine Characteristic.

  • Ein Descriptor sind Metadaten, die an eine Characteristic angehängt sind. Einige Descriptors sind standardisiert – der Client Characteristic Configuration Descriptor (CCCD) ist der bekannteste, denn durch das Beschreiben teilt ein Client dem Server mit: „Sende mir Benachrichtigungen zu dieser Characteristic“. Andere sind benutzerdefiniert und tragen Dinge wie das Darstellungsformat oder erweiterte Eigenschaften.

Ein GATT-Server (typischerweise das Peripheral) deklariert seine Datenbank einmalig beim Start, und die Datenbank ändert sich während des Betriebs nicht. Ein GATT-Client (typischerweise das Central) entdeckt nach dem Verbinden, was in der Datenbank steht – er durchläuft den Baum, liest die UUIDs der gefundenen Services und dann die Characteristics innerhalb jedes Services.

11.6.3. UUIDs

Jeder Service, jede Characteristic und jeder Descriptor hat eine UUID (Universally Unique IDentifier), die identifiziert, um welche Art von Ding es sich handelt. UUIDs gibt es in drei Breiten:

  • 16-Bit. Reserviert für Standards, die von der Bluetooth SIG definiert werden. Der Battery Service ist 0x180F. Battery Level (eine Characteristic) ist 0x2A19. Die vollständige Liste ist auf der Assigned-Numbers-Seite der Bluetooth SIG unter https://www.bluetooth.com/specifications/assigned-numbers/ veröffentlicht.

  • 32-Bit. Ein selten genutzter Mittelweg.

  • 128-Bit. Was alle anderen verwenden – ein Hersteller oder eine Anwendung erzeugt zufällig eine und verwendet sie für ihren eigenen Service oder ihre eigene Characteristic. Kameras, die ihr eigenes Protokoll definieren, sind hier angesiedelt.

Die Klasse bluetooth.UUID akzeptiert jede der drei Breiten:

import bluetooth

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

Eine 16-Bit-UUID kodiert sich in eine kleine Werbe-Nutzlast, was einer der Gründe ist, warum Standard-Services vorzuziehen sind, wenn einer existiert – ein Herzfrequenzgurt, der 0x180D (Heart Rate) aussendet, kostet zwei Bytes; eine benutzerdefinierte UUID kostet sechzehn. Für Anwendungen, die keine standardisierte Interoperabilität benötigen, ist eine erzeugte 128-Bit-UUID die richtige Antwort.

11.6.4. Was die von der SIG standardisierten Services bringen

Der Grund für die Verwendung eines Standard-Services ist einfach: bestehende Apps wissen bereits, wie sie mit ihm sprechen. Ein Gerät, das den Heart-Rate-Service (0x180D) aussendet und die Heart-Rate-Measurement-Characteristic (0x2A37) bereitstellt, funktioniert mit jeder Fitness-App der Welt, ohne dass jemand neuen Code schreiben muss. Ein Gerät, das dieselben Daten mit benutzerdefinierten UUIDs neu implementiert, benötigt seine eigene Begleit-App und sein eigenes Protokolldokument.

Die Standards haben jedoch ihren Preis. Die Byte-Layouts innerhalb jeder Characteristic sind festgelegt – die SIG hat entschieden, dass Heart Rate Measurement ein Ein-Byte-Flags-Feld ist, gefolgt von einem entweder 8-Bit- oder 16-Bit-Herzfrequenzwert, optional gefolgt von R-R-Intervallen – und ein konformes Gerät muss sich an diese Layouts halten. Benutzerdefinierte Services sind von dieser Einschränkung frei.

Die pragmatische Antwort für Kameras: Verwende den Standard-Service, wenn einer für die Art von Daten existiert, die du hast (Battery Service, Environmental Sensing), und definiere einen benutzerdefinierten mit einer 128-Bit-UUID für alles, was spezifisch für deine Anwendung ist.

11.6.5. Server- und clientseitige Objekte

Für dieselben konzeptionellen Bausteine (Service, Characteristic, Descriptor) stellt jede GATT-Bibliothek zwei parallele Sätze von Objekten bereit: