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.
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:
No servidor (o periférico, no esquema comum):
aioble.Characteristic.read()– lê o valor local atual da base de dados GATT (o lado do servidor de «o que veria o cliente»).aioble.Characteristic.write()– atualiza o valor local, opcionalmente enviando a atualização a cada cliente subscrito.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 calcular um valor a pedido.
No cliente (o central, no esquema comum):
aioble.ClientCharacteristic.read()– pede ao servidor o valor atual.aioble.ClientCharacteristic.write()– envia um novo valor, com ou sem resposta.aioble.ClientCharacteristic.subscribe()– ativa / desativa notificações e indicações.aioble.ClientCharacteristic.notified()/indicated()– aguarda o próximo push.