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.
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:
No servidor (o periférico, no layout comum):
aioble.Characteristic.read()– lê o valor local atual do banco de dados GATT (o lado do servidor de “o que o cliente veria”).aioble.Characteristic.write()– atualiza o valor local, opcionalmente enviando a atualização para todos os clientes inscritos.aioble.Characteristic.notify()/indicate()– envia um push para um cliente específico.aioble.Characteristic.written()– aguarda a próxima escrita recebida de qualquer cliente.aioble.Characteristic.on_read()– callback invocado de forma síncrona quando um cliente lê, útil para computar um valor sob demanda.
No cliente (o central, no layout comum):
aioble.ClientCharacteristic.read()– pede ao servidor o valor atual.aioble.ClientCharacteristic.write()– envia um novo valor, com ou sem resposta.aioble.ClientCharacteristic.subscribe()– habilita / desabilita notificações e indicações.aioble.ClientCharacteristic.notified()/indicated()– aguarda o próximo push.