Bluetooth GATT Client

Questo articolo illustra come usare le API client GATT (Bluetooth Generic Attribute) per le app Windows.

Important

È necessario dichiarare la funzionalità "bluetooth" in Package.appxmanifest.

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

Overview

È possibile usare le API nello spazio dei nomi Windows.Devices.Bluetooth.GenericAttributeProfile per accedere ai dispositivi Bluetooth LE. I dispositivi Bluetooth LE espongono le funzionalità tramite una raccolta di:

  • Services
  • Caratteristiche
  • Descrittori

I servizi definiscono il contratto funzionale del dispositivo LE e contengono una raccolta di caratteristiche che definiscono il servizio. Queste caratteristiche, a loro volta, contengono descrittori che descrivono le caratteristiche. Questi 3 termini sono noti in modo generico come attributi di un dispositivo.

Le API GATT Bluetooth LE espongono oggetti e funzioni, anziché accedere al trasporto non elaborato. Le API GATT consentono anche di lavorare con i dispositivi Bluetooth LE con la possibilità di eseguire le attività seguenti:

  • Eseguire l'individuazione degli attributi
  • Lettura e scrittura dei valori degli attributi
  • Registra un callback per l'evento Characteristic ValueChanged

Per creare un'implementazione utile è necessario conoscere in precedenza i servizi GATT e le caratteristiche che l'applicazione intende utilizzare e elaborare i valori di caratteristica specifici in modo che i dati binari forniti dall'API vengano trasformati in dati utili prima di essere presentati all'utente. Le API GATT Bluetooth espongono solo le primitive di base necessarie per comunicare con un dispositivo Bluetooth LE. Per interpretare i dati, è necessario definire un profilo dell'applicazione, da un profilo standard BLUETOOTH SIG o da un profilo personalizzato implementato da un fornitore di dispositivi. Un profilo crea un contratto di associazione tra l'applicazione e il dispositivo, in base a ciò che i dati scambiati rappresentano e come interpretarlo.

Per comodità, il Bluetooth SIG mantiene disponibile un elenco di profili pubblici.

Cercare dispositivi nelle vicinanze

Esistono due metodi principali per eseguire query per i dispositivi nelle vicinanze:

Il secondo metodo viene discusso a lungo nella documentazione dell'annuncio, quindi non verrà discusso molto qui, ma l'idea di base è trovare l'indirizzo Bluetooth dei dispositivi nelle vicinanze che soddisfano il particolare filtro annunci. Dopo aver ottenuto l'indirizzo, puoi chiamare BluetoothLEDevice.FromBluetoothAddressAsync per ottenere un riferimento al dispositivo.

Tornare ora al metodo DeviceWatcher. Un dispositivo Bluetooth LE è proprio come qualsiasi altro dispositivo in Windows e può essere sottoposto a query usando le API di Enumeration. Usare la classe DeviceWatcher e passare una stringa di query che specifica i dispositivi da cercare:

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

Dopo aver avviato DeviceWatcher, riceverai DeviceInformation per ogni dispositivo che soddisfa la query nel gestore per l'evento Added per i dispositivi in questione. Per un'analisi più dettagliata di DeviceWatcher, vedere l'esempio completo on Github.

Connettersi al dispositivo

Dopo aver individuato un dispositivo desiderato, usare il DeviceInformation.Id per ottenere l'oggetto Dispositivo Bluetooth LE per il dispositivo in questione:

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);
    // ...
}

