Cliente GATT de Bluetooth

En este artículo se muestra cómo usar las API del Cliente del atributo genérico (GATT) de Bluetooth para aplicaciones de Windows.

Importante

Debes declarar la funcionalidad "bluetooth" en Package.appxmanifest.

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

Overview

Puede usar las API en el Windows. Devices.Bluetooth.GenericAttributeProfile espacio de nombres para acceder a dispositivos Bluetooth LE. Los dispositivos Bluetooth LE exponen su funcionalidad a través de una colección de:

  • Servicios
  • Características
  • Descriptores

Los servicios definen el contrato funcional del dispositivo LE y contienen una colección de características que definen el servicio. Estas características, a su vez, contienen descriptores que describen las características. Estos 3 términos se conocen genéricamente como atributos de un dispositivo.

Las API de GATT de Bluetooth LE exponen objetos y funciones, en lugar de dar acceso al transporte en bruto. Las API GATT también permiten trabajar con dispositivos Bluetooth LE con la capacidad de realizar las siguientes tareas:

  • Realización de la detección de atributos
  • Leer y escribir valores de atributo
  • Registrar una devolución de llamada para el evento Characteristic ValueChanged

Para crear una implementación útil, debe tener conocimientos previos de los servicios y características GATT que la aplicación pretende consumir y procesar los valores de características específicos de forma que los datos binarios proporcionados por la API se transformen en datos útiles antes de presentarse al usuario. Las API gatt de Bluetooth exponen solo los primitivos básicos necesarios para comunicarse con un dispositivo Bluetooth LE. Para interpretar los datos, un perfil de aplicación debe definirse mediante un perfil estándar de Bluetooth SIG o un perfil personalizado implementado por un proveedor de dispositivos. Un perfil crea un contrato de enlace entre la aplicación y el dispositivo, en cuanto a lo que representan los datos intercambiados y cómo interpretarlos.

Para mayor comodidad, bluetooth SIG mantiene una lista de perfiles públicos disponibles.

Consulta de dispositivos cercanos

Hay dos métodos principales para consultar los dispositivos cercanos:

El segundo método se analiza en la documentación del anuncio , por lo que no se analizará mucho aquí, pero la idea básica es encontrar la dirección Bluetooth de los dispositivos cercanos que satisfacen el filtro de anuncios concreto. Una vez que tenga la dirección, puede llamar a BluetoothLEDevice.FromBluetoothAddressAsync para obtener una referencia al dispositivo.

Ahora, vuelva al método DeviceWatcher. Un dispositivo Bluetooth LE es igual que cualquier otro dispositivo en Windows y se puede consultar mediante las API de Enumeration. Use la clase DeviceWatcher y pase una cadena de consulta que especifique los dispositivos que se van a buscar:

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

Una vez que haya iniciado DeviceWatcher, recibirá DeviceInformation para cada dispositivo que cumpla los criterios de la consulta en el controlador del evento Added de dichos dispositivos. Para obtener un vistazo más detallado a DeviceWatcher, consulte el ejemplo completo on Github.

Conexión al dispositivo

Una vez que se detecta un dispositivo deseado, use el DeviceInformation.Id para obtener el objeto Bluetooth LE Device para el dispositivo en cuestión:

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

Por otro lado, la eliminación de todas las referencias a un objeto BluetoothLEDevice para un dispositivo (y si ninguna otra aplicación del sistema tiene una referencia al dispositivo) desencadenará una desconexión automática después de un período de tiempo de espera pequeño.

bluetoothLeDevice.Dispose();

Si la aplicación necesita acceder al dispositivo de nuevo, simplemente volver a crear el objeto de dispositivo y acceder a una característica (que se describe en la sección siguiente) desencadenará el sistema operativo para volver a conectarse cuando sea necesario. Si el dispositivo está cerca, se podrá acceder al dispositivo; de lo contrario, se devolverá el error DeviceUnreachable.

Note

La creación de un objeto BluetoothLEDevice llamando solo a este método no inicia (necesariamente) una conexión. Para iniciar una conexión, establezca GattSession.MaintainConnection en true, o llame a un método de detección de servicios sin caché en BluetoothLEDevice, o realice una operación de lectura/escritura en el dispositivo.

  • Si GattSession.MaintainConnection se establece en true, el sistema espera indefinidamente una conexión y se conectará cuando el dispositivo esté disponible. No hay nada para que la aplicación espere, ya que GattSession.MaintainConnection es una propiedad .
  • En el caso de las operaciones de detección y lectura y escritura de servicios en GATT, el sistema espera un tiempo finito pero variable. Desde algo instantáneo hasta unos minutos. Entre los factores se incluyen el tráfico en la pila y lo encolada que esté la solicitud. Si no hay ninguna otra solicitud pendiente y no se puede alcanzar el dispositivo remoto, el sistema esperará siete (7) segundos antes de que se agote el tiempo de espera. Si hay otras solicitudes pendientes, cada solicitud de la cola puede tardar siete (7) segundos en procesarse, por lo que, cuanto más atrás esté la suya en la cola, más tiempo tendrá que esperar.

Actualmente, no se puede cancelar el proceso de conexión.

Enumerar los servicios y características admitidos

Ahora que tiene un objeto BluetoothLEDevice, el siguiente paso es detectar los datos que expone el dispositivo. El primer paso para ello es consultar los servicios:

GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync();

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

Una vez identificado el servicio de interés, el siguiente paso es consultar las características.

GattCharacteristicsResult result = await service.GetCharacteristicsAsync();

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

El sistema operativo devuelve una lista ReadOnly de objetos GattCharacteristic en los que puede realizar operaciones.

Realizar operaciones de lectura y escritura en una característica

La característica es la unidad fundamental de la comunicación basada en el GATT. Contiene un valor que representa un fragmento de datos distinto en el dispositivo. Por ejemplo, la característica de nivel de batería tiene un valor que representa el nivel de batería del dispositivo.

Lea las propiedades de características para determinar qué operaciones se admiten:

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

Si se admite la lectura, puede leer el valor:

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 escritura en una característica sigue un procedimiento similar:

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 y DataWriter son indispensables al trabajar con los búferes sin procesar que obtiene de muchas de las API de Bluetooth.

Suscribirse a notificaciones

Asegúrese de que la característica admita Indicate o Notify (compruebe las propiedades de la característica para verificarlo).

Indicate se considera más confiable porque cada evento cambiado de valor está acoplado con una confirmación del dispositivo cliente. Notify es más frecuente porque la mayoría de las transacciones GATT prefieren conservar energía en lugar de ser extremadamente confiables. En cualquier caso, todo eso se controla en la capa de controlador para que la aplicación no se implique. Nos referiremos colectivamente a ellos como simplemente "notificaciones".

Hay dos cosas que se deben tener en cuenta antes de recibir notificaciones:

  • Escribir en el descriptor de configuración de características del cliente (CCCD)
  • Control del evento Characteristic.ValueChanged

Al escribir en el CCCD se indica al dispositivo servidor que este cliente quiere saber cada vez que cambia ese valor de característica concreto. Para ello, siga estos pasos:

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

Ahora, se llamará al evento ValueChanged de GattCharacteristic cada vez que se cambie el valor en el dispositivo remoto. Todo lo que queda es implementar el controlador:

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

Examples

Para ver un ejemplo completo, consulte ejemplo de Bluetooth de bajo consumo.