11.7. Operações GATT

Uma característica simplesmente reside na base de dados GATT como um valor com nome. O que a torna útil é o pequeno e bem definido conjunto de operações que um cliente pode executar sobre ela. Cada característica declara as operações que suporta como uma máscara de bits de propriedades – um servidor que não tem nada a expor pode publicar um valor só de leitura, um registo de controlo pode ser só de escrita, um sensor que transmite atualizações definiria o bit de notificação. O cliente descobre a máscara de bits durante a descoberta e respeita-a.

As cinco operações são leitura, escrita, escrita sem resposta, notificação e indicação. Dividem-se em dois grupos – pull (o cliente pede) e push (o servidor envia).

11.7.1. Pull: leitura e escrita

Estas duas são as mais simples e parecem exatamente chamadas de função.

  • Leitura. O cliente pede o valor atual, o servidor envia-o de volta. Uma ida-e-volta, o cliente obtém os bytes que o servidor definiu para essa característica, o servidor não fica a saber quem leu.

  • Escrita. O cliente envia novos bytes, o servidor armazena-os (e opcionalmente executa lógica de aplicação sobre o novo valor). Existem dois tipos:

    • Escrita com resposta – o servidor confirma, levantando qualquer erro de aplicação com um estado não nulo. Fiável, uma ida-e-volta.

    • Escrita sem resposta – o servidor armazena os bytes silenciosamente; o cliente não recebe nenhuma confirmação. Mais rápida (sem ida-e-volta à espera da confirmação) e útil para transmissão, ao custo de descobrir erros apenas via leitura de retorno por canal lateral.

Em aioble, a API do lado do cliente esconde a escolha atrás de um único método aioble.ClientCharacteristic.write() com uma palavra-chave response (True / False / None para selecionar automaticamente com base no que o par anuncia).

11.7.2. Push: notificação e indicação

O modelo pull é inadequado para dados de sensores. Um monitor de frequência cardíaca que o telemóvel teria de sondear a cada segundo desperdiçaria bateria em centenas de eventos de rádio desnecessários; um que envia um valor apenas quando tem uma nova leitura é o verdadeiro propósito do BLE.

O GATT resolve isto com operações iniciadas pelo servidor. O cliente subscreve uma característica; a partir desse momento, sempre que o servidor atualiza o valor, o novo valor é enviado pela ligação para o cliente. Dois tipos:

  • Notificação. Disparar e esquecer. O servidor coloca uma notificação em fila, a camada de ligação transmite-a durante o próximo evento de ligação, o cliente recebe-a. Não há confirmação ao nível GATT; a retransmissão normal da camada de ligação trata das perdas no lado do rádio, mas a aplicação não vê confirmação de que o valor foi processado.

  • Indicação. O servidor envia uma notificação e aguarda a confirmação ao nível GATT do cliente antes de enviar a próxima. Uma indicação de cada vez. Usado quando o servidor precisa de saber que o cliente realmente viu o valor – uma característica de alarme crítico, uma confirmação de configuração.

Two side-by-side diagrams of a server and a client. On the left, the client sends "read", the server replies with the value. Three reads in a row, each pair of arrows. On the right, the client sends a single "subscribe", then the server pushes three "notify" packets at the times it chooses, without any client request in between.

Pull (leitura) versus push (notificação). Com notificações, o cliente subscreve uma vez e o servidor envia novos valores sempre que estes mudam.

A subscrição acontece escrevendo num descritor ligado à característica – o Descritor de Configuração de Característica do Cliente (CCCD, 0x2902). Escrever 0x0001 ativa notificações, 0x0002 ativa indicações, 0x0000 desativa ambas. O método aioble.ClientCharacteristic.subscribe() efetua a escrita por si, com os indicadores de palavra-chave notify=True e indicate=True.

Depois de subscrito, o cliente aguarda pushes recebidos com notified() e indicated() – ambas corrotinas assíncronas que ficam suspensas até chegar o próximo push.

11.7.3. A MTU determina o tamanho da carga útil

Cada operação é limitada pela MTU negociada em que a ligação se fixou no momento da ativação. A MTU predefinida é de 23 bytes, o que deixa 20 bytes para os bytes do valor da característica após o cabeçalho GATT. Qualquer coisa maior do que isso tem de caber numa MTU maior (negociada para cima via aioble.DeviceConnection.exchange_mtu(), até 512 bytes na câmara) ou ser dividida em múltiplas características ou múltiplas notificações.

As leituras e escritas iniciadas pelo cliente de valores maiores que a MTU são tratadas pelos procedimentos long do GATT em segundo plano (Read Long / Prepare-Write + Execute-Write); o aioble executa-os de forma transparente, pelo que chamar read() / write() com um valor de tamanho excessivo apenas custa mais idas-e-voltas. As notificações e indicações iniciadas pelo servidor não são fragmentadas – um push é limitado pela MTU, e a aplicação divide qualquer coisa maior em múltiplas notificações ou abandona o GATT completamente.

Para transferências genuinamente grandes – um fotograma capturado, um lote de medições, um blob de firmware – a resposta certa é geralmente abandonar o GATT completamente e usar um canal L2CAP (consulte Canais L2CAP).

11.7.4. Os dois lados em resumo

As cinco operações expõem-se de forma diferente em cada lado da ligação: