Cliente GATT do BluetoothBluetooth GATT Client

Este artigo demonstra o uso das APIs de Cliente GATT (Atributo Genérico) do Bluetooth para aplicativos UWP (Plataforma Universal do Windows), juntamente com o código de exemplo para tarefas comuns de cliente GATT:This article demonstrates usage of the Bluetooth Generic Attribute (GATT) Client APIs for Universal Windows Platform (UWP) apps, along with sample code for common GATT client tasks:

  • Consulta de dispositivos próximosQuery for nearby devices
  • Conectar ao dispositivoConnect to device
  • Enumerar os serviços e as características do dispositivo com suporteEnumerate the supported services and characteristics of the device
  • Ler e gravar em uma característicaRead and write to a characteristic
  • Assinar notificações de quando o valor da característica é alteradoSubscribe for notifications when characteristic value changes

Importante

Você deve declarar o recurso "Bluetooth" em Package. appxmanifest.You must declare the "bluetooth" capability in Package.appxmanifest.

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

APIs importantesImportant APIs

Visão geralOverview

Os desenvolvedores podem usar as APIs no namespace Windows.Devices.Bluetooth.GenericAttributeProfile para acessar serviços, descritores e características de dispositivos Bluetooth LE.Developers can use the APIs in the Windows.Devices.Bluetooth.GenericAttributeProfile namespace to access Bluetooth LE devices. Os dispositivos Bluetooth LE expõem sua funcionalidade por meio de coleção de:Bluetooth LE devices expose their functionality through a collection of:

  • ServiçosServices
  • CaracterísticasCharacteristics
  • DescritoresDescriptors

Os serviços definem o contrato funcional do dispositivo LE e contêm uma coleção de características que definem o serviço.Services define the functional contract of the LE device and contain a collection of characteristics that define the service. Essas características, por sua vez, contêm descritores que descrevem as características.Those characteristics, in turn, contain descriptors that describe the characteristics. Estes três termos são conhecidos genericamente como os atributos de um dispositivo.These 3 terms are generically known as the attributes of a device.

As APIs de GATT de Bluetooth LE expõem objetos e funções, em vez de acessarem o transporte bruto.The Bluetooth LE GATT APIs expose objects and functions, rather than access to the raw transport. As APIs de GATT também permitem que os desenvolvedores usem dispositivos Bluetooth LE com a capacidade de executar as seguintes tarefas:The GATT APIs also enable developers to work with Bluetooth LE devices with the ability to perform the following tasks:

  • Executar a descoberta de atributosPerform attribute discovery
  • Ler e Gravar valores de atributoRead and Write attribute values
  • Registrar um retorno de chamada para o evento ValueChanged de CharacteristicRegister a callback for Characteristic ValueChanged event

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.To create a useful implementation a developer must have prior knowledge of the GATT services and characteristics the application intends to consume and to process the specific characteristic values such that the binary data provided by the API is transformed into useful data before being presented to the user. As APIs de GATT de Bluetooth expõem somente primitivas básicas necessárias para comunicação com um dispositivo Bluetooth LE.The Bluetooth GATT APIs expose only the basic primitives required to communicate with a Bluetooth LE device. 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.To interpret the data, an application profile must be defined, either by a Bluetooth SIG standard profile, or a custom profile implemented by a device vendor. 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.A profile creates a binding contract between the application and the device, as to what the exchanged data represents and how to interpret it.

Por conveniência, o Bluetooth SIG mantém uma lista de perfis públicos disponíveis.For convenience the Bluetooth SIG maintains a list of public profiles available.

Consulta de dispositivos próximosQuery for nearby devices

Há dois métodos principais para consultar os dispositivos próximos:There are two main methods to query for nearby devices:

  • DeviceWatcher em Windows.Devices.EnumerationDeviceWatcher in Windows.Devices.Enumeration
  • AdvertisementWatcher em Windows.Devices.Bluetooth.AdvertisementAdvertisementWatcher in Windows.Devices.Bluetooth.Advertisement

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.The 2nd method is discussed at length in the Advertisement documentation so it won't be discussed much here but the basic idea is to find the Bluetooth address of nearby devices that satisfy the particular Advertisement Filter. Quando você tiver o endereço, poderá chamar BluetoothLEDevice.FromBluetoothAddressAsync para obter uma referência ao dispositivo.Once you have the address, you can call BluetoothLEDevice.FromBluetoothAddressAsync to get a reference to the device.

Agora, de volta ao método DeviceWatcher.Now, back to the DeviceWatcher method. Um dispositivo Bluetooth LE é como qualquer outro dispositivo no Windows e pode ser consultado usando as APIs de enumeração.A Bluetooth LE device is just like any other device in Windows and can be queried using the Enumeration APIs. Use a classe DeviceWatcher e passe uma cadeia de caracteres de consulta que especifique os dispositivos a serem procurados:Use the DeviceWatcher class and pass a query string specifying the devices to look for:

// 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.Once you've started the DeviceWatcher, you will receive DeviceInformation for each device that satisfies the query in the handler for the Added event for the devices in question. Para obter uma visão mais detalhada do DeviceWatcher, veja o exemplo completo no Github.For a more detailed look at DeviceWatcher see the complete sample on Github.

Conexão ao dispositivoConnecting to the device

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:Once a desired device is discovered, use the DeviceInformation.Id to get the Bluetooth LE Device object for the device in question:

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.On the other hand, disposing of all references to a BluetoothLEDevice object for a device (and if no other app on the system has a reference to the device) will trigger an automatic disconnect after a small timeout period.

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.If the app needs to access the device again, simply re-creating the device object and accessing a characteristic (discussed in the next section) will trigger the OS to re-connect when necessary. Se o dispositivo estiver próximo, você terá acesso ao dispositivo; caso contrário, ele retornará com um erro DeviceUnreachable.If the device is nearby, you'll get access to the device otherwise it will return w/ a DeviceUnreachable error.

