11.4. Publicidade e pesquisa¶
Dois dispositivos BLE que nunca se encontraram antes têm primeiro de se descobrir mutuamente. A ligação em rede resolve isso atribuindo a cada dispositivo um endereço de um conjunto partilhado e permitindo que qualquer lado alcance o outro através de routers. O BLE não tem routers, nenhum conjunto partilhado e – entre a maioria dos pares de dispositivos – absolutamente nenhuma relação prévia. O Generic Access Profile (GAP) resolve a descoberta com um padrão de difusão-e-escuta. Um lado anuncia-se – transmite um pacote curto nos três canais de publicidade a intervalos regulares, descrevendo quem é. O outro lado pesquisa – varre os mesmos três canais à escuta desses pacotes.
O GAP define quatro funções em torno desse padrão, cada uma sendo uma combinação específica de publicidade e escuta.
11.4.1. As quatro funções GAP¶
As quatro funções GAP. O eixo vertical indica se o dispositivo anuncia; o eixo horizontal indica se aceita (ou inicia) ligações.¶
Um periférico anuncia pacotes que dizem «estou aqui e pode ligar-se a mim». Quando outro dispositivo abre uma ligação, o periférico para de anunciar e começa a responder a pedidos GATT. Cintas de frequência cardíaca, termómetros e a maioria das câmaras-como-sensores atuam como periféricos.
Um central pesquisa periféricos, escolhe um e inicia uma ligação. Após a ligação, fala GATT como cliente. Telemóveis, portáteis e câmaras que atuam como coletores de dados são centrais.
Um difusor anuncia mas nunca aceita ligações. A sua carga útil de publicidade é os dados – não há nada a que se ligar. Os iBeacons e a maioria dos beacons de presença em lojas são difusores.
Um observador pesquisa esses anúncios e lê a carga útil, também sem nunca ligar. Uma câmara que escuta beacons próximos e age com base no que ouve é um observador.
Um único dispositivo pode desempenhar mais de uma função ao mesmo tempo – uma câmara pode ser um periférico que publica o seu próprio estado e um central que se liga a um sensor próximo. O rádio multiplexa o trabalho.
11.4.2. O que um pacote de publicidade contém¶
Um pacote de publicidade é pequeno: 31 bytes de carga útil, ou 62 se o anunciante publicar também uma resposta de pesquisa que os pesquisadores podem solicitar em tempo real. A carga útil é uma lista de campos curtos com tipo:
Flags. Conectável ou não, detectável geral / limitado.
Nome local. Uma cadeia de caracteres curta e legível por humanos – o nome que o sistema operativo de um telemóvel ou portátil mostra no seu menu Bluetooth.
UUIDs de serviço. Uma lista de identificadores de serviço GATT que o dispositivo aloja, para que um pesquisador possa reconhecer periféricos capazes sem primeiro ligar. Uma cinta de frequência cardíaca anuncia
0x180D– o UUID padrão do serviço Heart-Rate – e uma aplicação de frequência cardíaca no telemóvel sabe apenas com isso que vale a pena ligar-se ao dispositivo.Aparência. Um valor de 16 bits da lista de números atribuídos do Bluetooth (sensor, multimédia genérico, relógio genérico, …) – uma dica para o central sobre o que mostrar.
Dados específicos do fabricante. Bytes de formato livre prefixados com um ID de empresa. Os iBeacons usam este campo para transportar o seu UUID, major e minor; as aplicações personalizadas podem colocar aqui o que quiserem.
As cargas úteis de publicidade são limitadas. O limite de 31 bytes torna a escolha do que incluir uma decisão de conceção real – um nome longo legível por humanos pode rapidamente não deixar espaço para UUIDs de serviço. A API aioble.advertise() aceita cada um destes como argumento de palavra-chave e monta os bytes por si, transbordando automaticamente para a resposta de pesquisa se o pacote principal ficar cheio.
11.4.3. Pesquisa ativa e passiva¶
Um pesquisador pode funcionar em modo passivo, onde escuta os pacotes de publicidade e analisa o que chega, ou em modo ativo, onde também envia um pedido de pesquisa a cada anunciante e analisa a resposta de pesquisa que recebe.
A pesquisa passiva vê apenas o pacote de publicidade inicial (até 31 bytes). A pesquisa ativa duplica isso – a resposta de pesquisa é mais 31 bytes que o periférico pode usar para campos que não couberam. A pesquisa ativa também tem custos de energia em ambos os lados, uma vez que o pesquisador transmite e o anunciante transmite um pacote extra, pelo que é uma escolha e não uma predefinição.
Na API aioble, active=True em aioble.scan() muda o modo, e cada ScanResult expõe os adv_data combinados mais resp_data bem como auxiliares como result.name() e result.services() que ocultam a análise ao nível de bytes.
Nota
Os atributos adv_data e resp_data são as cargas úteis brutas de publicidade e resposta de pesquisa (bytes). Os auxiliares – name(), services(), manufacturer() – cobrem os campos padrão comuns e são a escolha certa em 99% dos casos. 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 publicidade personalizados). O esquema de bytes é o TLV padrão: cada campo é length, type, value....
11.4.4. O intervalo de publicidade¶
A frequência com que o periférico transmite é uma compensação entre consumo/latência de descoberta. Anúncios enviados a cada 20 ms são detetados quase de imediato por um pesquisador, mas mantêm o rádio ocupado e esgotam a bateria; anúncios a cada segundo usam quase nenhuma energia, mas tornam a varredura do pesquisador mais lenta para detetar 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, a aplicação espera uma resposta rápida, dispositivo ligado à corrente.
250 000 a 1 000 000 us (250 ms - 1 s) – uma predefinição razoável para um periférico alimentado por bateria que quer ser detectável sem gastar carga.
Acima de 1 000 000 us – difusão de fundo lenta, beacons que enviam uma atualização de posição a cada poucos segundos.
O lado do pesquisador tem os seus próprios controlos – aioble.scan() aceita interval_us e window_us (com que frequência o pesquisador acorda o seu rádio e quanto tempo escuta de cada vez). As predefinições são adequadas; a única alteração comum é definir ambas como iguais para uma pesquisa contínua quando a bateria não é uma preocupação.
11.4.5. Padrões sem ligação – difusor e observador¶
As páginas em Agir como periférico e Agir como central percorrem a forma conectável da API – onde um periférico aceita uma ligação e os dois lados trocam dados através do GATT. A outra forma é sem ligação: um difusor transmite a carga útil como anúncio, e qualquer observador ao alcance pode lê-la sem nunca ligar. Beacons, sensores de presença e telemetria unidirecional vivem aqui.
Um difusor é aioble.advertise() com connectable=False. Os dados específicos do fabricante transportam a carga útil:
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 termina a chamada de anuncio após um segundo; o ciclo externo reemite-a com o número de sequência seguinte para que os ouvintes vejam dados atualizados. O flag connectable=False é o que torna o anúncio do estilo difusor – a câmara não responderá a um pedido de ligação mesmo que chegue um.
Um observador é o scanner somente de leitura correspondente. 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 pesquisa até o gestor de contexto sair; active=False mantém o rádio do próprio observador silencioso (sem pedidos de resposta de pesquisa) para o menor consumo de energia. O argumento filter= em manufacturer() descarta todos os anúncios que não correspondem ao ID da empresa, pelo que o ciclo só é acionado pelo tráfego do difusor.
11.4.6. Da descoberta a uma ligação¶
Quando um central escolhe um periférico com que falar, para de escutar, envia um pedido de ligação no canal de publicidade que o periférico usou por último, e ambos os lados entram nos canais de dados de salto da camada de ligação. O periférico normalmente para de anunciar neste ponto. O que acontece a seguir – parâmetros de ligação, descoberta GATT, o tempo de vida da ligação – está em Ligações.