Share via


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. Sin embargo, 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 las comunicaciones 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 del 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 define estos como UUID. Para nuestros fines, estos dos términos son intercambiables, por lo que continuaremos usando el término UUID.

Si el atributo es estándar y definido por el SIG de Bluetooth definido, también tendrá un id. corto de 16 bits correspondiente (por ejemplo, el UUID de nivel de batería es 0000 2A19-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 realiza fácilmente en Visual Studio a través de Herramientas:> CreateGuid (use la opción 5 para obtenerlo en el "xxxxxxxx-xxxx-... formato xxxx". Este uuid ahora se puede usar para declarar nuevos servicios locales, características o descriptores.

Servicios restringidos

Los siguientes servicios están reservados por el sistema y no se pueden publicar en este momento:

  1. Servicio de información de dispositivos (DIS)
  2. Servicio de perfil de atributo genérico (GATT)
  3. Servicio de perfil de acceso genérico (GAP)
  4. Servicio de dispositivo de interfaz humana (HOGP)
  5. Servicio de parámetros de examen (SCP)

Si se intenta crear un servicio bloqueado, BluetoothError.DisabledByPolicy se devolverá 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 se marca como indicable o notificable).
  2. Descripción del usuario característica (si la propiedad UserDescription está establecida). 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 de 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ística (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 de características ReliableWrites y WritableAuxiliaries.

Si se 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 de 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 ha mostrado 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 característicos 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 se debe devolver el servicio 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 los dispositivos remotos en el anuncio, lo que hace que el dispositivo se pueda detectar.
  • 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 se suspenda la aplicación.

Responder a solicitudes de lectura y escritura

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

Lectura

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

Escritura

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 2 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

Las operaciones del servidor GATT más frecuentes, 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 desee elegir qué dispositivos deben enviar el nuevo valor a:

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á cualquier dato mayor que el tamaño máximo.

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