11.4. Anúncio e varredura¶
Dois dispositivos BLE que nunca se encontraram antes precisam primeiro localizar um ao outro. A conexão em rede resolve isso atribuindo a cada dispositivo um endereço de um pool compartilhado e deixando que qualquer lado alcance o outro por meio de roteadores. O BLE não tem roteadores, não tem pool compartilhado e – entre a maioria dos pares de dispositivos – nenhuma relação prévia. O Generic Access Profile (GAP) resolve a descoberta com um padrão de transmissão e escuta. Um lado anuncia – ele transmite um pacote curto nos três canais de anúncio em um intervalo regular, descrevendo quem é. O outro lado varre (scan) – ele percorre os mesmos três canais ouvindo esses pacotes.
O GAP define quatro papéis em torno desse padrão, cada um uma combinação específica de anúncio e escuta.
11.4.1. Os quatro papéis do GAP¶
Os quatro papéis do GAP. O eixo vertical indica se o dispositivo anuncia; o eixo horizontal indica se ele aceita (ou inicia) conexões.¶
Um peripheral anuncia pacotes que dizem “estou aqui e você pode se conectar a mim”. Quando outro dispositivo abre uma conexão, o peripheral para de anunciar e começa a atender requisições GATT. Cintas de frequência cardíaca, termômetros e a maioria das câmeras como sensores atuam como peripherals.
Um central varre em busca de peripherals, escolhe um e inicia uma conexão. Após conectar, ele fala GATT como cliente. Celulares, laptops e câmeras atuando como coletores de dados são centrals.
Um broadcaster anuncia, mas nunca aceita conexões. Seu payload de anúncio é o dado – não há nada a que se conectar. iBeacons e a maioria dos beacons de presença em lojas são broadcasters.
Um observer varre esses anúncios e lê o payload, novamente sem nunca se conectar. Uma câmera que ouve beacons próximos e age conforme o que ouve é um observer.
Um único dispositivo pode desempenhar mais de um papel ao mesmo tempo – uma câmera pode ser um peripheral que publica seu próprio estado e um central que se conecta a um sensor próximo. O rádio multiplexa o trabalho.
11.4.2. O que um pacote de anúncio contém¶
Um pacote de anúncio é pequeno: 31 bytes de payload, ou 62 se o anunciante também publicar uma scan response que os scanners podem solicitar dinamicamente. O payload é uma lista de campos curtos e tipados:
Flags. Conectável ou não, descobrível geral / limitado.
Nome local. Uma string curta e amigável – o nome que o sistema operacional de um celular ou laptop mostra no seu menu Bluetooth.
UUIDs de serviço. Uma lista de identificadores de serviços GATT que o dispositivo hospeda, para que um scanner possa reconhecer peripherals capazes sem se conectar antes. Uma cinta de frequência cardíaca anuncia
0x180D– o UUID padrão do serviço Heart-Rate – e um aplicativo de frequência cardíaca no celular sabe, só por isso, que vale a pena conectar-se ao dispositivo.Appearance. Um valor de 16 bits da lista de assigned-numbers do Bluetooth (sensor, mídia genérica, relógio genérico, …) – uma dica para o central sobre o que exibir.
Dados específicos do fabricante. Bytes de formato livre prefixados com um ID de empresa. iBeacons usam esse campo para carregar seu UUID, major e minor; aplicações personalizadas podem colocar o que quiserem aqui.
Os payloads de anúncio são apertados. O limite de 31 bytes torna a escolha do que incluir uma verdadeira decisão de projeto – um nome longo e legível por humanos pode rapidamente não deixar espaço para os UUIDs de serviço. A API aioble.advertise() recebe cada um deles como um argumento nomeado e monta os bytes para você, transbordando para a scan response automaticamente se o pacote principal encher.
11.4.3. Varredura ativa e passiva¶
Um scanner pode operar em modo passivo, em que ouve os pacotes de anúncio e analisa o que chega, ou ativo, em que também envia uma scan request a cada anunciante e analisa a scan response que retorna.
A varredura passiva enxerga apenas o pacote de anúncio inicial (até 31 bytes). A varredura ativa dobra isso – a scan response são outros 31 bytes que o peripheral pode usar para campos que não couberam. A varredura ativa também consome energia em ambos os lados, já que o scanner transmite e o anunciante transmite um pacote extra, então é uma escolha em vez de um padrão.
Na API aioble, active=True em aioble.scan() alterna o modo, e cada ScanResult expõe o adv_data combinado mais o resp_data, além de auxiliares como result.name() e result.services() que escondem a análise no nível de bytes.
Nota
Os atributos adv_data e resp_data são os payloads brutos de anúncio e scan-response (bytes). Os auxiliares – name(), services(), manufacturer() – cobrem os campos padrão comuns e são a escolha certa 99% das vezes. Recorra aos bytes brutos apenas quando precisar de um campo de fornecedor que os auxiliares não analisam (URLs Eddystone, UUID/major/minor de iBeacon, tipos de anúncio personalizados). O layout de bytes é o TLV padrão: cada campo é length, type, value....
11.4.4. O intervalo de anúncio¶
A frequência com que o peripheral transmite é uma compensação entre energia e latência de descoberta. Anúncios que saem a cada 20 ms são captados quase imediatamente por um scanner, mas mantêm o rádio ocupado e drenam a bateria; anúncios a cada segundo usam quase nenhuma energia, mas fazem o scanner demorar mais para notar o dispositivo.
interval_us em aioble.advertise() define o intervalo em microssegundos:
20.000 a 100.000 us (20 ms - 100 ms) – emparelhamento rápido, o aplicativo espera uma resposta rápida, dispositivo ligado à energia.
250.000 a 1.000.000 us (250 ms - 1 s) – um padrão razoável para um peripheral alimentado por bateria que quer ser descobrível sem gastar carga.
Acima de 1.000.000 us – transmissão lenta em segundo plano, beacons que enviam uma atualização de posição a cada poucos segundos.
O lado do scanner tem seus próprios ajustes – aioble.scan() recebe interval_us e window_us (com que frequência o scanner acorda seu rádio e por quanto tempo ele ouve a cada vez). Os padrões são adequados; a única mudança comum é definir ambos iguais para uma varredura contínua quando a bateria não é uma preocupação.
11.4.5. Padrões sem conexão – broadcaster e observer¶
As páginas sobre Atuando como um periférico e Atuando como central percorrem a forma conectável da API – em que um peripheral aceita uma conexão e os dois lados trocam dados via GATT. A outra forma é sem conexão: um broadcaster transmite o payload como anúncio, e qualquer observer dentro do alcance pode lê-lo sem nunca se conectar. Beacons, sensores de presença e telemetria de mão única vivem aqui.
Um broadcaster é aioble.advertise() com connectable=False. Os dados específicos do fabricante carregam o payload:
import aioble
import asyncio
import struct
_COMPANY_ID = const(0xFFFF) # 0xFFFF is "no specific vendor"
async def beacon():
seq = 0
while True:
seq = (seq + 1) & 0xFFFF
payload = struct.pack("<H", seq)
await aioble.advertise(
interval_us=500000,
connectable=False,
name="openmv-beacon",
manufacturer=(_COMPANY_ID, payload),
timeout_ms=1000, # one cycle, then loop
)
asyncio.run(beacon())
A palavra-chave timeout_ms encerra a chamada de anúncio após um segundo; o laço externo a reemite com o próximo número de sequência para que os ouvintes vejam dados atualizados. A flag connectable=False é o que torna o anúncio do tipo broadcaster – a câmera não responderá a uma requisição de conexão mesmo que uma chegue.
Um observer é o scanner somente leitura correspondente. Ele executa aioble.scan() indefinidamente, analisa os anúncios recebidos e nunca chama connect()
import aioble
import asyncio
_COMPANY_ID = const(0xFFFF)
async def watch():
async with aioble.scan(duration_ms=0, active=False) as scanner:
async for result in scanner:
for company, data in result.manufacturer(filter=_COMPANY_ID):
print(result.device.addr_hex(),
"rssi", result.rssi, "data", data)
asyncio.run(watch())
duration_ms=0 varre até que o gerenciador de contexto saia; active=False mantém o rádio do próprio observer em silêncio (sem requisições de scan-response) para o menor consumo de energia. O argumento filter= em manufacturer() descarta todo anúncio que não corresponda ao ID da empresa, de modo que o laço só dispara para o tráfego do broadcaster.
11.4.6. Da descoberta a uma conexão¶
Assim que um central escolhe um peripheral para conversar, ele para de ouvir, envia uma connect request no canal de anúncio que o peripheral usou por último, e ambos os lados caem nos canais de dados com salto de frequência da camada de link. O peripheral normalmente para de anunciar nesse ponto. O que acontece em seguida – parâmetros de conexão, descoberta GATT, o tempo de vida do link – está em Conexões.