11.6. Services et caractéristiques

Une fois que GAP a établi une connexion ouverte entre deux appareils, la couche située au-dessus – le Generic Attribute Profile, GATT – doit donner un sens aux octets qui circulent dans cette connexion. Le choix de BLE est ici inhabituel. Là où TCP expose un flux d’octets brut et laisse à l’application le soin d’inventer son propre découpage en trames, GATT expose une petite base de données clé/valeur qu’un côté héberge et que l’autre lit, écrit ou auquel il s’abonne.

Cette base de données est ce à quoi les concepteurs d’applications consacrent la majeure partie de leur temps en BLE. Ce que la caméra publie vers un téléphone, ce qu’elle surveille sur un capteur distant, la façon dont un clavier Bluetooth indique à son hôte quelle touche a été pressée – tout cela correspond à des valeurs de caractéristiques dans une base de données GATT quelque part.

11.6.1. Deux axes de rôles, pas un seul

Une source fréquente de confusion : peripheral / central et server / client sont deux axes indépendants, et non des synonymes.

  • Peripheral et central sont des rôles GAP, définis lors de la connexion. Le peripheral diffuse des annonces et reçoit la connexion ; le central effectue le balayage et initie la connexion. Cela est fixé au moment où le lien s’établit et ne change pas.

  • Server et client sont des rôles GATT, définis pour chaque opération sur une caractéristique. Le serveur héberge la caractéristique ; le client la lit, l’écrit ou s’y abonne.

Les deux axes sont découplés par la spécification. Un peripheral est généralement le serveur (une ceinture de fréquence cardiaque publie ses relevés) et un central est généralement le client (un téléphone les lit), mais le BLE autorise n’importe quelle combinaison – un peripheral peut découvrir une caractéristique sur le central auquel il vient de se connecter, ou une même connexion peut héberger des services des deux côtés à la fois.

La plupart des applications de caméra s’en tiennent à l’appariement conventionnel (peripheral + serveur, ou central + client), de sorte que le reste de cette section les traite comme un seul axe lorsque c’est le cas conventionnel qui est décrit. Lorsque la distinction importe, les deux termes sont explicitement précisés.

11.6.2. À l’intérieur de la base de données

Une base de données GATT est un arbre. Les feuilles portent les octets réels. Les branches regroupent les feuilles apparentées en unités ayant un sens pour l’humain.

Un arbre dont le nœud supérieur est intitulé « GATT database ». En dessous, trois nœuds Service intitulés « Generic Access (0x1800) », « Battery (0x180F) » et « Environmental Sensing (0x181A) ». Chaque Service possède des nœuds Characteristic enfants ; le service Battery a « Battery Level (0x2A19) » avec un Descriptor enfant « CCCD ». Le service Environmental Sensing a « Temperature (0x2A6E) » et « Humidity (0x2A6F) ».

Une base de données GATT. Les services regroupent les caractéristiques ; les caractéristiques portent les octets de l’application ; les descripteurs portent des métadonnées sur la caractéristique.

Il existe trois types de nœud :

  • Un service est un groupe logique de valeurs apparentées. Le Bluetooth SIG publie des définitions de services standard pour les cas d’usage courants – Battery Service pour le niveau de batterie, Environmental Sensing pour la température / l’humidité / la pression, Heart Rate pour les moniteurs de fréquence cardiaque – de sorte qu’une application générique sur un téléphone peut reconnaître un service qu’elle n’a jamais vu auparavant. Une application est également libre de définir ses propres services pour des éléments que le SIG n’a pas normalisés.

  • Une caractéristique est une valeur nommée à l’intérieur d’un service. Le service Battery comporte une seule caractéristique – Battery Level, un pourcentage d’un octet. Environmental Sensing possède des caractéristiques distinctes pour la température, l’humidité, la pression, et ainsi de suite. La caractéristique est l’unité des opérations GATT – on lit une caractéristique, on écrit une caractéristique, on s’abonne à une caractéristique.

  • Un descripteur est une métadonnée attachée à une caractéristique. Certains descripteurs sont normalisés – le Client Characteristic Configuration Descriptor (CCCD) est le plus connu, car y écrire est la manière dont un client indique au serveur « envoie-moi des notifications sur cette caractéristique ». D’autres sont définis par l’utilisateur et portent des éléments tels que le format de présentation ou des propriétés étendues.

Un serveur GATT (généralement le peripheral) déclare sa base de données une seule fois au démarrage, et celle-ci ne change pas pendant l’exécution. Un client GATT (généralement le central) découvre ce que contient la base de données après la connexion – en parcourant l’arbre, en lisant les UUID des services qu’il trouve, puis les caractéristiques à l’intérieur de chacun.

11.6.3. UUID

Chaque service, caractéristique et descripteur possède un UUID (Universally Unique IDentifier) qui identifie de quel type d’élément il s’agit. Les UUID existent en trois largeurs :

  • 16 bits. Réservés aux normes définies par le Bluetooth SIG. Le service Battery est 0x180F. Battery Level (une caractéristique) est 0x2A19. La liste complète est publiée sur le site des numéros assignés du Bluetooth SIG à l’adresse https://www.bluetooth.com/specifications/assigned-numbers/.

  • 32 bits. Un intermédiaire rarement utilisé.

  • 128 bits. Ce que tout le monde d’autre utilise – un fournisseur ou une application en génère un de façon aléatoire et l’utilise pour son service ou sa caractéristique personnalisés. Les caméras qui définissent leur propre protocole se trouvent ici.

La classe bluetooth.UUID accepte n’importe laquelle des trois largeurs

import bluetooth

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

Un UUID 16 bits s’encode dans une petite charge utile d’annonce, ce qui est l’une des raisons pour lesquelles les services standard sont préférables lorsqu’il en existe un – une ceinture de fréquence cardiaque qui annonce 0x180D (Heart Rate) coûte deux octets ; un UUID personnalisé en coûte seize. Pour les applications qui n’ont pas besoin d’interopérabilité standard, un UUID 128 bits généré est la bonne réponse.

11.6.4. Ce que vous apportent les services normalisés par le SIG

L’argument en faveur d’un service standard est simple : les applications existantes savent déjà comment lui parler. Un appareil qui annonce le service Heart Rate (0x180D) et expose la caractéristique Heart Rate Measurement (0x2A37) fonctionne avec toutes les applications de fitness de la planète sans que personne n’ait à écrire de nouveau code. Un appareil qui réimplémente les mêmes données avec des UUID personnalisés nécessite sa propre application compagnon et son propre document de protocole.

Les normes ont toutefois un coût. Les agencements d’octets à l’intérieur de chaque caractéristique sont spécifiés – le SIG a décidé que Heart Rate Measurement est un champ de drapeaux d’un octet suivi d’une valeur de fréquence cardiaque sur 8 ou 16 bits, éventuellement suivie d’intervalles R-R – et un appareil conforme doit respecter ces agencements. Les services personnalisés sont exempts de cette contrainte.

La réponse pragmatique pour les caméras : utilisez le service standard lorsqu’il en existe un pour le type de données que vous avez (Battery Service, Environmental Sensing), et définissez-en un personnalisé avec un UUID 128 bits pour tout ce qui est spécifique à votre application.

11.6.5. Objets côté serveur et côté client

Pour les mêmes briques conceptuelles (service, caractéristique, descripteur), chaque bibliothèque GATT expose deux ensembles d’objets parallèles :