Freigeben über


Bluetooth GATT Server

In diesem Thema wird veranschaulicht, wie Sie die GATT-Server-APIs (Bluetooth Generic Attribute) für Universelle Windows-Plattform-Apps (UWP) verwenden.

Wichtig

Sie müssen die Bluetooth-Funktion in Package.appxmanifest deklarieren.

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

Wichtige APIs

Übersicht

Windows arbeitet in der Regel in der Clientrolle. Dennoch ergeben sich viele Szenarien, in denen Windows auch als Bluetooth LE GATT Server fungiert. Fast alle Szenarien für IoT-Geräte sowie die meisten plattformübergreifenden BLE-Kommunikation erfordern, dass Windows ein GATT-Server ist. Darüber hinaus ist das Senden von Benachrichtigungen an wearable-Geräte in der Nähe zu einem beliebten Szenario geworden, für das diese Technologie ebenfalls erforderlich ist.

Servervorgänge beziehen sich auf den Dienstanbieter und gattLocalCharacteristic. Diese beiden Klassen stellen die Funktionalität bereit, die zum Deklarieren, Implementieren und Verfügbarmachen einer Datenhierarchie für ein Remotegerät erforderlich ist.

Definieren der unterstützten Dienste

Ihre App kann einen oder mehrere Dienste deklarieren, die von Windows veröffentlicht werden. Jeder Dienst wird durch eine UUID eindeutig identifiziert.

Attribute und UUIDs

Jeder Dienst, jede Eigenschaft und jeder Deskriptor wird durch seine eigene eindeutige 128-Bit-UUID definiert.

Die Windows-APIs verwenden alle den Begriff GUID, aber der Bluetooth-Standard definiert diese als UUIDs. Für unsere Zwecke sind diese beiden Begriffe austauschbar, daher verwenden wir weiterhin den Begriff UUID.

Wenn das Attribut standard ist und von der Bluetooth-SIG definiert ist, verfügt es auch über eine entsprechende 16-Bit-Kurz-ID (z. B. ist die UUID des Akkustands 0000 2A19-0000-1000-8000-00805F9B34FB und die kurze ID ist 0x2A19). Diese Standard-UUIDs sind in GattServiceUuids und GattCharacteristicUuids zu sehen.

Wenn Ihre App einen eigenen benutzerdefinierten Dienst implementiert, muss eine benutzerdefinierte UUID generiert werden. Dies ist in Visual Studio über Tools –> CreateGuid einfach möglich (verwenden Sie Option 5, um sie in "xxxxxxxx-xxxx-... xxxx"-Format). Diese uuid kann jetzt verwendet werden, um neue lokale Dienste, Merkmale oder Deskriptoren zu deklarieren.

Eingeschränkte Dienste

Die folgenden Dienste sind vom System reserviert und können derzeit nicht veröffentlicht werden:

  1. Geräteinformationsdienst (DIS)
  2. Generic Attribute Profile Service (GATT)
  3. Generischer Zugriffsprofildienst (GAP)
  4. Human Interface Device Service (HOGP)
  5. Überprüfungsparameterdienst (SCP)

Der Versuch, einen blockierten Dienst zu erstellen, führt dazu, dass BluetoothError.DisabledByPolicy vom Aufruf von CreateAsync zurückgegeben wird.

Generierte Attribute

Die folgenden Deskriptoren werden automatisch vom System generiert, basierend auf den GattLocalCharacteristicParameters, die während der Erstellung des Merkmals bereitgestellt werden:

  1. Client characteristic configuration (if the characteristic is marked as indicatable or notifiable).
  2. Charakteristische Benutzerbeschreibung (wenn die UserDescription-Eigenschaft festgelegt ist). Weitere Informationen finden Sie unter GattLocalCharacteristicParameters.UserDescription-Eigenschaft.
  3. Charakteristisches Format (ein Deskriptor für jedes angegebene Präsentationsformat). Weitere Informationen finden Sie unter GattLocalCharacteristicParameters.PresentationFormats-Eigenschaft.
  4. Charakteristisches Aggregatformat (wenn mehr als ein Präsentationsformat angegeben ist). GattLocalCharacteristicParameters.Weitere Informationen finden Sie unter PresentationFormats-Eigenschaft.
  5. Erweiterte Eigenschaften für charakteristische Eigenschaften (wenn das Merkmal mit dem Bit für erweiterte Eigenschaften gekennzeichnet ist).

Der Wert des Deskriptors für erweiterte Eigenschaften wird über die Eigenschaften ReliableWrites und WritableAuxiliaries bestimmt.

Der Versuch, einen reservierten Deskriptor zu erstellen, führt zu einer Ausnahme.

Beachten Sie, dass die Übertragung derzeit nicht unterstützt wird. Die Angabe von Broadcast GattCharacteristicProperty führt zu einer Ausnahme.

Erstellen der Hierarchie von Diensten und Merkmalen

GattServiceProvider wird verwendet, um die Definition des primären Stammdiensts zu erstellen und anzukündigen. Jeder Dienst erfordert ein eigenes ServiceProvider-Objekt, das eine GUID akzeptiert:

GattServiceProviderResult result = await GattServiceProvider.CreateAsync(uuid);

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

Primäre Dienste sind die oberste Ebene der GATT-Struktur. Primäre Dienste enthalten Merkmale sowie andere Dienste (als "eingeschlossene" oder sekundäre Dienste bezeichnet).

Füllen Sie nun den Dienst mit den erforderlichen Merkmalen und Deskriptoren auf:

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;

Wie oben gezeigt, ist dies auch ein guter Ort, um Ereignishandler für die Vorgänge zu deklarieren, die jedes Merkmal unterstützt. Um ordnungsgemäß auf Anforderungen zu reagieren, muss eine App einen Ereignishandler für jeden Anforderungstyp definieren und festlegen, den das Attribut unterstützt. Wenn ein Handler nicht registriert wird, wird die Anforderung sofort mit "UnlikelyError" vom System abgeschlossen.

Konstante Merkmale

Manchmal gibt es charakteristische Werte, die sich im Laufe der Lebensdauer der App nicht ändern. In diesem Fall empfiehlt es sich, ein konstantes Merkmal zu deklarieren, um eine unnötige App-Aktivierung zu verhindern:

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

Veröffentlichen des Diensts

Nachdem der Dienst vollständig definiert wurde, besteht der nächste Schritt darin, die Unterstützung für den Dienst zu veröffentlichen. Dadurch wird das Betriebssystem darüber informiert, dass der Dienst zurückgegeben werden soll, wenn Remotegeräte eine Dienstermittlung durchführen. Sie müssen zwei Eigenschaften festlegen: IsDiscoverable und IsConnectable:

GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
    IsDiscoverable = true,
    IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
  • IsDiscoverable: Gibt den Anzeigenamen für Remotegeräte in der Ankündigung an, sodass das Gerät auffindbar ist.
  • IsConnectable: Kündigt eine anschließbare Ankündigung für die Verwendung in der Peripherierolle an.

Wenn ein Dienst sowohl erkennbar als auch verbindend ist, fügt das System dem Ankündigungspaket die Dienst-Uuid hinzu. Das Ankündigungspaket enthält nur 31 Bytes, und eine 128-Bit-UUID nimmt 16 davon auf!

Wenn ein Dienst im Vordergrund veröffentlicht wird, muss eine Anwendung StopAdvertising aufrufen, wenn die Anwendung angehalten wird.

Reagieren auf Lese- und Schreibanforderungen

Wie wir oben beim Deklarieren der erforderlichen Merkmale gesehen haben, verfügt GattLocalCharacteristics über drei Ereignistypen: ReadRequested, WriteRequested und SubscribedClientsChanged.

Lesen

Wenn ein Remotegerät versucht, einen Wert aus einem Merkmal zu lesen (und es ist kein konstanter Wert), wird das ReadRequested-Ereignis aufgerufen. Das Merkmal, für das der Lesevorgang aufgerufen wurde, sowie Args (die Informationen über das Remotegerät enthalten) werden an den Delegaten übergeben:

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();
}

Schreiben

Wenn ein Remotegerät versucht, einen Wert in ein Merkmal zu schreiben, wird das WriteRequested-Ereignis mit Details zum Remotegerät aufgerufen, welches Merkmal geschrieben werden soll, und dem Wert selbst:

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();
}

Es gibt zwei Typen von Schreibvorgängen – mit und ohne Antwort. Verwenden Sie GattWriteOption (eine Eigenschaft für das GattWriteRequest-Objekt), um herauszufinden, welchen Schreibtyp das Remotegerät ausführt.

Senden von Benachrichtigungen an abonnierte Clients

Bei den häufigsten GATT-Servervorgängen haben Benachrichtigungen die wichtige Funktion, Daten per Push an die Remotegeräte zu übertragen. Manchmal möchten Sie alle abonnierten Clients benachrichtigen, aber manchmal möchten Sie auswählen, an welche Geräte der neue Wert gesendet werden soll:

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

Wenn ein neues Gerät Benachrichtigungen abonniert, wird das SubscribedClientsChanged-Ereignis aufgerufen:

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

Hinweis

Ihre Anwendung kann die maximale Benachrichtigungsgröße für einen bestimmten Client mit der MaxNotificationSize-Eigenschaft abrufen. Alle Daten, die größer als die maximale Größe sind, werden vom System abgeschnitten.

Wenn Sie das Ereignis GattLocalCharacteristic.SubscribedClientsChanged behandeln, können Sie den unten beschriebenen Prozess verwenden, um vollständige Informationen zu den aktuell abonnierten Clientgeräten zu ermitteln: