11.6. Serviços e características¶
Depois que o GAP coloca dois dispositivos em uma conexão aberta, a camada acima dele – o Generic Attribute Profile, GATT – precisa dar significado aos bytes que fluem por essa conexão. A escolha do BLE aqui é incomum. Enquanto o TCP expõe um fluxo bruto de bytes e deixa a cargo da aplicação inventar seu próprio enquadramento, o GATT expõe um pequeno banco de dados de chave/valor que um lado hospeda e o outro lê, escreve ou assina.
Esse banco de dados é aquilo em que os projetistas de aplicações passam a maior parte do tempo pensando ao trabalhar com BLE. O que a câmera publica para um telefone, o que ela monitora em um sensor remoto, como um teclado Bluetooth informa ao seu host qual tecla foi pressionada – tudo são valores de característica em algum banco de dados GATT em algum lugar.
11.6.1. Dois eixos de papéis, não um¶
Uma fonte frequente de confusão: peripheral / central e server / client são dois eixos independentes, não sinônimos.
Peripheral e central são papéis GAP, definidos durante a conexão. O peripheral anuncia e recebe a conexão; o central faz a varredura e inicia a conexão. Isso é estabelecido no momento em que o enlace sobe e não muda.
Server e client são papéis GATT, definidos por operação de característica. O server hospeda a característica; o client a lê, escreve ou assina.
Os dois eixos são desacoplados pela especificação. Um peripheral é normalmente o server (uma cinta de frequência cardíaca publica suas leituras) e um central é normalmente o client (um telefone as lê), mas o BLE permite qualquer combinação – um peripheral pode descobrir uma característica no central ao qual acabou de se conectar, ou uma única conexão pode hospedar serviços em ambos os lados ao mesmo tempo.
A maioria das aplicações de câmera segue o emparelhamento convencional (peripheral + server, ou central + client), portanto o restante desta seção os trata como um único eixo quando o caso convencional é o que está sendo descrito. Quando a distinção importa, ambos os termos são explicitados.
11.6.2. Dentro do banco de dados¶
Um banco de dados GATT é uma árvore. As folhas carregam os bytes reais. Os galhos agrupam folhas relacionadas em unidades com significado para o ser humano.
Um banco de dados GATT. Os serviços agrupam características; as características carregam os bytes da aplicação; os descritores carregam metadados sobre a característica.¶
Há três tipos de nó:
Um service é um grupo lógico de valores relacionados. O Bluetooth SIG publica definições de serviço padrão para casos de uso comuns – Battery Service para nível de bateria, Environmental Sensing para temperatura / umidade / pressão, Heart Rate para monitores de frequência cardíaca – de modo que um aplicativo genérico em um telefone consiga reconhecer um serviço que nunca viu antes. Uma aplicação também é livre para definir seus próprios serviços para coisas que o SIG não padronizou.
Uma characteristic é um valor nomeado dentro de um serviço. O serviço Battery tem uma única característica – Battery Level, uma porcentagem de um byte. O Environmental Sensing tem características separadas para temperatura, umidade, pressão e assim por diante. Uma característica é a unidade das operações GATT – você lê uma característica, escreve uma característica, assina uma característica.
Um descriptor é metadado anexado a uma característica. Alguns descritores são padronizados – o Client Characteristic Configuration Descriptor (CCCD) é o mais conhecido, porque escrever nele é a forma como um client diz ao server “envie-me notificações desta característica”. Outros são definidos pelo usuário e carregam coisas como formato de apresentação ou propriedades estendidas.
Um server GATT (tipicamente o peripheral) declara seu banco de dados uma vez na inicialização e o banco de dados não muda durante a execução. Um client GATT (tipicamente o central) descobre o que há no banco de dados após conectar – percorrendo a árvore, lendo os UUIDs dos serviços que encontra e, em seguida, as características dentro de cada um.
11.6.3. UUIDs¶
Todo serviço, característica e descritor tem um UUID (Universally Unique IDentifier) que identifica que tipo de coisa ele é. Os UUIDs vêm em três larguras:
16 bits. Reservados para padrões definidos pelo Bluetooth SIG. O Battery Service é
0x180F. O Battery Level (uma característica) é0x2A19. A lista completa é publicada no site de números atribuídos do Bluetooth SIG, em https://www.bluetooth.com/specifications/assigned-numbers/.32 bits. Um meio-termo raramente usado.
128 bits. O que todos os outros usam – um fabricante ou aplicação gera um aleatoriamente e o utiliza para seu serviço ou característica personalizada. As câmeras que definem seu próprio protocolo ficam aqui.
A classe bluetooth.UUID aceita qualquer uma das três larguras:
import bluetooth
BATTERY_SERVICE = bluetooth.UUID(0x180F)
CUSTOM_SERVICE = bluetooth.UUID("12345678-1234-5678-9abc-def012345678")
Um UUID de 16 bits codifica em um pequeno payload de anúncio, o que é uma das razões pelas quais os serviços padrão são preferíveis quando existe um – uma cinta de frequência cardíaca que anuncia 0x180D (Heart Rate) custa dois bytes; um UUID personalizado custa dezesseis. Para aplicações que não precisam de interoperabilidade padrão, um UUID de 128 bits gerado é a resposta certa.
11.6.4. O que os serviços padronizados pelo SIG oferecem a você¶
O argumento para usar um serviço padrão é direto: aplicativos existentes já sabem como conversar com ele. Um dispositivo que anuncia o serviço Heart Rate (0x180D) e expõe a característica Heart Rate Measurement (0x2A37) funciona com todos os aplicativos de fitness do planeta sem que ninguém precise escrever código novo. Um dispositivo que reimplementa os mesmos dados com UUIDs personalizados precisa de seu próprio aplicativo complementar e de seu próprio documento de protocolo.
Os padrões têm um custo. Os layouts de bytes dentro de cada característica são especificados – o SIG decidiu que o Heart Rate Measurement é um campo de flags de um único byte seguido por um valor de frequência cardíaca de 8 ou 16 bits, opcionalmente seguido por intervalos R-R – e um dispositivo em conformidade precisa seguir esses layouts. Os serviços personalizados estão livres dessa restrição.
A resposta pragmática para as câmeras: use o serviço padrão quando existir um para o tipo de dado que você tem (Battery Service, Environmental Sensing) e defina um personalizado com um UUID de 128 bits para qualquer coisa específica da sua aplicação.
11.6.5. Objetos do lado do servidor e do lado do cliente¶
Para os mesmos blocos de construção conceituais (serviço, característica, descritor), toda biblioteca GATT expõe dois conjuntos paralelos de objetos:
Objetos do lado do servidor – o que o peripheral declara que hospeda. No
aioble:aioble.Service,aioble.Characteristic,aioble.Descriptor. Eles são construídos antes de o anúncio começar e registrados comaioble.register_services().Objetos do lado do cliente – o que o central descobre no par após conectar. No
aioble:aioble.ClientService,aioble.ClientCharacteristic,aioble.ClientDescriptor. Eles são percorridos por meio deaioble.DeviceConnection.service()/services()após conectar.