11.8. O módulo aioble¶
A especificação Bluetooth Core fornece um vocabulário que se mapeia para dois módulos MicroPython.
bluetooth– a ligação de baixo nível com o controlador BLE. Síncrona, orientada a eventos através de um callback no estilo de IRQ, e estruturada em torno de buffers de bytes, handles e as primitivas GATT puras. Ela expõe o protocolo como ele é, não como as aplicações Python querem consumi-lo.aioble– um wrapper de nível mais alto, escrito em Python sobre obluetooth, que transforma cada operação remota em uma corrotinaasyncioe cada objeto BLE (serviços, características, conexões, resultados de varredura, canais L2CAP) em uma classe Python ergonômica. As varreduras se tornam iteradores assíncronos; as conexões se tornam gerenciadores de contexto assíncronos; as notificações se tornam aguardáveis (awaitable).
11.8.1. Quando recorrer ao módulo de nível mais baixo¶
bluetooth ainda é a resposta certa para dois casos restritos:
Você está escrevendo o tipo de código a partir do qual o próprio
aiobleé construído – um novo padrão que precisa de controle em nível de IRQ sobre o protocolo.Você está rodando em uma plataforma de hardware onde o pacote
aioblenão está disponível, e uma camada fina em torno do controlador é a única opção.
Para toda aplicação de câmera, aioble é a resposta certa.
11.8.2. Partes de um programa aioble¶
Toda aplicação baseada em aioble tem um pequeno conjunto de partes móveis, independentemente das funções que ela desempenha.
Um loop de eventos
asynciode longa duração. Tudo emaiobleé uma corrotina, então a aplicação é estruturada como uma ou mais tarefas em um único loop de eventos. Consulte Asyncio para detalhes sobre o loop, as tarefas e as exceções.Um rádio ligado.
aiobleativa o rádio BLE implicitamente no primeiro uso, mas ele também pode ser controlado explicitamente comaioble.config()(que repassa parabluetooth.BLE.config()após garantir que o rádio está ativo) e desligado comaioble.stop().Uma ou mais funções em andamento ao mesmo tempo. No lado peripheral: um conjunto registrado de serviços GATT (consulte
aioble.register_services()) e uma corrotinaaioble.advertise()em execução. No lado central: um iteradoraioble.scan()em execução ou umaioble.Device.connect()pendente. O rádio multiplexa o trabalho; a aplicação vê cada função como uma tarefa independente.
11.8.3. Um peripheral mínimo¶
O menor programa aioble útil – um peripheral que anuncia uma única característica somente leitura – é curto:
import aioble
import asyncio
import bluetooth
SERVICE_UUID = bluetooth.UUID(0x181A) # Environmental Sensing
TEMP_UUID = bluetooth.UUID(0x2A6E) # Temperature
service = aioble.Service(SERVICE_UUID)
temp = aioble.Characteristic(service, TEMP_UUID, read=True)
aioble.register_services(service)
async def main():
while True:
conn = await aioble.advertise(
interval_us=250000,
name="openmv-temp",
services=[SERVICE_UUID],
)
async with conn:
await conn.disconnected()
asyncio.run(main())
Um central que não faz nada além de conectar e ler uma vez é igualmente curto:
import aioble
import asyncio
import bluetooth
SERVICE_UUID = bluetooth.UUID(0x181A)
TEMP_UUID = bluetooth.UUID(0x2A6E)
async def main():
device = None
async with aioble.scan(duration_ms=5000, active=True) as scanner:
async for result in scanner:
if SERVICE_UUID in result.services():
device = result.device
break
if device is None:
return
async with await device.connect() as conn:
service = await conn.service(SERVICE_UUID)
char = await service.characteristic(TEMP_UUID)
print(await char.read())
asyncio.run(main())
Ambos os programas têm cerca de quinze linhas e cobrem todo o fluxo desde “o rádio está desligado” até “trabalho útil concluído”.
11.8.4. Desligando o rádio¶
Em uma câmera alimentada por bateria, o rádio BLE é o maior consumo discricionário do orçamento. Dois controles importam.
O primeiro é implícito: aioble ativa o rádio no primeiro uso, e o rádio dorme entre eventos agendados (rajadas de anúncio, eventos de conexão, janelas de varredura) automaticamente. Escolher intervalos mais longos em aioble.advertise() / aioble.scan() e concordar com um intervalo de conexão mais longo no momento do connect() mantém o rádio desligado proporcionalmente por mais tempo. A tabela de anúncio em Anúncio e varredura é o guia prático aqui.
O segundo é o desligamento explícito
import aioble
await do_burst_of_ble_work()
aioble.stop() # radio deactivated; in-flight tasks unwound
await asyncio.sleep(60) # sleep with the radio off
# ... next aioble call brings the radio back up automatically
aioble.stop() desativa o rádio BLE subjacente e encerra qualquer coisa em andamento – conexões abertas caem, varredores e anunciantes são cancelados, canais L2CAP fecham. As corrotinas que estavam aguardando essas operações levantam suas exceções usuais (DeviceDisconnectedError e afins), que é o mecanismo de limpeza para o qual os blocos async with ao redor foram escritos. Chamar qualquer corrotina aioble depois disso ativa o rádio novamente do zero.
O padrão típico para uma câmera de sensor periódica alimentada por bateria é:
Acordar segundo um cronograma (timer, sensor de movimento, botão).
Executar a rajada de trabalho BLE – anunciar, aceitar uma conexão, enviar o valor, desconectar.
Chamar
aioble.stop()e dormir até o próximo despertar.
11.8.5. O que o aioble não faz¶
aioble cobre deliberadamente GATT, GAP e L2CAP – as camadas que uma aplicação utiliza. Três peças estão fora de escopo:
Qualquer coisa abaixo da camada de enlace. Seleção de canal, salto de frequência, confirmações de pacotes e criptografia em nível de enlace acontecem todas dentro da porta BLE e do silício do controlador;
aioblenão expõe ganchos nesse nível.Bluetooth clássico.
aiobleé exclusivo para BLE. Enlaces de áudio, RFCOMM, A2DP e outros recursos de perfis clássicos não fazem parte da API.Bluetooth Mesh. A camada de rede em malha do Bluetooth SIG (uma pilha separada sobre o anúncio BLE) não está implementada na câmera. A câmera pode anunciar e observar, mas não pode participar das funções de relay / friend / proxy de uma rede em malha.
11.8.6. Exceções¶
Quatro tipos de exceção saem do aioble. Cada uma é disparada de dentro de uma corrotina que estava aguardando uma operação quando algo deu errado; os blocos async with se desfazem de forma limpa quando elas se propagam.
aioble.DeviceDisconnectedError– o enlace BLE com o par caiu enquanto uma operação GATT (read,write,notified,indicated,subscribe,exchange_mtu, …) estava em andamento. Levantada dentro de qualquer corrotina que estivesse aguardando. De longe, a exceção mais comum; capture-a em qualquer código que deva reconectar em caso de perda.aioble.GattError– uma operação GATT chegou ao par, mas foi concluída com um status ATT diferente de zero (write-with-response rejeitado, indicate não confirmado, read-not-permitted, …). O código de status está no atributo_statusda exceção.aioble.L2CAPDisconnectedError– o canal L2CAP caiu enquanto umsend(),recvinto()ouflush()estava em andamento. Qualquer um dos lados pode ter fechado o canal, ou a conexão GAP subjacente desapareceu.aioble.L2CAPConnectionError– levantada porl2cap_connect()quando o listener recusou ou o controlador falhou na configuração do canal. O código de status do Bluetooth é o primeiro argumento posicional.
Operações que recebem um timeout_ms explícito (as chamadas de connect / discovery / read / write / pair, além de timeout() como wrapper) levantam adicionalmente asyncio.TimeoutError do asyncio quando o prazo expira antes de a operação ser concluída.