Bluetooth GATT-Client

In diesem Artikel wird die Verwendung der Bluetooth Generic Attribute (GATT)-Client-APIs für Windows-Apps veranschaulicht.

Important

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

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

Übersicht

Sie können die APIs im Namespace Windows.Devices.Bluetooth.GenericAttributeProfile verwenden, um auf Bluetooth LE-Geräte zuzugreifen. Bluetooth LE-Geräte stellen ihre Funktionen über eine Sammlung von:

  • Dienste
  • Merkmale
  • Deskriptoren

Dienste definieren den Funktionsvertrag des LE-Geräts und enthalten eine Sammlung von Merkmalen, die den Dienst definieren. Diese Merkmale enthalten wiederum Beschreibungen, die die Merkmale beschreiben. Diese 3 Begriffe werden generisch als Attribute eines Geräts bezeichnet.

Die Bluetooth LE GATT-APIs machen Objekte und Funktionen verfügbar, anstatt auf den unformatierten Transport zuzugreifen. Mit den GATT-APIs können Sie auch mit Bluetooth LE-Geräten arbeiten, um die folgenden Aufgaben auszuführen:

  • Attributermittlung durchführen
  • Lese- und Schreib-Attributwerte
  • Registrieren eines Rückrufs für das Merkmal ValueChanged-Ereignis

Um eine nützliche Implementierung zu erstellen, müssen Sie bereits über Kenntnisse der GATT-Dienste und -Charakteristiken verfügen, die die Anwendung nutzen soll, und die spezifischen Charakteristikwerte so verarbeiten, dass die von der API bereitgestellten Binärdaten in verwertbare Daten umgewandelt werden, bevor sie dem Benutzer angezeigt werden. Die Bluetooth GATT-APIs machen nur die grundlegenden Grundtypen verfügbar, die für die Kommunikation mit einem Bluetooth LE-Gerät erforderlich sind. Um die Daten zu interpretieren, muss ein Anwendungsprofil entweder durch ein Bluetooth SIG-Standardprofil oder ein von einem Geräteanbieter implementiertes benutzerdefiniertes Profil definiert werden. Ein Profil erstellt einen verbindlichen Vertrag zwischen der Anwendung und dem Gerät, was die ausgetauschten Daten darstellen und wie sie interpretiert werden.

Aus Gründen der Einfachheit verwaltet die Bluetooth SIG eine Liste der verfügbaren öffentlichen Profile .

Abfrage nach Geräten in der Nähe

Es gibt zwei Hauptmethoden zum Abfragen von Geräten in der Nähe:

Die 2. Methode wird in der Ankündigungsdokumentation ausführlich behandelt, damit sie hier nicht viel diskutiert wird, aber die Grundidee besteht darin, die Bluetooth-Adresse von nahe gelegenen Geräten zu finden, die den bestimmten Ankündigungsfilter erfüllen. Sobald Sie über die Adresse verfügen, können Sie BluetoothLEDevice.FromBluetoothAddressAsync aufrufen, um einen Verweis auf das Gerät abzurufen.

Zurück zur DeviceWatcher-Methode. Ein Bluetooth LE-Gerät ist wie jedes andere Gerät in Windows und kann mithilfe der Enumeration-APIs abgefragt werden. Verwenden Sie die DeviceWatcher-Klasse , und übergeben Sie eine Abfragezeichenfolge, die die zu suchden Geräte angibt:

// Query for extra properties you want returned
string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected" };

DeviceWatcher deviceWatcher =
            DeviceInformation.CreateWatcher(
                    BluetoothLEDevice.GetDeviceSelectorFromPairingState(false),
                    requestedProperties,
                    DeviceInformationKind.AssociationEndpoint);

// Register event handlers before starting the watcher.
// Added, Updated and Removed are required to get all nearby devices
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Updated += DeviceWatcher_Updated;
deviceWatcher.Removed += DeviceWatcher_Removed;

// EnumerationCompleted and Stopped are optional to implement.
deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted;
deviceWatcher.Stopped += DeviceWatcher_Stopped;

// Start the watcher.
deviceWatcher.Start();

