Partager via


Serveur GATT Bluetooth

Cette rubrique montre comment utiliser les API du 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 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 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.

Les API Windows utilisent tous le terme GUID, mais la norme Bluetooth les définit en tant qu’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 bluetooth SIG défini, il aura également un ID court de 16 bits correspondant (par exemple, le niveau de batterie UUID est 00002A19-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é. Cette opération est facilement effectuée dans Visual Studio via Tools -> CreateGuid (utilisez l’option 5 pour l’obtenir dans « 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 de paramètres d’analyse (SCP)

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 des caractéristiques du client (si la caractéristique est marquée comme indiquant ou notifiable).
  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). Propriété GattLocalCharacteristicParameters.See PresentationFormats pour plus d’informations.
  5. Propriétés étendues caractéristiques (si la caractéristique est marquée avec le bit des propriétés étendues).

La valeur du descripteur de 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

Le GattServiceProvider est utilisé pour créer et publier la définition du service principal racine. Chaque service requiert son propre objet ServiceProvider qui prend 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. L’échec de l’inscription d’un gestionnaire entraîne la fin de la requête immédiatement avec l’option UnlikelyError par le système.

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 : IsDiscoverable et IsConnectable :

GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
    IsDiscoverable = true,
    IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
  • IsDiscoverable : publie le nom convivial des appareils distants dans la publicité, ce qui rend 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 ajoute le service Uuid au paquet de publication. Il n’y a que 31 octets dans le paquet Publication et un UUID 128 bits prend jusqu’à 16 d’entre eux !

Notez que 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 nous l’avons vu ci-dessus 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 de lecture a été appelée ainsi que les arguments (contenant des informations sur l’appareil distant) sont transmises 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();
}

Écrire

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.  
}

Remarque

Votre application peut obtenir la taille de notification maximale d’un client particulier avec la propriété MaxNotificationSize . 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 :