Serveur BLUETOOTH GATT

Cette rubrique montre comment utiliser les API du serveur GATT (Bluetooth Generic Attribute) pour les applications Windows.

Important

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

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

Overview

Windows fonctionne généralement dans le rôle client. Néanmoins, dans de nombreux scénarios, Windows doit également agir en tant que serveur GATT Bluetooth LE. Presque tous les scénarios pour les appareils IoT, ainsi que la plupart des communications BLE multiplateformes nécessitent Windows être 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 de 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 à 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 UUID

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

Note

Les API Windows utilisent tous le terme GUID, mais la norme Bluetooth les définit comme UUID. À nos fins, ces deux termes sont interchangeables afin que nous continuions à utiliser le terme UUID.

Si l’attribut est standard et défini par le Bluetooth SIG, il aura également un identifiant court correspondant de 16 bits (par exemple, l’UUID Battery Level est 00002A19-0000-1000-8000-00805F9B34FB et l’identifiant 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 le fichier « xxxxxxxx-xxxx-... format xxxx " ). Cet uuid peut désormais être utilisé pour déclarer de nouveaux services locaux, caractéristiques ou descripteurs.

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 l’appareil (DIS)
  2. Service de profil d’attribut générique (GATT)
  3. Service de profil d’accès générique (GAP)
  4. Service des paramètres de numérisation (SCP)

Avertissement

Toute tentative de création d’un service bloqué entraîne le renvoi 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 GattLocalCharacteristicParameters fournis lors de la création de la caractéristique :

  1. Configuration de la caractéristique du client (si la caractéristique est marquée comme pouvant faire l’objet d’une indication ou d’une notification).
  2. Description de l’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). Consultez la propriété PresentationFormats de GattLocalCharacteristicParameters pour plus d’informations.
  5. Propriétés étendues de la caractéristique (si la caractéristique est marquée par le bit des propriétés étendues).

Note

La valeur du descripteur de propriétés étendues est déterminée à partir des propriétés caractéristiques ReliableWrites et WritableAuxiliaries.

Avertissement

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

Avertissement

Broadcast n’est pas pris 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

Le GattServiceProvider est utilisé pour créer et publier la définition du service principal racine. Chaque service nécessite son propre objet ServiceProvider, auquel on passe un GUID :

GattServiceProviderResult result = await GattServiceProvider.CreateAsync(uuid);

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

Les services principaux sont 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).

Maintenant, renseignez le service avec les caractéristiques et les 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 requêtes, 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. Si aucun gestionnaire n’est enregistré, le système traite immédiatement la requête avec UnlikelyError.

Caractéristiques constantes

Parfois, il existe des valeurs caractéristiques qui ne changeront pas pendant la durée de vie de l’application. Dans ce cas, il est conseillé 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 les appareils distants effectuent une découverte de service. Vous devez définir deux propriétés : IsDiscoverableIsConnectable

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

Lorsqu’un service est à la fois détectable et connectable, le système ajoutera l’UUID du service au paquet d’annonce. Il n’y a que 31 octets dans le paquet publicitaire, et un UUID de 128 bits en occupe 16 !

Lorsqu’un service est publié au premier plan, une application doit appeler StopAdvertising lorsque l’application s’interrompt.

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

Comme indiqué précédemment lors de la déclaration des caractéristiques requises, GattLocalCharacteristics a 3 types d’événements - ReadRequestedWriteRequested 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 read a été appelé, ainsi que args (contenant des informations sur l’appareil distant), est transmise 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, caractéristique à é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 que l’appareil distant exécute.

Envoyer des notifications aux clients abonnés

Les opérations les plus fréquentes du serveur GATT, les notifications effectuent la fonction critique d’envoi de données aux appareils distants. Parfois, vous voudrez avertir tous les clients abonnés, mais d’autres fois, vous pouvez choisir les appareils à 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.  
}

Note

Votre application peut obtenir la taille de notification maximale d’un client particulier avec la MaxNotificationSize propriété. Toutes les données supérieures à la taille maximale seront 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 les informations complètes sur les appareils clients actuellement abonnés :