Nachdem Sie deviceWatcher gestartet haben, erhalten Sie DeviceInformation für jedes Gerät, das die Abfrage im Handler für das betreffende AddedEreignis für die betreffenden Geräte erfüllt. Ausführlichere Informationen zu DeviceWatcher finden Sie im vollständigen Beispiel on Github.

Herstellen einer Verbindung mit dem Gerät

Nachdem ein gewünschtes Gerät erkannt wurde, verwenden Sie die DeviceInformation.Id , um das Bluetooth LE Device-Objekt für das betreffende Gerät abzurufen:

private async Task ConnectDevice(DeviceInformation deviceInfo)
{
    // Note: BluetoothLEDevice.FromIdAsync must be called from a UI thread because it may prompt for consent.
    BluetoothLEDevice bluetoothLeDevice = await BluetoothLEDevice.FromIdAsync(deviceInfo.Id);
    // ...
}

Andererseits löst das Löschen aller Verweise auf ein BluetoothLEDevice-Objekt für ein Gerät (und wenn keine andere App auf dem System über einen Verweis auf das Gerät verfügt) eine automatische Trennung nach einem kleinen Timeoutzeitraum aus.

bluetoothLeDevice.Dispose();

Wenn die App erneut auf das Gerät zugreifen muss, wird die erneute Erstellung des Geräteobjekts und der Zugriff auf ein Merkmal (im nächsten Abschnitt erläutert) das Betriebssystem bei Bedarf auslösen, um eine erneute Verbindung herzustellen. Wenn sich das Gerät in der Nähe befindet, erhalten Sie Zugriff auf das Gerät, andernfalls wird es mit einem DeviceUnreachable-Fehler zurückgegeben.

Note

Das Erstellen eines BluetoothLEDevice-Objekts durch Aufrufen dieser Methode allein initiiert keine Verbindung (notwendigerweise). Um eine Verbindung zu initiieren, legen Sie GattSession.MaintainConnection auf true, oder rufen Sie eine nicht zwischengespeicherte Dienstermittlungsmethode auf BluetoothLEDevice auf, oder führen Sie einen Lese-/Schreibvorgang für das Gerät aus.

  • Wenn GattSession.MaintainConnection auf "true" festgelegt ist, wartet das System auf unbestimmte Zeit auf eine Verbindung, und es wird verbunden, wenn das Gerät verfügbar ist. Da GattSession.MaintainConnection eine Eigenschaft ist, gibt es nichts, worauf Ihre Anwendung warten könnte.
  • Bei der Dienstsuche und bei Lese- und Schreibvorgängen in GATT wartet das System für eine begrenzte, aber variable Zeit. Alles von sofort bis zu einer Angelegenheit von Minuten. Zu den Faktoren gehören die Auslastung des Stacks und wie lange die Anforderung bereits in der Warteschlange steht. Wenn keine weiteren ausstehenden Anfragen vorliegen und das Remote-Gerät nicht erreichbar ist, wartet das System sieben (7) Sekunden, bevor eine Zeitüberschreitung eintritt. Wenn weitere ausstehende Anfragen vorliegen, kann die Verarbeitung jeder Anfrage in der Warteschlange sieben (7) Sekunden dauern; je weiter Ihre Anfrage also hinten in der Warteschlange steht, desto länger warten Sie.

Derzeit können Sie den Verbindungsprozess nicht abbrechen.

Auflisten unterstützter Dienste und Merkmale

Nachdem Sie nun über ein BluetoothLEDevice-Objekt verfügen, besteht der nächste Schritt darin, zu ermitteln, welche Daten das Gerät verfügbar macht. Der erste Schritt besteht darin, Dienste abzufragen:

GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync();

if (result.Status == GattCommunicationStatus.Success)
{
    var services = result.Services;
    // ...
}

Sobald der Interessendienst identifiziert wurde, besteht der nächste Schritt darin, Merkmale abzufragen.

GattCharacteristicsResult result = await service.GetCharacteristicsAsync();

if (result.Status == GattCommunicationStatus.Success)
{
    var characteristics = result.Characteristics;
    // ...
}

Das Betriebssystem gibt eine ReadOnly-Liste von GattCharacteristic-Objekten zurück, für die Sie dann Vorgänge ausführen können.

Ausführen von Lese-/Schreibvorgängen für ein Merkmal

Das Merkmal ist die grundlegende Einheit der GATT-basierten Kommunikation. Sie enthält einen Wert, der ein bestimmtes Datenstück auf dem Gerät darstellt. Beispielsweise weist das Merkmal des Akkustands einen Wert auf, der den Akkustand des Geräts darstellt.

Lesen Sie die Merkmalseigenschaften, um zu bestimmen, welche Vorgänge unterstützt werden:

GattCharacteristicProperties properties = characteristic.CharacteristicProperties

if(properties.HasFlag(GattCharacteristicProperties.Read))
{
    // This characteristic supports reading from it.
}
if(properties.HasFlag(GattCharacteristicProperties.Write))
{
    // This characteristic supports writing to it.
}
if(properties.HasFlag(GattCharacteristicProperties.Notify))
{
    // This characteristic supports subscribing to notifications.
}

Wenn das Lesen unterstützt wird, können Sie den Wert lesen:

GattReadResult result = await selectedCharacteristic.ReadValueAsync();
if (result.Status == GattCommunicationStatus.Success)
{
    var reader = DataReader.FromBuffer(result.Value);
    byte[] input = new byte[reader.UnconsumedBufferLength];
    reader.ReadBytes(input);
    // Utilize the data as needed
}

Das Schreiben in ein Merkmal folgt einem ähnlichen Muster:

var writer = new DataWriter();
// WriteByte used for simplicity. Other common functions - WriteInt16 and WriteSingle
writer.WriteByte(0x01);

GattCommunicationStatus result = await selectedCharacteristic.WriteValueAsync(writer.DetachBuffer());
if (result == GattCommunicationStatus.Success)
{
    // Successfully wrote to device
}

Tip

DataReader und DataWriter sind unerlässlich, wenn Sie mit den rohen Puffern arbeiten, die Sie von vielen der Bluetooth-APIs erhalten.

Abonnieren von Benachrichtigungen

Stellen Sie sicher, dass das Merkmal entweder Indicate oder Notify unterstützt (überprüfen Sie dazu die Merkmalseigenschaften).

Indicate wird als zuverlässiger angesehen, da jedes Wertänderungsereignis mit einer Bestätigung vom Clientgerät gekoppelt ist. Notify ist häufiger, da die meisten GATT-Transaktionen lieber Energie sparen würden, anstatt extrem zuverlässig zu sein. In jedem Fall wird all dies auf der Controllerebene behandelt, sodass die App nicht einbezogen wird. Wir bezeichnen sie gemeinsam als "Benachrichtigungen".

Es gibt zwei Dinge, die Sie sich kümmern müssen, bevor Sie Benachrichtigungen erhalten:

  • In Client Characteristic Configuration Descriptor (CCCD) schreiben
  • Auf das Characteristic.ValueChanged-Ereignis reagieren

Das Schreiben in die CCCD teilt dem Servergerät mit, dass dieser Client jedes Mal benachrichtigt werden möchte, wenn sich der Wert dieses Merkmals ändert. Gehen Sie dazu wie folgt vor:

GattCommunicationStatus status = await selectedCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                        GattClientCharacteristicConfigurationDescriptorValue.Notify);
if(status == GattCommunicationStatus.Success)
{
    // Server has been informed of clients interest.
}

Jetzt wird das ValueChanged-Ereignis der GattCharacteristic jedes Mal ausgelöst, wenn der Wert auf dem Remotegerät geändert wird. Alles, was übrig ist, besteht darin, den Handler zu implementieren:

characteristic.ValueChanged += Characteristic_ValueChanged;

...

void Characteristic_ValueChanged(GattCharacteristic sender,
                                    GattValueChangedEventArgs args)
{
    // An Indicate or Notify reported that the value has changed.
    var reader = DataReader.FromBuffer(args.CharacteristicValue)
    // Parse the data however required.
}

Beispiele

Ein vollständiges Beispiel finden Sie unter Bluetooth Low Energy sample.