D'altra parte, l'eliminazione di tutti i riferimenti a un oggetto BluetoothLEDevice per un dispositivo (e se nessun'altra app nel sistema ha un riferimento al dispositivo) attiverà una disconnessione automatica dopo un breve periodo di timeout.

bluetoothLeDevice.Dispose();

Se l'app deve accedere di nuovo al dispositivo, è sufficiente creare nuovamente l'oggetto dispositivo e accedere a una caratteristica (descritta nella sezione successiva) attiverà il sistema operativo per riconnettersi quando necessario. Se il dispositivo è nelle vicinanze, si otterrà l'accesso al dispositivo in caso contrario restituirà un errore DeviceUnreachable.

Note

La creazione di un oggetto BluetoothLEDevice chiamando questo metodo da solo non avvia necessariamente una connessione. Per avviare una connessione, impostare GattSession.MaintainConnection su trueo chiamare un metodo di individuazione del servizio non memorizzato nella cache in BluetoothLEDevice oppure eseguire un'operazione di lettura/scrittura sul dispositivo.

  • Se GattSession.MaintainConnection è impostato su true, il sistema attende a tempo indeterminato una connessione e si connette quando il dispositivo è disponibile. Non c'è nulla che l'applicazione attenda, perché GattSession.MaintainConnection è una proprietà.
  • Per le operazioni di individuazione e lettura/scrittura del servizio in GATT, il sistema attende un tempo finito ma variabile. Qualsiasi cosa, da istantaneo a una questione di minuti. I fattori includono il traffico nello stack e la modalità di accodamento della richiesta. Se non sono presenti altre richieste in sospeso e il dispositivo remoto non è raggiungibile, il sistema attenderà sette (7) secondi prima del timeout. Se sono presenti altre richieste in sospeso, ognuna delle richieste nella coda può richiedere sette (7) secondi per l'elaborazione, quindi l'ulteriore percorso è verso la parte posteriore della coda, maggiore sarà l'attesa.

Attualmente, non è possibile annullare il processo di connessione.

Enumerare i servizi e le caratteristiche supportati

Ora che si dispone di un oggetto BluetoothLEDevice, il passaggio successivo consiste nell'individuare i dati esposti dal dispositivo. Il primo passaggio per eseguire questa operazione consiste nell'eseguire query per i servizi:

GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync();

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

Dopo aver identificato il servizio di interesse, il passaggio successivo consiste nell'eseguire una query sulle caratteristiche.

GattCharacteristicsResult result = await service.GetCharacteristicsAsync();

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

Il sistema operativo restituisce un elenco ReadOnly di oggetti GattCharacteristic su cui è possibile eseguire operazioni.

Eseguire operazioni di lettura e scrittura su un attributo

La caratteristica è l'unità fondamentale della comunicazione basata su GATT. Contiene un valore che rappresenta una parte distinta di dati nel dispositivo. Ad esempio, la caratteristica del livello della batteria ha un valore che rappresenta il livello della batteria del dispositivo.

Leggere le proprietà delle caratteristiche per determinare quali operazioni sono supportate:

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

Se la lettura è supportata, è possibile leggere il valore:

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
}

La scrittura su una caratteristica segue uno schema simile:

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 e DataWriter sono indispensabili quando si lavora con i buffer non elaborati che si ottengono da molte API Bluetooth.

Sottoscrivere le notifiche

Assicurarsi che la caratteristica supporti Indicate o Notify (controllare le proprietà delle caratteristiche per assicurarsi).

Indicate è considerato più affidabile perché ogni evento di modifica del valore è associato a un acknowledgement dal dispositivo client. Notify è più diffuso perché la maggior parte delle transazioni GATT preferirebbe risparmiare energia anziché essere estremamente affidabile. In ogni caso, tutto ciò viene gestito a livello di controller in modo che l'app non venga coinvolta. Si farà riferimento collettivamente a loro come semplicemente "notifiche".

Prima di ricevere notifiche, è necessario occuparsi di due aspetti:

  • Scrivi nel descrittore di configurazione della caratteristica del client (CCCD)
  • Gestire l'evento Characteristic.ValueChanged

La scrittura in CCCD indica al dispositivo Server che questo client vuole conoscere ogni volta che cambia il valore di una caratteristica specifica. Per eseguire questa operazione:

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

Ora, l'evento ValueChanged di GattCharacteristic verrà chiamato ogni volta che il valore viene modificato nel dispositivo remoto. Tutto ciò che rimane è implementare il gestore:

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

Esempi

Per un esempio completo, vedi esempio Bluetooth Low Energy.