11.14. Conclusão

Percorreu o Bluetooth Low Energy desde o rádio até à API Python utilizada para o controlar:

  • A motivação – O BLE é a resposta quando a câmara quer falar com algo próximo sem qualquer infraestrutura entre eles. Um telemóvel na mesma sala, um wearable no pulso, um beacon numa parede. Curto alcance, sem rede a que se juntar, quase sem consumo de energia.

  • O rádio – 2,4 GHz, 40 canais: três para publicidade, 37 para dados de ligação, com saltos numa sequência pseudo-aleatória com evitamento adaptativo de canais com ruído. Pacotes breves, rádios maioritariamente em repouso.

  • A camada de ligação – enquadramento de pacotes, endereçamento, agendamento de ligações, retransmissão e encriptação ao nível da camada de ligação. Nada disto é configurado a partir de Python; tudo isso transparece nos parâmetros de ligação e no MTU.

  • Perfil de Acesso Genérico (GAP) – descoberta e gestão de ligações. Quatro papéis: periférico e emissor (publicam), central e observador (examinam). As cargas de publicidade transportam 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 ligação, a latência do periférico e o tempo limite de supervisão controlam o comportamento de uma ligação aberta.

  • Perfil de Atributos Genéricos (GATT) – uma árvore de serviços, cada um contendo características, cada uma opcionalmente contendo descritores, identificados por UUIDs (16 bits para os 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, subscritas através do Descritor de Configuração de Característica do Cliente). O tamanho da carga útil é limitado pelo MTU negociado.

  • A API Python – o aioble transforma cada padrão BLE numa corrotina asyncio. Um periférico é aioble.advertise() a iterar sobre ligações, com objectos Service / Characteristic construídos uma vez e confirmados por aioble.register_services(). Uma central usa aioble.scan() para encontrar um par, connect() para abrir a ligação, service() e characteristic() para percorrer a árvore GATT remota, e depois read() / write() / subscribe() / notified() para os dados reais. As desconexões surgem como aioble.DeviceDisconnectedError dentro da corrotina que estava à espera.

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

  • Emparelhamento e encriptação – as ligações BLE são públicas por predefinição. aioble.DeviceConnection.pair() inicia uma troca de chaves que produz uma ligação encriptada; bond=True (a predefinição) persiste as chaves para que as ligações subsequentes saltem o handshake. Sem mitm=True e uma capacidade de IO utilizável, a encriptação protege contra intrusos passivos mas não contra um redirecionamento activo durante o emparelhamento original.

Isso é suficiente para escrever aplicações de câmara que publicam estado como periférico, lêem dados de sensor como central, enviam valores em tempo real para um telemóvel via BLE, protegem a ligação com um passo de emparelhamento e vinculação, e – para o caso raro de transferência em volume – saem do GATT para um canal L2CAP.

11.14.1. Resolução de problemas

As falhas do BLE são principalmente discrepâncias entre o que os dois lados esperam, e um inspector no lado do telemóvel é a forma mais rápida de ver de quem são as expectativas erradas. A ferramenta padrão é o nRF Connect for Mobile (Nordic Semiconductor, gratuito para Android e iOS): examina, liga, percorre a base de dados GATT, lê e escreve características, e subscreve notificações – para que o comportamento do lado da câmara possa ser testado isoladamente, sem escrever uma aplicação complementar de todo.

Os modos de falha comuns:

  • «O meu dispositivo aparece no scanner mas não liga.» Na maior parte das vezes o pacote de publicidade tem connectable=False (modo emissor), ou uma ligação anterior ainda está aberta e a câmara já passou aioble.advertise(). Adicione declarações de impressão em torno da chamada de publicidade para confirmar.

  • «exchange_mtu(512) foi executado mas as minhas notificações ainda estão limitadas a 20 bytes.» O MTU negociado é min(local, peer) – o telemóvel ou a biblioteca central pode não ter pedido um MTU maior do seu lado, caso em que a ligação permanece em 23. Inspeccione mtu após o retorno de exchange_mtu(). Note também que exchange_mtu() só funciona uma vez por ligação; chame-o antes da primeira operação de grande dimensão.

  • «O emparelhamento falha com um erro genérico.» Dois culpados habituais: a incompatibilidade de capacidade de IO (pedir mitm=True numa câmara que declara io=3 / sem entrada sem saída – não há forma de confirmar o código numérico, portanto o motor de emparelhamento abandona), e uma hora de relógio de parede completamente errada na câmara quando o par a requer. Defina o relógio com ntptime.settime() antes da primeira tentativa de emparelhamento.

  • «As notificações nunca chegam ao cliente.» Duas coisas a verificar, por ordem: (a) foi a característica declarada com notify=True? – o bit de propriedade tem de estar definido no lado do servidor; (b) o cliente chamou subscribe()? – sem escrever o Descritor de Configuração de Característica do Cliente (CCCD), o servidor é informado de que nenhum cliente quer notificações e descarta-as silenciosamente.

  • «O nome publicado está truncado ou em falta.» A carga de publicidade tem 31 bytes, e os campos de flags + UUID de serviço + aparência consomem cada um bytes no topo. Um name= longo mais vários UUIDs de serviço transborda. Encurte o nome ou use scanning activo para que a resposta de scan (outros 31 bytes) transporte o excesso. O nRF Connect mostra ambas as metades separadamente, o que torna a divisão óbvia.

  • «L2CAP connect levanta imediatamente.» Normalmente uma discrepância de PSM – ambos os lados têm de concordar no mesmo número de PSM fora de banda. Um L2CAPConnectionError transporta o código de estado Bluetooth como primeiro argumento; o estado 2 («PSM não suportado») é o sinal revelador.

  • «As ligações vinculadas ainda desencadeiam um handshake de emparelhamento completo a cada reconexão.» aioble.security.load_secrets() não foi chamado na inicialização. Sem isso, as chaves guardadas estão em flash mas nunca são carregadas para memória, pelo que a identidade do par é desconhecida e o emparelhamento é executado de raiz de cada vez.

Quando tudo o resto falha, o módulo de nível inferior bluetooth expõe um callback IRQ que dispara para cada evento subjacente; subscrever a ele brevemente e imprimir os eventos é o equivalente a um rastreio Wireshark para o lado da câmara.

11.14.2. Utilizar esta referência mais tarde

Trate os capítulos sobre Bluetooth como material de referência; consultar a disposição exacta de uma carga de publicidade de periférico ou o fluxo de scan-e-subscrição central é a utilização pretendida. As páginas de referência aioble — BLE Assíncrono e bluetooth — Bluetooth de baixo nível listam cada método, flag e constante num único local quando a questão é apenas «qual é o nome exacto desta chamada».