Bluetooth GATT Server
In diesem Thema wird die Verwendung der Bluetooth Generic Attribute (GATT)-Server-APIs für Universelle Windows-Plattform-Apps (UWP) veranschaulicht.
Wichtig
Sie müssen die Funktion „bluetooth“ 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, die erfordern, dass Windows auch als Bluetooth LE GATT-Server fungiert. Fast alle Szenarien für IoT-Geräte sowie die meisten plattformübergreifenden BLE-Kommunikation erfordern Windows als GATT-Server. Darüber hinaus wurde das Senden von Benachrichtigungen an nahe gelegene tragbare Geräte zu einem beliebten Szenario, das diese Technologie erfordert.
Servervorgänge beziehen sich auf den Dienstanbieter und die 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, merkmal und Deskriptor wird durch eine eigene 128-Bit-UUID definiert.
Die Windows-APIs verwenden alle den Begriff GUID, aber der Bluetooth-Standard definiert diese als UUIDs. Zu unseren Zwecken sind diese beiden Begriffe austauschbar, sodass wir weiterhin den Begriff UUID verwenden.
Wenn das Attribut standard ist und von der Bluetooth SIG definiert wird, verfügt es auch über eine entsprechende 16-Bit-Kurz-ID (z. B. Akkustand UUID ist 00002A19-0000-1000-8000-00805F9B34FBB 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 erfolgt ganz einfach in Visual Studio über Tools –> CreateGuid (verwenden Sie Option 5, um sie im "xxxxxxxx-xxxxxx-... 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 zurzeit nicht veröffentlicht werden:
- Geräteinformationsdienst (Device Information Service, DIS)
- Generic Attribute Profile Service (GATT)
- Generic Access Profile Service (GAP)
- Scan Parameters Service (SCP)
Der Versuch, einen blockierten Dienst zu erstellen, führt dazu, dass BluetoothError.DisabledByPolicy vom Aufruf an 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:
- Client-Merkmalskonfiguration (wenn das Merkmal als angibtd oder notifizierbar gekennzeichnet ist).
- Merkmalsbenutzerbeschreibung (wenn die UserDescription-Eigenschaft festgelegt ist). Weitere Informationen finden Sie unter GattLocalCharacteristicParameters.UserDescription-Eigenschaft.
- Merkmalsformat (ein Deskriptor für jedes angegebene Präsentationsformat). Weitere Informationen finden Sie unter GattLocalCharacteristicParameters.PresentationFormats-Eigenschaft.
- Charakteristisches Aggregatformat (wenn mehrere Präsentationsformate angegeben werden). GattLocalCharacteristicParameters.See PresentationFormats-Eigenschaft für weitere Informationen.
- Charakteristische erweiterte Eigenschaften (wenn das Merkmal mit dem bit der erweiterten Eigenschaften markiert 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 zurzeit nicht unterstützt wird. Die Angabe der Broadcast GattCharacteristicProperty führt zu einer Ausnahme.
Aufbau der Hierarchie von Diensten und Merkmalen
Der GattServiceProvider wird zum Erstellen und Ankündigen der primären Stammdienstdefinition verwendet. Jeder Dienst erfordert ein eigenes ServiceProvider-Objekt, das eine GUID verwendet:
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 "Eingeschlossen" oder sekundäre Dienste bezeichnet).
Füllen Sie nun den Dienst mit den erforderlichen Merkmalen und Beschreibungen 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 zum Deklarieren von Ereignishandlern für die Vorgänge, die von den einzelnen Merkmalen unterstützt werden. Um ordnungsgemäß auf Anforderungen zu reagieren, muss eine App einen Ereignishandler für jeden anforderungstyp festlegen, den das Attribut unterstützt. Wenn sie einen Handler nicht registrieren, wird die Anforderung sofort mit UnwahrscheinlichError vom System abgeschlossen.
Konstantenmerkmale
Manchmal gibt es Merkmalswerte, die sich während der Lebensdauer der App nicht ändern. In diesem Fall ist es ratsam, ein konstantes Merkmal zu deklarieren, um 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 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: Kündigt den Anzeigenamen für Remotegeräte in der Ankündigung an und macht das Gerät auffindbar.
- IsConnectable: Kündigt eine verbindebare Werbung für die Verwendung in der Peripherierolle an.
Wenn ein Dienst sowohl discoverable als auch Connectable ist, fügt das System den Dienst Uuid zum Ankündigungspaket hinzu. Es gibt nur 31 Bytes im Ankündigungspaket, und eine UUID der 128-Bit-Version nimmt 16 davon auf!
Beachten Sie, dass beim Veröffentlichen eines Diensts im Vordergrund eine Anwendung StopAdvertising aufrufen muss, wenn die Anwendung angehalten wird.
Antworten auf Lese- und Schreibanforderungen
Wie wir oben gesehen haben, während wir die erforderlichen Merkmale deklarieren, haben GattLocalCharacteristics drei Arten von Ereignissen - ReadRequested, WriteRequested und SubscribedClientsChanged.
Lesen Sie
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 das Lesen aufgerufen wurde, sowie Argen (die Informationen über das Remotegerät enthalten) werden an die Stellvertretung ü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, in das das Merkmal geschrieben werden soll, und den 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 im GattWriteRequest-Objekt), um herauszufinden, welche Art von Schreibzugriff das Remotegerät ausführt.
Senden von Benachrichtigungen an abonnierte Clients
Die am häufigsten verwendeten GATT-Servervorgänge führen Benachrichtigungen die kritische Funktion des Pushens von Daten an die Remotegeräte aus. Manchmal möchten Sie alle abonnierten Clients benachrichtigen, aber in anderen Fällen können 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 GattLocalCharacteristic.SubscribedClientsChanged-Ereignis behandeln, können Sie den unten beschriebenen Prozess verwenden, um vollständige Informationen zu den aktuell abonnierten Clientgeräten zu ermitteln:
- Die Argumente des SubscribedClientsChanged-Ereignisses sind ein GattLocalCharacteristic-Objekt.
- Greifen Sie auf die GattLocalCharacteristic.SubscribedClients-Eigenschaft des Objekts zu, die eine Auflistung von GattSubscribedClient-Objekten ist.
- Durchlaufen Sie diese Sammlung. Führen Sie für jedes Element die folgenden Schritte aus:
- Greifen Sie auf die GattSubscribedClient.Session-Eigenschaft zu, bei der es sich um ein GattSession-Objekt handelt.
- Greifen Sie auf die GattSession.DeviceId-Eigenschaft zu, bei der es sich um ein BluetoothDeviceId-Objekt handelt.
- Greifen Sie auf die eigenschaft BluetoothDeviceId.Id zu, bei der es sich um die Geräte-ID-Zeichenfolge handelt.
- Übergeben Sie die Geräte-ID-Zeichenfolge an BluetoothLEDevice.FromIdAsync, um ein BluetoothLEDevice-Objekt abzurufen. Sie können vollständige Informationen über das Gerät aus diesem Objekt abrufen.