Cliente GATT Bluetooth

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

Importante

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

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

Overview

Você pode usar as APIs no Windows. Devices.Bluetooth.GenericAttributeProfile namespace para acessar dispositivos Bluetooth LE. Os dispositivos BLUETOOTH LE expõem suas funcionalidades por meio de uma coleção de:

  • Services
  • 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. Esses 3 termos são genericamente conhecidos como os atributos de um dispositivo.

As APIs BLUETOOTH LE GATT expõem objetos e funções, em vez de acesso ao transporte bruto. As APIs gatt também permitem que você trabalhe com dispositivos Bluetooth LE com a capacidade de executar as seguintes tarefas:

  • Executar descoberta de atributo
  • Ler e gravar valores de atributos
  • Registrar um callback para o evento ValueChanged da Característica

Para criar uma implementação útil, você deve ter conhecimento prévio dos serviços e das características GATT que a aplicação pretende consumir, bem como processar os valores específicos das características, de modo que os dados binários fornecidos pela API sejam transformados em dados úteis antes de serem apresentados ao usuário. As APIs Bluetooth GATT disponibilizam apenas as primitivas básicas necessárias para a comunicação com um dispositivo Bluetooth LE. Para interpretar os dados, um perfil de aplicativo deve ser definido, por um perfil padrão do Bluetooth SIG ou por um perfil personalizado implementado por um fornecedor de dispositivos. Um perfil cria um contrato de associação entre o aplicativo e o dispositivo, quanto ao que os dados trocados representam e como interpretá-los.

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

Buscar dispositivos próximos

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

O segundo método é discutido longamente na documentação do Anúncio para que ele não seja muito discutido aqui, mas a ideia básica é encontrar o endereço Bluetooth de dispositivos próximos que satisfaçam o filtro de anúncio específico. Depois de ter o endereço, você pode 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 Enumeration. Use a classe DeviceWatcher e passe uma cadeia de caracteres de consulta especificando os dispositivos para procurar:

// 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 satisfaça a consulta no manipulador do evento Added para os dispositivos em questão. Para obter uma visão mais detalhada do DeviceWatcher, consulte o exemplo completo on Github.

Conectar-se ao dispositivo

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

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 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 pequeno período de tempo limite.

bluetoothLeDevice.Dispose();

Se o aplicativo precisar acessar o dispositivo novamente, basta recriar o objeto do dispositivo e acessar uma característica (discutida na próxima seção) disparará o sistema operacional para se conectar novamente quando necessário. Se o dispositivo estiver próximo, você obterá acesso ao dispositivo caso contrário, ele retornará com um erro DeviceUnreachable.

Note

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

  • Se GattSession.MaintainConnection for definido como true, o sistema aguardará indefinidamente uma conexão e se conectará quando o dispositivo estiver disponível. Não há nada para o qual seu aplicativo precise aguardar, 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 nível de enfileiramento da solicitação. Se não houver nenhuma outra solicitação pendente e o dispositivo remoto estiver inacessível, o sistema aguardará sete (7) 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 longe a sua estiver na parte de trás da fila, mais tempo você aguardará.

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

Enumerar serviços e características com suporte

Agora que você tem um objeto BluetoothLEDevice, a próxima etapa é descobrir quais dados o dispositivo expõe. 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 de interesse tiver sido identificado, a próxima etapa é consultar as 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 em que você pode executar operações.

Executar operações de leitura e gravação em uma característica

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

Leia as propriedades 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
}

Escrever 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 que você obtém de muitas das APIs Bluetooth.

Inscrever-se para notificações

Certifique-se de que a característica oferece suporte a Indicate ou Notify (verifique as propriedades da característica para ter certeza).

Indicate é considerado mais confiável porque cada evento alterado de valor é associado a uma confirmação do dispositivo cliente. Notify é mais prevalente porque a maioria das transações GATT prioriza a economia de energia em vez de oferecer máxima confiabilidade. Em qualquer caso, tudo isso é tratado 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 para cuidar antes de receber notificações:

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

Gravar no CCCD informa ao dispositivo Server que esse cliente deseja saber sempre que esse valor de característica específico for alterado. Para fazer isso:

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

Agora, o evento ValueChanged da GattCharacteristic será chamado sempre que o valor for alterado no dispositivo remoto. Tudo o que resta é 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.
}

Exemplos

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