Observação

Criar um objeto BluetoothLEDevice chamando esse método sozinho não (necessariamente) iniciar uma conexão.Creating a BluetoothLEDevice object by calling this method alone doesn't (necessarily) initiate a connection. Para iniciar uma conexão, defina GattSession. MaintainConnection como true ou chame um método de descoberta de serviço não armazenado em cache em BluetoothLEDeviceou execute uma operação de leitura/gravação no dispositivo.To initiate a connection, set GattSession.MaintainConnection to true, or call an uncached service discovery method on BluetoothLEDevice, or perform a read/write operation against the device.

  • Se GattSession. MaintainConnection for definido como true, o sistema aguardará indefinidamente uma conexão e será conectado quando o dispositivo estiver disponível.If GattSession.MaintainConnection is set to true, then the system waits indefinitely for a connection, and it will connect when the device is available. Não há nada para o seu aplicativo aguardar, já que GattSession. MaintainConnection é uma propriedade.There's nothing for your application to wait on, since GattSession.MaintainConnection is a property.
  • Para operações de descoberta de serviço e de leitura/gravação em GATT, o sistema aguarda uma hora finita, mas variável.For service discovery and read/write operations in GATT, the system waits a finite but variable time. Tudo, de imediato a uma questão de minutos.Anything from instantaneous to a matter of minutes. Os fatores informam o tráfego na pilha e o modo como a fila é enfileirada.Factors inclue the traffic on the stack, and how queued up the request is. 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 tarde for para trás da fila, mais tempo você esperará.If there are no other pending request, and the remote device is unreachable, then the system will wait for seven (7) seconds before it times out. If there are other pending requests, then each of the requests in the queue can take seven (7) seconds to process, so the further yours is toward the back of the queue, the longer you'll wait.

No momento, não é possível cancelar o processo de conexão.Currently, you can't cancel the connection process.

Enumeração de serviços e características com suporteEnumerating supported services and characteristics

Agora que você tem um objeto BluetoothLEDevice, a próxima etapa será descobrir quais dados são expostos pelo dispositivo.Now that you have a BluetoothLEDevice object, the next step is to discover what data the device exposes. A primeira etapa para fazer isso é consultar serviços:The first step to do this is to query for services:

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.Once the service of interest has been identified, the next step is to query for characteristics.

GattCharacteristicsResult result = await service.GetCharacteristicsAsync();

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

O sistema operacional retorna uma lista somente leitura de objetos GattCharacteristic em que você poderá executar operações.The OS returns a ReadOnly list of GattCharacteristic objects that you can then perform operations on.

Executar operações de Leitura/Gravação em uma característicaPerform Read/Write operations on a characteristic

A característica é a unidade fundamental da comunicação baseada em GATT.The characteristic is the fundamental unit of GATT based communication. Ela contém um valor que representa uma parte distinta dos dados no dispositivo.It contains a value that represents a distinct piece of data on the device. Por exemplo, a característica de nível de bateria tem um valor que representa o nível da bateria do dispositivo.For example, the battery level characteristic has a value that represents the battery level of the device.

Leia as propriedades das características para determinar quais operações têm suporte:Read the characteristic properties to determine what operations are supported:

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:If read is supported, you can read the value:

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:Writing to a characteristic follows a similar pattern:

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: o DataReader e o DataWriter são indispensáveis ao trabalhar com os buffers brutos obtidos de muitas APIs do Bluetooth.Tip: DataReader and DataWriter are indispensible when working with the raw buffers you get from many of the Bluetooth APIs.

Assinatura de notificaçõesSubscribing for notifications

Verifique se a característica dá suporte a Indicar ou Notificar (verifique as propriedades das características para assegurar-se).Make sure the characteristic supports either Indicate or Notify (check the characteristic properties to make sure).

Aparte: Indicar é considerado mais confiável porque cada evento de valor alterado é associado a uma confirmação do dispositivo cliente.Aside: Indicate is considered more reliable because each value changed event is coupled with an acknowledgement from the client device. Notificar é predominante porque a maioria das transações de GATT conserva energia em vez de ser extremamente confiável.Notify is more prevalent because most GATT transactions would rather conserve power rather than be extremely reliable. Em qualquer caso, tudo isso é manipulado na camada do controlador para que o aplicativo não se envolva.In any case, all of that is handled at the controller layer so the app does not get involved. Vamos nos referir a eles coletivamente como simples "notificações", mas agora você sabe.We'll collectively refer to them as simply "notifications" but now you know.

Há duas coisas que devem ser consideradas antes de receber notificações:There are two things to take care of before getting notifications:

  • Gravar no CCCD (Descritor de Configuração de Característica do Cliente)Write to Client Characteristic Configuration Descriptor (CCCD)
  • Manipular o evento Characteristic.ValueChangedHandle the Characteristic.ValueChanged event

Gravar no CCCD informa ao dispositivo Servidor que esse cliente quer ser informado sobre cada alteração de valor daquela característica em particular.Writing to the CCCD tells the Server device that this client wants to know each time that particular characteristic value changes. Para fazer isso:To do this:

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.Now, the GattCharacteristic's ValueChanged event will get called each time the value gets changed on the remote device. Tudo o que está à esquerda serve para implementar o manipulador:All that's left is to implement the handler:

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