Cliente de GATT de Bluetooth
En este artículo se muestra cómo usar las API de cliente de atributos genéricos de Bluetooth (GATT) para aplicaciones de Plataforma universal de Windows (UWP).
Importante
Debes declarar la funcionalidad "bluetooth" en Package.appxmanifest.
<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>
API importantes
Información general
Los desarrolladores pueden usar las API en el espacio de nombres Windows.Devices.Bluetooth.GenericAttributeProfile 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 de
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 GATT de Bluetooth LE exponen objetos y funciones, en lugar de acceder al transporte sin procesar. Las API GATT también permiten a los desarrolladores 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, un desarrollador 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.
Ejemplos
Para obtener un ejemplo completo, consulta Ejemplo de baja energía de Bluetooth.
Consulta de dispositivos cercanos
Hay dos métodos principales para consultar los dispositivos cercanos:
- DeviceWatcher en Windows.Devices.Enumeration
- BluetoothLEAdvertisementWatcher en Windows.Devices.Bluetooth.Advertisement
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 de Windows y se puede consultar mediante las API de enumeración. 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 satisfaga la consulta en el controlador del evento Added para los dispositivos en cuestión. Para obtener un vistazo más detallado a DeviceWatcher, consulte el ejemplo completo en 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:
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);
// ...
}
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, obtendrá acceso al dispositivo; de lo contrario, devolverá un error DeviceUnreachable.
Nota:
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 o llame a true
un método de detección de servicio sin almacenar en caché en BluetoothLEDevice o realice una operación de lectura y 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. Cualquier cosa de instantánea a cuestión de minutos. Los factores incluyen el tráfico de la pila y cómo se pone en cola la solicitud. Si no hay ninguna otra solicitud pendiente y el dispositivo remoto no es accesible, el sistema esperará siete (7) segundos antes de que se agote el tiempo de espera. Si hay otras solicitudes pendientes, cada una de las solicitudes de la cola puede tardar siete (7) segundos en procesarse, por lo que el resto se dirige hacia la parte posterior de la cola, cuanto más tiempo espere.
Actualmente, no se puede cancelar el proceso de conexión.
Enumeración de 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
}
Escribir en una característica sigue un patrón 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
}
Sugerencia
DataReader y DataWriter son indispensables al trabajar con los búferes sin procesar que obtiene de muchas de las API de Bluetooth.
Suscripción a notificaciones
Asegúrese de que la característica admite Indicar o Notificar (compruebe las propiedades de las características para asegurarse).
Indica que se considera más confiable porque cada evento cambiado de valor está acoplado con una confirmación del dispositivo cliente. La notificación 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.
}