11.7. Operações GATT

Uma característica simplesmente fica no banco de dados GATT como um valor nomeado. 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 quais operações suporta como uma property bitmask – um servidor que não tem nada a expor pode publicar um valor somente leitura, um registrador de controle pode ser somente escrita, um sensor que transmite atualizações definiria o bit notify. O cliente descobre a bitmask durante a descoberta e a respeita.

As cinco operações são read, write, write without response, notify e indicate. Elas se dividem em dois grupos – pull (o cliente pede) e push (o servidor envia).

11.7.1. Pull: read e write

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

  • Read. O cliente pede o valor atual, o servidor o envia de volta. Uma ida e volta, o cliente obtém quaisquer bytes que o servidor tenha definido para aquela característica, e o servidor não recebe nada sobre quem leu.

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

    • Write with response – o servidor confirma, levantando qualquer erro de aplicação em um status diferente de zero. Confiável, uma ida e volta.

    • Write without response – o servidor armazena os bytes silenciosamente; o cliente não recebe nenhuma confirmação. Mais rápido (sem ida e volta aguardando o ack) e útil para streaming, ao custo de descobrir sobre erros apenas via releitura por canal lateral.

Em aioble, a API do lado do cliente esconde a escolha por trá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: notify e indicate

O modelo pull é inadequado para dados de sensor. Uma cinta de frequência cardíaca que o celular tivesse que consultar a cada segundo queimaria bateria em uma centena de eventos de rádio desnecessários; uma que envia um valor apenas quando tem uma nova leitura é justamente o propósito do BLE.

O GATT resolve isso com operações iniciadas pelo servidor. O cliente se inscreve em uma característica; a partir desse ponto, toda vez que o servidor atualiza o valor, o novo valor é enviado através do enlace para o cliente. Duas variantes:

  • Notify. Dispare e esqueça. O servidor enfileira uma notificação, a camada de enlace a transmite durante o próximo evento de conexão, o cliente a recebe. Não há confirmação no nível do GATT; a retransmissão normal da camada de enlace lida com perdas no lado do rádio, mas a aplicação não vê confirmação de que o valor foi processado.

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

Dois diagramas lado a lado de um servidor e um cliente. À esquerda, o cliente envia "read", o servidor responde com o valor. Três leituras em sequência, cada par de setas. À direita, o cliente envia um único "subscribe", e então o servidor envia três pacotes "notify" nos momentos que escolhe, sem qualquer requisição do cliente entre eles.

Pull (read) versus push (notify). Com notificações, o cliente se inscreve uma vez e o servidor envia novos valores sempre que eles mudam.

A inscrição acontece escrevendo em um descritor anexado à característica – o Client Characteristic Configuration Descriptor (CCCD, 0x2902). Escrever 0x0001 habilita notificações, 0x0002 habilita indicações, 0x0000 desabilita ambas. O método aioble.ClientCharacteristic.subscribe() executa a escrita para você, com as flags de palavra-chave notify=True e indicate=True.

Uma vez inscrito, o cliente aguarda os envios recebidos com notified() e indicated() – ambas corrotinas async que suspendem até que o próximo envio chegue.

11.7.3. A MTU governa o tamanho do payload

Toda operação é restringida pela MTU negociada que a conexão definiu no momento do estabelecimento do enlace. A MTU padrão é de 23 bytes, o que deixa 20 bytes para os bytes de valor da característica após o cabeçalho GATT. Qualquer coisa maior que isso tem que ou caber em uma MTU maior (negociada para cima via aioble.DeviceConnection.exchange_mtu(), até 512 bytes na câmera) ou ser dividida em múltiplas características ou múltiplas notificações.

Leituras e escritas iniciadas pelo cliente de valores maiores que a MTU são tratadas pelos procedimentos long do GATT nos bastidores (Read Long / Prepare-Write + Execute-Write); o aioble os executa de forma transparente, então chamar read() / write() com um valor grande demais simplesmente custa mais idas e voltas. Notificações e indicações iniciadas pelo servidor não são fragmentadas – um envio é limitado pela MTU, e a aplicação divide qualquer coisa maior em múltiplas notificações ou abandona o GATT por completo.

Para transferências genuinamente grandes – um quadro capturado, um lote de medições, um blob de firmware – a resposta certa geralmente é abandonar o GATT por completo e usar um L2CAP channel em vez disso (veja Canais L2CAP).

11.7.4. Os dois lados em resumo

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