Serveur GATT Bluetooth

Cette rubrique montre comment utiliser les API de serveur GATT (Bluetooth Generic Attribute) pour les applications plateforme Windows universelle (UWP).

Important

Vous devez déclarer la fonctionnalité « bluetooth » dans Package.appxmanifest.

<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>

API importantes

Vue d’ensemble

Windows fonctionne généralement dans le rôle client. Néanmoins, de nombreux scénarios se produisent qui nécessitent que Windows agisse également en tant que serveur Bluetooth LE GATT. Presque tous les scénarios pour les appareils IoT, ainsi que la plupart des communications BLE multiplateformes, nécessitent que Windows soit un serveur GATT. En outre, l’envoi de notifications à des appareils portables à proximité est devenu un scénario populaire qui nécessite également cette technologie.

Les opérations serveur tournent autour du fournisseur de services et de GattLocalCharacteristic. Ces deux classes fournissent les fonctionnalités nécessaires pour déclarer, implémenter et exposer une hiérarchie de données sur un appareil distant.

Définir les services pris en charge

Votre application peut déclarer un ou plusieurs services qui seront publiés par Windows. Chaque service est identifié de manière unique par un UUID.

Attributs et UUIDs

Chaque service, caractéristique et descripteur est défini par son propre UUID 128 bits unique.

Les API Windows utilisent toutes le terme GUID, mais la norme Bluetooth les définit comme des UUID. À nos fins, ces deux termes étant interchangeables, nous allons continuer à utiliser le terme UUID.

Si l’attribut est standard et défini par bluetooth SIG, il aura également un ID court de 16 bits correspondant (par exemple, l’UUID au niveau de la batterie est 0000 2A19-0000-1000-8000-00805F9B34FB et l’ID court est 0x2A19). Ces UUID standard sont visibles dans GattServiceUuids et GattCharacteristicUuids.

Si votre application implémente son propre service personnalisé, un UUID personnalisé doit être généré. Cela s’effectue facilement dans Visual Studio via Tools -> CreateGuid (utilisez l’option 5 pour l’obtenir dans « xxxxxxxx-xxxx-... xxxx » format). Cet uuid peut désormais être utilisé pour déclarer de nouveaux services, caractéristiques ou descripteurs locaux.

Services restreints

Les services suivants sont réservés par le système et ne peuvent pas être publiés pour l’instant :

  1. Service d’informations sur les appareils (DIS)
  2. Service de profil d’attribut générique (GATT)
  3. Service de profil d’accès générique (GAP)
  4. Service d’appareil d’interface humaine (HOGP)
  5. Service de paramètres d’analyse (SCP)

La tentative de création d’un service bloqué entraîne le retour de BluetoothError.DisabledByPolicy à partir de l’appel à CreateAsync.

Attributs générés

Les descripteurs suivants sont générés automatiquement par le système, en fonction des paramètres GattLocalCharacteristicParameters fournis lors de la création de la caractéristique :

  1. Configuration des caractéristiques du client (si la caractéristique est marquée comme indiquant ou notifiable).
  2. Description utilisateur caractéristique (si la propriété UserDescription est définie). Pour plus d’informations, consultez La propriété GattLocalCharacteristicParameters.UserDescription.
  3. Format caractéristique (un descripteur pour chaque format de présentation spécifié). Pour plus d’informations, consultez la propriété GattLocalCharacteristicParameters.PresentationFormats.
  4. Format d’agrégation caractéristique (si plusieurs formats de présentation sont spécifiés). GattLocalCharacteristicParameters.Voir la propriété PresentationFormats pour plus d’informations.
  5. Propriétés étendues caractéristiques (si la caractéristique est marquée avec le bit de propriétés étendues).

La valeur du descripteur Propriétés étendues est déterminée via les propriétés caractéristiques ReliableWrites et WritableAuxiliaries.

Toute tentative de création d’un descripteur réservé entraîne une exception.

Notez que la diffusion n’est pas prise en charge pour l’instant. La spécification de Broadcast GattCharacteristicProperty entraîne une exception.

Créer la hiérarchie des services et des caractéristiques

GattServiceProvider est utilisé pour créer et publier la définition du service principal racine. Chaque service nécessite son propre objet ServiceProvider qui accepte un GUID :

GattServiceProviderResult result = await GattServiceProvider.CreateAsync(uuid);

if (result.Error == BluetoothError.Success)
{
    serviceProvider = result.ServiceProvider;
    // 
}

Les services principaux constituent le niveau supérieur de l’arborescence GATT. Les services principaux contiennent des caractéristiques ainsi que d’autres services (appelés « Inclus » ou services secondaires).

À présent, renseignez le service avec les caractéristiques et descripteurs requis :

GattLocalCharacteristicResult characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid1, ReadParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}
_readCharacteristic = characteristicResult.Characteristic;
_readCharacteristic.ReadRequested += ReadCharacteristic_ReadRequested;

characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid2, WriteParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}
_writeCharacteristic = characteristicResult.Characteristic;
_writeCharacteristic.WriteRequested += WriteCharacteristic_WriteRequested;

characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid3, NotifyParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}
_notifyCharacteristic = characteristicResult.Characteristic;
_notifyCharacteristic.SubscribedClientsChanged += SubscribedClientsChanged;

Comme indiqué ci-dessus, il s’agit également d’un bon endroit pour déclarer des gestionnaires d’événements pour les opérations que chaque caractéristique prend en charge. Pour répondre correctement aux demandes, une application doit définir et définir un gestionnaire d’événements pour chaque type de requête pris en charge par l’attribut. L’échec de l’inscription d’un gestionnaire entraîne l’exécution immédiate de la demande avec Improbableerror par le système.

Caractéristiques constantes

Parfois, il existe des valeurs caractéristiques qui ne changent pas au cours de la durée de vie de l’application. Dans ce cas, il est recommandé de déclarer une caractéristique constante pour empêcher l’activation inutile de l’application :

byte[] value = new byte[] {0x21};
var constantParameters = new GattLocalCharacteristicParameters
{
    CharacteristicProperties = (GattCharacteristicProperties.Read),
    StaticValue = value.AsBuffer(),
    ReadProtectionLevel = GattProtectionLevel.Plain,
};

var characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid4, constantParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
    // An error occurred.
    return;
}

Publier le service

Une fois le service entièrement défini, l’étape suivante consiste à publier la prise en charge du service. Cela informe le système d’exploitation que le service doit être retourné lorsque des appareils distants effectuent une découverte de service. Vous devez définir deux propriétés : IsDiscoverable et IsConnectable :

GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
    IsDiscoverable = true,
    IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
  • IsDiscoverable : publie le nom convivial sur les appareils distants dans la publicité, ce qui rend l’appareil détectable.
  • IsConnectable : publie une publication connectable à utiliser dans un rôle de périphérique.

Lorsqu’un service est à la fois détectable et connectable, le système ajoute le service Uuid au paquet de publication. Il n’y a que 31 octets dans le paquet Publication et un UUID 128 bits en prend 16 !

Notez que lorsqu’un service est publié au premier plan, une application doit appeler StopAdvertising quand l’application s’interrompt.

Répondre aux demandes de lecture et d’écriture

Comme nous l’avons vu plus haut lors de la déclaration des caractéristiques requises, GattLocalCharacteristics a 3 types d’événements : ReadRequested, WriteRequested et SubscribedClientsChanged.

Lire

Lorsqu’un appareil distant tente de lire une valeur à partir d’une caractéristique (et qu’il ne s’agit pas d’une valeur constante), l’événement ReadRequested est appelé. La caractéristique sur laquelle la lecture a été appelée, ainsi que les args (contenant des informations sur l’appareil distant) est passée au délégué :

characteristic.ReadRequested += Characteristic_ReadRequested;
// ... 

async void ReadCharacteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
{
    var deferral = args.GetDeferral();
    
    // Our familiar friend - DataWriter.
    var writer = new DataWriter();
    // populate writer w/ some data. 
    // ... 

    var request = await args.GetRequestAsync();
    request.RespondWithValue(writer.DetachBuffer());
    
    deferral.Complete();
}

Write

Lorsqu’un appareil distant tente d’écrire une valeur dans une caractéristique, l’événement WriteRequested est appelé avec des détails sur l’appareil distant, la caractéristique dans laquelle écrire et la valeur elle-même :

characteristic.ReadRequested += Characteristic_ReadRequested;
// ...

async void WriteCharacteristic_WriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
{
    var deferral = args.GetDeferral();
    
    var request = await args.GetRequestAsync();
    var reader = DataReader.FromBuffer(request.Value);
    // Parse data as necessary. 

    if (request.Option == GattWriteOption.WriteWithResponse)
    {
        request.Respond();
    }
    
    deferral.Complete();
}

Il existe 2 types d’écritures : avec et sans réponse. Utilisez GattWriteOption (une propriété sur l’objet GattWriteRequest) pour déterminer le type d’écriture effectué par l’appareil distant.

Envoyer des notifications aux clients abonnés

La plus fréquente des opérations de serveur GATT, les notifications remplissent la fonction critique d’envoi de données aux appareils distants. Parfois, vous souhaiterez informer tous les clients abonnés, mais vous pouvez également choisir les appareils auxquels envoyer la nouvelle valeur :

async void NotifyValue()
{
    var writer = new DataWriter();
    // Populate writer with data
    // ...
    
    await notifyCharacteristic.NotifyValueAsync(writer.DetachBuffer());
}

Lorsqu’un nouvel appareil s’abonne aux notifications, l’événement SubscribedClientsChanged est appelé :

characteristic.SubscribedClientsChanged += SubscribedClientsChanged;
// ...

void _notifyCharacteristic_SubscribedClientsChanged(GattLocalCharacteristic sender, object args)
{
    List<GattSubscribedClient> clients = sender.SubscribedClients;
    // Diff the new list of clients from a previously saved one 
    // to get which device has subscribed for notifications. 

    // You can also just validate that the list of clients is expected for this app.  
}

Notes

Votre application peut obtenir la taille de notification maximale pour un client particulier avec la propriété MaxNotificationSize . Toutes les données supérieures à la taille maximale sont tronquées par le système.

Lorsque vous gérez l’événement GattLocalCharacteristic.SubscribedClientsChanged , vous pouvez utiliser le processus décrit ci-dessous pour déterminer des informations complètes sur les appareils clients actuellement abonnés :