Client GATT Bluetooth

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

Importante

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

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

API importanti

Panoramica

Gli sviluppatori possono 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:

  • Servizi
  • 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 inoltre agli sviluppatori 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
  • Registrazione di un callback per l'evento Characteristic ValueChanged

Per creare un'implementazione utile, uno sviluppatore deve avere una conoscenza precedente dei servizi GATT e delle 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 i dati primitivi di base necessari 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 che è stato 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 praticità, Il SIG Bluetooth mantiene un elenco di profili pubblici disponibili.

Esempi

Per un esempio completo, vedi l'esempio Bluetooth Low Energy.

Eseguire una query per i 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 è di trovare l'indirizzo Bluetooth dei dispositivi nelle vicinanze che soddisfano il particolare Filtro annunci. Dopo aver ottenuto l'indirizzo, è possibile richiamare BluetoothLEDevice.FromBluetoothAddressAsync per ottenere un riferimento al dispositivo.

Ritornare a questo punto al metodo DeviceWatcher. Un dispositivo Bluetooth LE è proprio come qualsiasi altro dispositivo in Windows e può essere sottoposto a query usando le API di enumerazione. 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, l'utente riceverà DeviceInformation per ogni dispositivo che soddisfa la query nel gestore per l'evento Added dei dispositivi in questione. Per un'analisi più dettagliata di DeviceWatcher, vedere l'esempio completo in Github.

Connessione al dispositivo

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

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

Dall'altro lato, 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'applicazione ha bisogno di accedere di nuovo al dispositivo, è sufficiente ricreare l'oggetto dispositivo e accedere a una caratteristica (descritta nella sezione successiva) per far sì che il sistema operativo si riconnetta quando necessario. Se il dispositivo è nelle vicinanze, si otterrà l'accesso al dispositivo. In caso contrario riporterà un errore DeviceUnreachable.

Nota

La creazione di un oggetto BluetoothLEDevice richiamando questo metodo da solo non avvia necessariamente una connessione. Per avviare una connessione, impostare GattSession.MaintainConnection su true, o 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 vero, il sistema attende per un periodo illimitato per una connessione e si connetterà quando il dispositivo è disponibile. Non c'è nulla che l'applicazione attenda, poiché 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 più è in fondo alla coda, più lunga sarà l'attesa.

Attualmente, non è possibile annullare il processo di connessione.

Enumerazione di servizi e 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 una caratteristica

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 in una caratteristica segue un modello 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
}

Suggerimento

DataReader e DataWriter sono indispensabili quando si lavora con i buffer non elaborati che si ottengono da molte API Bluetooth.

Sottoscrizione per le notifiche

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

L'indicazione è considerata più affidabile perché ogni evento di modifica del valore è associato a una conferma dal dispositivo client. La notifica è più diffusa 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:

  • Scrivere nel descrittore di configurazione delle caratteristiche 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. A questo scopo, è necessario:

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