Compartir a través de


Servidor de GATT de Bluetooth

En este tema se muestra cómo usar las API de servidor 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

Windows normalmente funciona en el rol de cliente. No obstante, surgen muchos escenarios que requieren que Windows actúe también como un servidor GATT de Bluetooth LE. Casi todos los escenarios para dispositivos IoT, junto con la mayoría de la comunicación BLE multiplataforma requerirán que Windows sea un servidor GATT. Además, el envío de notificaciones a dispositivos portátiles cercanos se ha convertido en un escenario popular que también requiere esta tecnología.

Las operaciones de servidor girarán en torno al proveedor de servicios y a la gattlocalCharacteristic. Estas dos clases proporcionarán la funcionalidad necesaria para declarar, implementar y exponer una jerarquía de datos en un dispositivo remoto.

Definición de los servicios admitidos

La aplicación puede declarar uno o varios servicios publicados por Windows. Cada servicio se identifica de forma única mediante un UUID.

Atributos e UUID

Cada servicio, característica y descriptor se define mediante su propio UUID de 128 bits único.

Todas las API de Windows usan el término GUID, pero el estándar Bluetooth los define como UUID. Para nuestros propósitos, estos dos términos son intercambiables, por lo que continuaremos usando el término UUID.

Si el atributo es estándar y está definido por el SIG de Bluetooth, también tendrá un identificador corto de 16 bits correspondiente (por ejemplo, el UUID de nivel de batería es 00002A19-0000-1000-8000-00805F9B34FB y el identificador corto es 0x2A19). Estos UUID estándar se pueden ver en GattServiceUuids y GattCharacteristicUuids.

Si la aplicación implementa su propio servicio personalizado, tendrá que generarse un UUID personalizado. Esto se hace fácilmente en Visual Studio a través de Herramientas:> CreateGuid (use la opción 5 para obtenerlo en "xxxxxxxx-xxxx-... formato xxxx". Este uuid ahora se puede usar para declarar nuevos servicios locales, características o descriptores.

Servicios restringidos

El sistema reserva los siguientes servicios y no se puede publicar en este momento:

  1. Device Information Service (DIS)
  2. Servicio de perfil de atributo genérico (GATT)
  3. Servicio de perfil de acceso genérico (GAP)
  4. Servicio de parámetros de examen (SCP)

Si intenta crear un servicio bloqueado, se devolverá BluetoothError.DisabledByPolicy desde la llamada a CreateAsync.

Atributos generados

El sistema genera automáticamente los descriptores siguientes, basados en GattLocalCharacteristicParameters proporcionados durante la creación de la característica:

  1. Configuración de características del cliente (si la característica está marcada como indicable o notificable).
  2. Descripción de usuario característica (si se establece la propiedad UserDescription). Consulta la propiedad GattLocalCharacteristicParameters.UserDescription para obtener más información.
  3. Formato de característica (un descriptor para cada formato de presentación especificado). Consulta la propiedad GattLocalCharacteristicParameters.PresentationFormats para obtener más información.
  4. Formato agregado característico (si se especifica más de un formato de presentación). Propiedad GattLocalCharacteristicParameters.See PresentationFormats para obtener más información.
  5. Propiedades extendidas características (si la característica está marcada con el bit de propiedades extendidas).

El valor del descriptor De propiedades extendidas se determina a través de las propiedades características ReliableWrites y WritableAuxiliaries.

Si intenta crear un descriptor reservado, se producirá una excepción.

Tenga en cuenta que la difusión no se admite en este momento. Si se especifica Broadcast GattCharacteristicProperty, se producirá una excepción.

Creación de la jerarquía de servicios y características

GattServiceProvider se usa para crear y anunciar la definición del servicio principal raíz. Cada servicio requiere su propio objeto ServiceProvider que toma un GUID:

GattServiceProviderResult result = await GattServiceProvider.CreateAsync(uuid);

if (result.Error == BluetoothError.Success)
{
    serviceProvider = result.ServiceProvider;
    // 
}

Los servicios principales son el nivel superior del árbol GATT. Los servicios principales contienen características, así como otros servicios (denominados "Incluidos" o servicios secundarios).

Ahora, rellene el servicio con las características y descriptores necesarios:

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;

Como se muestra anteriormente, también es un buen lugar para declarar controladores de eventos para las operaciones que admite cada característica. Para responder a las solicitudes correctamente, una aplicación debe definir y establecer un controlador de eventos para cada tipo de solicitud que admita el atributo. Si no se registra un controlador, la solicitud se completará inmediatamente con UnlikelyError por el sistema.

Características constantes

A veces, hay valores de características que no cambiarán durante la duración de la aplicación. En ese caso, es aconsejable declarar una característica constante para evitar la activación innecesaria de la aplicación:

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

Publicación del servicio

Una vez que el servicio se haya definido por completo, el siguiente paso es publicar la compatibilidad con el servicio. Esto informa al sistema operativo de que el servicio debe devolverse cuando los dispositivos remotos realizan una detección de servicios. Tendrá que establecer dos propiedades: IsDiscoverable e IsConnectable:

GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
    IsDiscoverable = true,
    IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
  • IsDiscoverable: anuncia el nombre descriptivo para dispositivos remotos en el anuncio, lo que hace que el dispositivo sea reconocible.
  • IsConnectable: anuncia un anuncio conectable para su uso en el rol periférico.

Cuando un servicio es Detectable y Connectable, el sistema agregará el Uuid de servicio al paquete de anuncio. Solo hay 31 bytes en el paquete Anuncio y un UUID de 128 bits ocupa 16 de ellos!

Tenga en cuenta que cuando un servicio se publica en primer plano, una aplicación debe llamar a StopAdvertising cuando la aplicación se suspende.

Responder a solicitudes de lectura y escritura

Como vimos anteriormente al declarar las características necesarias, GattLocalCharacteristics tiene 3 tipos de eventos: ReadRequested, WriteRequested y SubscribedClientsChanged.

Leer

Cuando un dispositivo remoto intenta leer un valor de una característica (y no es un valor constante), se llama al evento ReadRequested. La característica en la que se llamó a la lectura, así como los argumentos (que contienen información sobre el dispositivo remoto) se pasa al delegado:

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

Escribir

Cuando un dispositivo remoto intenta escribir un valor en una característica, se llama al evento WriteRequested con detalles sobre el dispositivo remoto, que característica para escribir en y el propio valor:

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

Hay dos tipos de escrituras: con y sin respuesta. Use GattWriteOption (una propiedad en el objeto GattWriteRequest) para averiguar qué tipo de escritura está realizando el dispositivo remoto.

Envío de notificaciones a clientes suscritos

La más frecuente de las operaciones del servidor GATT, las notificaciones realizan la función crítica de insertar datos en los dispositivos remotos. A veces, querrá notificar a todos los clientes suscritos, pero en otras ocasiones es posible que quiera elegir los dispositivos a los que enviar el nuevo valor:

async void NotifyValue()
{
    var writer = new DataWriter();
    // Populate writer with data
    // ...
    
    await notifyCharacteristic.NotifyValueAsync(writer.DetachBuffer());
}

Cuando un nuevo dispositivo se suscribe a las notificaciones, se llama al evento SubscribedClientsChanged:

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

Nota:

La aplicación puede obtener el tamaño máximo de notificación de un cliente determinado con la propiedad MaxNotificationSize . El sistema truncará los datos mayores que el tamaño máximo.

Al controlar el evento GattLocalCharacteristic.SubscribedClientsChanged , puede usar el proceso descrito a continuación para determinar información completa sobre los dispositivos cliente suscritos actualmente: