11.14. Conclusão

Você percorreu o Bluetooth Low Energy desde o rádio até a API Python usada para controlá-lo:

  • A motivação – o BLE é a resposta quando a câmera quer se comunicar com algo próximo sem qualquer infraestrutura entre eles. Um celular na mesma sala, um wearable no pulso, um beacon na parede. Curto alcance, sem rede para ingressar, quase nenhum consumo de energia.

  • O rádio – 2,4 GHz, 40 canais: três para anúncio, 37 para dados de conexão, saltados em uma sequência pseudoaleatória com prevenção adaptativa de canais ruidosos. Pacotes breves, rádios em sua maior parte adormecidos.

  • A camada de enlace – enquadramento de pacotes, endereçamento, agendamento de conexão, retransmissão e criptografia da camada de enlace. Nada disso é configurado a partir do Python; tudo isso se manifesta nos parâmetros de conexão e no MTU.

  • Generic Access Profile (GAP) – descoberta e gerenciamento de conexão. Quatro papéis: periférico e broadcaster (anunciam), central e observador (escaneiam). As cargas úteis de anúncio carregam o nome local, UUIDs de serviço, aparência e dados específicos do fabricante – 31 bytes mais uma resposta de scan opcional de 31 bytes. O intervalo de conexão, a latência do periférico e o tempo limite de supervisão governam como uma conexão aberta se comporta.

  • Generic Attribute Profile (GATT) – uma árvore de serviços, cada um contendo características, cada uma opcionalmente contendo descritores, identificados por UUIDs (16 bits para padrões Bluetooth-SIG, 128 bits para os personalizados). Cinco operações: read e write (pull, iniciadas pelo cliente), notify e indicate (push, iniciadas pelo servidor, inscritas via o Client Characteristic Configuration Descriptor). O tamanho da carga útil é limitado pelo MTU negociado.

  • A API Python – o aioble transforma cada padrão BLE em uma corrotina asyncio. Um periférico é aioble.advertise() em laço sobre conexões, com objetos Service / Characteristic construídos uma vez e confirmados por aioble.register_services(). Uma central é aioble.scan() para encontrar um par, connect() para abrir o enlace, service() e characteristic() para percorrer a árvore GATT remota, e então read() / write() / subscribe() / notified() para os dados em si. Desconexões surgem como aioble.DeviceDisconnectedError dentro da corrotina que estava aguardando.

  • Canais L2CAP – a saída de emergência para fluxos de bytes em massa que não se encaixam no modelo chave/valor do GATT. aioble.DeviceConnection.l2cap_accept() / l2cap_connect() abrem um canal por aplicação sobre a conexão GAP, com envio / recebimento controlados por fluxo de créditos e um MTU maior do que o GATT consegue carregar.

  • Pareamento e criptografia – enlaces BLE são públicos por padrão. aioble.DeviceConnection.pair() inicia uma troca de chaves que produz um enlace criptografado; bond=True (o padrão) persiste as chaves para que conexões subsequentes pulem o handshake. Sem mitm=True e uma capacidade de IO utilizável, a criptografia protege contra bisbilhoteiros passivos, mas não contra um redirecionamento ativo durante o pareamento original.

Isso é suficiente para escrever aplicações de câmera que publicam status como periférico, leem dados de sensores como central, enviam valores ao vivo para um celular via BLE, protegem o enlace com uma etapa de pareie-e-vincule e – para o raro caso de transferência em massa – saem do GATT para um canal L2CAP.

11.14.1. Solução de problemas

Falhas de BLE são, em sua maioria, incompatibilidades entre o que cada lado espera, e um inspetor no lado do celular é a maneira mais rápida de ver de quem são as expectativas desalinhadas. A ferramenta padrão é o nRF Connect for Mobile (Nordic Semiconductor, gratuito no Android e no iOS): ele escaneia, conecta, percorre a base de dados GATT, lê e escreve características e se inscreve em notificações – de modo que o comportamento do lado da câmera pode ser testado isoladamente, sem escrever um aplicativo complementar.

Os modos de falha comuns:

  • “Meu dispositivo aparece no scanner, mas não conecta.” Na maioria das vezes o pacote de anúncio tem connectable=False (modo broadcaster), ou uma conexão anterior ainda está aberta e a cam já passou de aioble.advertise(). Adicione instruções print ao redor da chamada de anúncio para confirmar.

  • “exchange_mtu(512) executou, mas minhas notificações ainda estão limitadas a 20 bytes.” O MTU negociado é min(local, peer) – o celular ou a biblioteca central pode não ter solicitado um MTU maior do seu lado, caso em que a conexão permanece em 23. Inspecione mtu após o retorno de exchange_mtu(). Observe também que exchange_mtu() só funciona uma vez por conexão; chame-o antes da primeira operação grande.

  • “O pareamento falha com um erro genérico.” Dois culpados usuais: a incompatibilidade de capacidade de IO (pedir mitm=True em uma cam que declara io=3 / sem entrada e sem saída – não há como confirmar o código numérico, então o mecanismo de pareamento desiste), e um horário de relógio totalmente errado na cam quando o par o exige. Ajuste o relógio com ntptime.settime() antes da primeira tentativa de pareamento.

  • “As notificações nunca chegam ao cliente.” Duas coisas a verificar, nesta ordem: (a) a característica foi declarada com notify=True? – o bit de propriedade deve estar definido no lado do servidor; (b) o cliente chamou subscribe()? – sem escrever o Client Characteristic Configuration Descriptor (CCCD), o servidor é informado de que nenhum cliente quer notificações e as descarta silenciosamente.

  • “O nome anunciado está truncado ou ausente.” A carga útil de anúncio tem 31 bytes, e os campos de flags + UUID de serviço + aparência cada um consome bytes do topo. Um name= longo mais vários UUIDs de serviço causam transbordamento. Ou encurte o nome ou use escaneamento ativo para que a resposta de scan (outros 31 bytes) carregue o excedente. O nRF Connect mostra as duas metades separadamente, o que torna a divisão evidente.

  • “O connect L2CAP lança uma exceção imediatamente.” Geralmente uma incompatibilidade de PSM – ambos os lados precisam concordar com o mesmo número de PSM fora de banda. Um L2CAPConnectionError carrega o código de status Bluetooth como seu primeiro argumento; o status 2 (“PSM not supported”) é o indício.

  • “Conexões vinculadas ainda disparam um handshake de pareamento completo a cada reconexão.” aioble.security.load_secrets() não foi chamado na inicialização. Sem isso, as chaves salvas estão no flash, mas nunca são carregadas na memória, então a identidade do par é desconhecida e o pareamento é executado do zero a cada vez.

Quando tudo o mais falhar, o módulo de mais baixo nível bluetooth expõe um callback de IRQ que dispara para cada evento subjacente; inscrever-se nele brevemente e imprimir os eventos é o equivalente a uma captura do Wireshark para o lado da cam.

11.14.2. Usando esta referência posteriormente

Trate os capítulos de Bluetooth como material de referência; voltar para consultar o layout exato da carga útil de anúncio de um periférico ou o fluxo de escaneamento e inscrição da central é o uso pretendido. As páginas de referência aioble — BLE Assíncrono e bluetooth — Bluetooth de baixo nível listam cada método, flag e constante em um só lugar quando a pergunta é apenas “qual é o nome exato desta chamada”.