Cliente GATT do Bluetooth

Este artigo demonstra como usar as APIs de cliente GATT (Atributo Genérico Bluetooth) para aplicativos Plataforma Universal do Windows (UWP).

Importante

Você deve declarar a funcionalidade "bluetooth" em Package.appxmanifest.

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

APIs importantes

Visão geral

Os desenvolvedores podem usar as APIs no namespace Windows.Devices.Bluetooth.GenericAttributeProfile para acessar serviços, descritores e características de dispositivos Bluetooth LE. Os dispositivos Bluetooth LE expõem sua funcionalidade por meio de coleção de:

  • Serviços
  • Características
  • Descritores

Os serviços definem o contrato funcional do dispositivo LE e contêm uma coleção de características que definem o serviço. Essas características, por sua vez, contêm descritores que descrevem as características. Estes três termos são conhecidos genericamente como os atributos de um dispositivo.

As APIs de GATT de Bluetooth LE expõem objetos e funções, em vez de acessarem o transporte bruto. As APIs de GATT também permitem que os desenvolvedores usem dispositivos Bluetooth LE com a capacidade de executar as seguintes tarefas:

  • Executar a descoberta de atributos
  • Ler e Gravar valores de atributo
  • Registrar um retorno de chamada para o evento ValueChanged de Characteristic

Para criar uma implementação útil, um desenvolvedor deve antes conhecer os serviços GATT e características que o aplicativo pretende consumir, e para processar os valores característicos específicos, como aqueles dados binários fornecidos pela API, são transformados em dados úteis antes de serem apresentados ao usuário. As APIs de GATT de Bluetooth expõem somente primitivas básicas necessárias para comunicação com um dispositivo Bluetooth LE. Para interpretar dados, um perfil de aplicativo deve ser definido, tanto pelo perfil padrão de um Bluetooth SIG, como por um perfil personalizado implementado por um fornecedor de dispositivo. Um perfil cria um contrato de ligação entre o aplicativo e o dispositivo, como o que os dados de intercâmbio representam e como ele os interpreta.

Por conveniência, o Bluetooth SIG mantém uma lista de perfis públicos disponíveis.

Exemplos

Para obter um exemplo completo, consulte Exemplo de Bluetooth de Baixa Energia.

Consulta de dispositivos próximos

Há dois métodos principais para consultar os dispositivos próximos:

O segundo método será discutido em detalhes na documentação Anúncio e, portanto, não será mencionado aqui, mas a ideia básica é encontrar o endereço Bluetooth de dispositivos próximos que atendam ao Filtro de Anúncio em particular. Quando você tiver o endereço, poderá chamar BluetoothLEDevice.FromBluetoothAddressAsync para obter uma referência ao dispositivo.

Agora, de volta ao método DeviceWatcher. Um dispositivo Bluetooth LE é como qualquer outro dispositivo no Windows e pode ser consultado usando as APIs de enumeração. Use a classe DeviceWatcher e passe uma cadeia de caracteres de consulta que especifique os dispositivos a serem procurados:

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

Depois de iniciar o DeviceWatcher, você receberá DeviceInformation para cada dispositivo que atenda à consulta no manipulador para o evento Added para os dispositivos em questão. Para obter uma visão mais detalhada do DeviceWatcher, veja o exemplo completo no Github.

Conexão ao dispositivo

Depois que um dispositivo desejado for descoberto, use a DeviceInformation.Id para obter o objeto do Dispositivo Bluetooth LE para o dispositivo em questão:

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 outro lado, descartar todas as referências a um objeto BluetoothLEDevice para um dispositivo (e se nenhum outro aplicativo no sistema tiver uma referência ao dispositivo) disparará uma desconexão automática após um breve período de tempo limite.

bluetoothLeDevice.Dispose();

Se o aplicativo precisar acessar o dispositivo novamente, a simples recriação do objeto de dispositivo e o acesso a uma característica (discutida na próxima seção) acionarão o sistema operacional para que ele se conecte novamente quando necessário. Se o dispositivo estiver próximo, você terá acesso ao dispositivo; caso contrário, ele retornará com um erro DeviceUnreachable.

Observação

Criar um objeto BluetoothLEDevice chamando esse método sozinho não inicia (necessariamente) uma conexão. Para iniciar uma conexão, defina GattSession.MaintainConnectiontruecomo ou chame um método de descoberta de serviço não coletado em BluetoothLEDevice ou execute uma operação de leitura/gravação no dispositivo.

  • Se GattSession.MaintainConnection estiver definido como true, o sistema aguardará indefinidamente por uma conexão e se conectará quando o dispositivo estiver disponível. Não há nada para seu aplicativo esperar, já que GattSession.MaintainConnection é uma propriedade.
  • Para operações de descoberta de serviço e leitura/gravação no GATT, o sistema aguarda um tempo finito, mas variável. Qualquer coisa, de instantânea a questão de minutos. Os fatores incluem o tráfego na pilha e o quão enfileirada é a solicitação. Se não houver outra solicitação pendente e o dispositivo remoto estiver inacessível, o sistema aguardará sete (sete) segundos antes de atingir o tempo limite. Se houver outras solicitações pendentes, cada uma das solicitações na fila poderá levar sete (7) segundos para ser processada, portanto, quanto mais a sua estiver na parte de trás da fila, mais você aguardará.

Atualmente, você não pode cancelar o processo de conexão.

Enumeração de serviços e características com suporte

Agora que você tem um objeto BluetoothLEDevice, a próxima etapa será descobrir quais dados são expostos pelo dispositivo. A primeira etapa para fazer isso é consultar serviços:

GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync();

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

Depois que o serviço desejado tiver sido identificado, a próxima etapa será consultar características.

GattCharacteristicsResult result = await service.GetCharacteristicsAsync();

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

O sistema operacional retorna uma lista ReadOnly de objetos GattCharacteristic nos quais você pode executar operações.

Executar operações de Leitura/Gravação em uma característica

A característica é a unidade fundamental da comunicação baseada em GATT. Ela contém um valor que representa uma parte distinta dos dados no dispositivo. Por exemplo, a característica de nível de bateria tem um valor que representa o nível da bateria do dispositivo.

Leia as propriedades das características para determinar quais operações têm suporte:

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 houver suporte para leitura, você poderá ler o 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
}

Gravar em uma característica segue um padrão semelhante:

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
}

Dica

DataReader e DataWriter são indispensáveis ao trabalhar com os buffers brutos obtidos de muitas das APIs Bluetooth.

Assinatura de notificações

Verifique se a característica dá suporte a Indicar ou Notificar (verifique as propriedades das características para assegurar-se).

Indica que é considerado mais confiável porque cada evento alterado de valor é associado a uma confirmação do dispositivo cliente. Notificar é predominante porque a maioria das transações de GATT conserva energia em vez de ser extremamente confiável. Em qualquer caso, tudo isso é manipulado na camada do controlador para que o aplicativo não se envolva. Vamos nos referir coletivamente a eles como simplesmente "notificações".

Há duas coisas que devem ser consideradas antes de receber notificações:

  • Gravar no CCCD (Descritor de Configuração de Característica do Cliente)
  • Manipular o evento Characteristic.ValueChanged

Gravar no CCCD informa ao dispositivo Servidor que esse cliente quer ser informado sobre cada alteração de valor daquela característica em particular. Para fazer isso:

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

Agora, evento ValueChanged da GattCharacteristic será chamado sempre que o valor for alterado no dispositivo remoto. Tudo o que está à esquerda serve para implementar o manipulador:

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