11.11. Canais L2CAP¶
O GATT é um modelo chave/valor. As operações que oferece (leitura, escrita, notificação, indicação) movem um valor curto de cada vez, e a maior carga útil singular que conseguem transportar é aquela que o MTU negociado permite – algumas centenas de bytes, no melhor caso. Isto funciona bem para leituras de sensores, registos de comandos e sinalizadores de estado. Falha com kilobytes ou megabytes: dividir uma mancha longa em centenas de escritas pequenas custa percursos de ida e volta que o rádio consegue fazer muito mais rapidamente.
Para fluxos de dados em massa – um fotograma capturado que a câmara transmite para um telemóvel, uma imagem de atualização over-the-air, uma exportação em lote de medições – o BLE oferece um caminho alternativo: o Logical Link Control and Adaptation Protocol, L2CAP. O L2CAP situa-se entre a camada de ligação e o GATT e permite que uma aplicação reivindique o seu próprio canal orientado à ligação sobre a mesma ligação de rádio. O canal é um caminho de bytes controlado por créditos de fluxo, com um MTU por pacote muito maior e sem enquadramento GATT no meio.
11.11.1. Quando usar L2CAP¶
Os canais L2CAP são a ferramenta adequada quando:
A transferência tem mais de algumas centenas de bytes.
Ambas as extremidades sabem que será utilizado um canal L2CAP (não está exposto na carga útil de publicidade; o cliente tem de conhecer o número do protocol/service multiplexer, ou PSM, do canal fora de banda).
A aplicação está disposta a prescindir das conveniências do GATT: sem endereçabilidade por UUID incorporada, sem capacidade de descoberta pelo cliente através de aplicações padrão, sem notificações.
O caso mais comum em aplicações baseadas em aioble é mover uma mancha binária entre dois componentes de software que conhecem a convenção PSM – um protocolo personalizado câmara-para-telemóvel, um par de câmaras openmv a comunicar entre si, um caminho interno de atualização de firmware sob o serviço GATT de um periférico.
Para tudo o resto, fique no GATT. Um estado curto, um registo de controlo, uma leitura de sensor – todos esses pertencem a uma característica.
11.11.2. Estabelecer um canal¶
O L2CAP corre em cima de uma aioble.DeviceConnection existente, pelo que o fluxo de descoberta / publicidade / ligação do lado GAP é exatamente o mesmo que para o GATT. Quando ambos os lados têm uma ligação, um lado fica à escuta num PSM e o outro lado liga-se a ele.
O PSM é apenas um pequeno inteiro. O Bluetooth SIG reserva a parte inferior do intervalo para uso normalizado (0x0001-0x007F); para canais específicos de aplicação, use um número do intervalo dinâmico (0x0080-0x00FF para PSMs fixos, 0x0040 em diante tipicamente livre para uso personalizado). Ambos os lados têm de concordar no valor previamente.
O MTU num canal L2CAP é a maior SDU (Service Data Unit) singular que qualquer lado entregará numa send() – não o MTU da ligação BLE. O Aioble fragmenta automaticamente cargas úteis maiores. O host BLE da câmara limita o MTU L2CAP a 1017 bytes; 512 é um valor predefinido sensato que deixa margem em ambos os lados sem desperdiçar RAM.
No lado do ouvinte (p. ex., a câmara como periférico):
async def serve_l2cap(connection, image_bytes):
channel = await connection.l2cap_accept(psm=0x80, mtu=512)
async with channel:
# image_bytes is a bytearray -- e.g. csi0.snapshot().bytearray()
# or a compressed JPEG buffer. send() fragments into MTU-sized
# chunks automatically and awaits flow-control credits between.
await channel.send(image_bytes)
await channel.flush()
No lado do conector (p. ex., um telemóvel ou central):
async def open_l2cap(connection, total_bytes):
channel = await connection.l2cap_connect(psm=0x80, mtu=512)
async with channel:
image_bytes = bytearray(total_bytes)
view = memoryview(image_bytes)
received = 0
while received < total_bytes:
n = await channel.recvinto(view[received:])
if n == 0:
break
received += n
return image_bytes
l2cap_accept() bloqueia até que o par se ligue (ou timeout_ms expire); l2cap_connect() bloqueia até que o ouvinte aceite (ou falhe). Ambos devolvem um aioble.L2CAPChannel – que é em si um gestor de contexto assíncrono que fecha o canal ao sair.
11.11.3. Enviar e receber¶
As duas operações principais num canal são send() (escreve bytes para o par) e recvinto() (lê para um buffer pré-alocado). Ambas são corrotinas.
send()fragmenta o buffer em blocos do tamanho do MTU e aguarda créditos de controlo de fluxo da camada de ligação entre eles. Um envio longo é um únicoawaitda perspetiva da aplicação; internamente pode colocar muitos pacotes em fila e pausar sempre que os créditos de receção do par se esgotem.recvinto()preenche o buffer passado com o que estiver disponível (até ao MTU do canal) e devolve a contagem de bytes. Aguarda se não houver nada disponível.available()devolveTruede forma síncrona se houver dados em buffer prontos – útil para sondagem sem suspensão.flush()aguarda até que qualquer envio pendente tenha sido totalmente transmitido ao controlador.
Os canais L2CAP são semelhantes a fluxos no sentido em que os bytes chegam por ordem e sem perdas, mas os limites de um único send são preservados – cada SDU sai de um único recvinto. Isto é diferente do TCP, onde os limites de um send() podem distribuir-se por múltiplas chamadas recv().
11.11.4. Tratamento de desligamento¶
O canal desaparece em três condições: qualquer um dos lados chama disconnect(), a ligação GAP subjacente cai, ou chega o desligamento a nível L2CAP. Operações ativas levantam aioble.L2CAPDisconnectedError. Tal como no lado GATT, isto surge como uma exceção na corrotina que estava à espera, e o bloco async with channel sai de forma limpa.
Se um canal ficar inacessível devido a um desligamento ao nível GAP, a aplicação volta a publicitar ou a fazer scan da mesma forma que faria para um desligamento GATT.
11.11.5. Custo de memória¶
MTUs maiores e filas mais longas usam mais RAM em ambos os lados. Um MTU de 512 bytes mais um buffer de receção por canal representa cerca de 1 KB por canal – não é gratuito numa câmara pequena se vários canais estiverem abertos ao mesmo tempo. Mantenha-se num canal por ligação e escolha um MTU que corresponda ao tamanho esperado das mensagens; a predefinição de um L2CAPChannel por DeviceConnection é suficiente para a maioria das aplicações.
O L2CAP é a válvula de segurança do BLE. O GATT é aquilo a que quase todas as aplicações recorrem em primeiro lugar, e os exemplos central/periférico do resto desta secção ficam no GATT. A API orientada a canais é a resposta quando uma aplicação ultrapassa o modelo chave/valor.