Uma ponte Raspberry Pi BLE para MQTT transforma sensores Bluetooth baratos e alimentados por bateria em dispositivos de rede que você pode colocar em gráficos, monitorar com alertas e automatizar. Neste tutorial você vai usar o rádio Bluetooth integrado do Pi, a biblioteca bleak e o paho-mqtt para escutar os advertisements dos sensores, decodificar temperatura, umidade e bateria e publicá-los em um broker MQTT em uma taxa sensata. Primeiro você testa contra o broker.connectmq.com, o servidor MQTT público do ConnectMQ, e depois migra para credenciais privadas.
Por que fazer a ponte de BLE para MQTT
Sensores Bluetooth Low Energy são pequenos, baratos e funcionam por um ano ou mais com uma bateria tipo moeda, mas seu alcance é de um ou dois cômodos e eles não chegam à internet sozinhos. Um Raspberry Pi com rádio integrado (Pi 3B e posteriores, Zero W e Zero 2 W) pode ficar no meio de uma casa, estufa ou câmara fria, ouvir todos os sensores próximos e encaminhar os dados via Wi-Fi ou Ethernet.
A maioria dos sensores transmite suas leituras em pacotes de advertisement. Isso é conveniente: a ponte nunca precisa se conectar ao sensor, então dezenas de dispositivos podem ser lidos passivamente sem esgotar suas baterias. A leitura baseada em conexão (GATT) continua possível com o bleak, mas é mais lenta, consome mais energia do sensor e limita quantos dispositivos você consegue tratar ao mesmo tempo. Este guia foca nos advertisements, com uma observação sobre GATT no final.
O que você precisa
| Item | Observações |
|---|---|
| Raspberry Pi com Bluetooth | Pi 3B, 4, 5, Zero W ou Zero 2 W; um dongle BLE USB também funciona |
| Raspberry Pi OS Bookworm | O BlueZ vem instalado por padrão |
| Sensores BLE | Este exemplo decodifica termômetros Xiaomi LYWSD03MMC com o popular firmware customizado de código aberto |
| Python 3.11+ | Vem com o Bookworm |
A lógica de decodificação é específica de cada dispositivo, mas a estrutura da ponte não. Se você tem um sensor diferente, só precisa trocar uma função.
Instale o bleak e o paho-mqtt
O bleak usa o BlueZ via D-Bus no Linux, então não é preciso acesso root nem pacotes extras de sistema em uma instalação padrão. Verifique se o rádio está ativo e crie um ambiente virtual:
bluetoothctl show | grep -E "Powered|Name"
sudo rfkill unblock bluetooth
python3 -m venv ~/ble-bridge/.venv
source ~/ble-bridge/.venv/bin/activate
pip install bleak "paho-mqtt>=2.0"
Um scan rápido confirma que o bleak enxerga os dispositivos:
python -c "import asyncio; from bleak import BleakScanner; print(*asyncio.run(BleakScanner.discover(timeout=8)), sep='\n')"
Decodificando advertisements
O firmware customizado dos sensores LYWSD03MMC transmite as leituras como service data sob o UUID de Environmental Sensing 0x181A. Dois layouts são comuns, e dá para diferenciá-los pelo tamanho:
- Formato pvvx, 15 bytes, little-endian: MAC (6 bytes, invertido), temperatura como int16 em 0,01 grau C, umidade como uint16 em 0,01 por cento, milivolts da bateria como uint16, porcentagem da bateria, um contador de frames e flags.
- Formato ATC1441, 13 bytes, big-endian: MAC (6 bytes), temperatura como int16 em 0,1 grau C, porcentagem de umidade, porcentagem da bateria, milivolts da bateria como uint16 e um contador.
Decodificar isso com struct é curto e testável. Para outros dispositivos, olhe o dicionário manufacturer_data (indexado pelo ID da empresa) ou service_data (indexado pela string do UUID) no advertisement e aplique o formato documentado pelo fabricante.
O script da ponte Raspberry Pi BLE para MQTT
O script roda um scanner contínuo com um callback de detecção. Cada advertisement de um sensor conhecido é decodificado e armazenado; uma tarefa async separada publica a leitura mais recente de cada sensor no máximo uma vez a cada 30 segundos. O paho-mqtt roda sua própria thread de rede via loop_start(), então convive bem com o asyncio.
import asyncio
import json
import os
import struct
import time
import uuid
import paho.mqtt.client as mqtt
from bleak import BleakScanner
BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"ble-bridge-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("TOPIC_PREFIX", "connectmq/demo/b4n8r2/ble")
PUBLISH_EVERY = 30 # segundos por sensor
ESS_UUID = "0000181a-0000-1000-8000-00805f9b34fb"
latest = {} # mac -> leitura
last_sent = {} # mac -> timestamp
def decode_181a(data: bytes):
if len(data) == 15: # formato customizado pvvx
temp, hum, mv, batt = struct.unpack_from("<hHHB", data, 6)
return {"temperature": temp / 100, "humidity": hum / 100,
"battery_mv": mv, "battery": batt}
if len(data) == 13: # formato ATC1441
temp, hum, batt, mv = struct.unpack_from(">hBBH", data, 6)
return {"temperature": temp / 10, "humidity": hum,
"battery_mv": mv, "battery": batt}
return None
def on_advertisement(device, adv):
data = adv.service_data.get(ESS_UUID)
if not data:
return
reading = decode_181a(data)
if reading:
reading["rssi"] = adv.rssi
reading["ts"] = int(time.time())
latest[device.address.replace(":", "").lower()] = reading
def on_connect(client, userdata, flags, reason_code, properties):
print("MQTT connected:", reason_code)
client.publish(f"{PREFIX}/bridge/status", "online", qos=1, retain=True)
def make_client():
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
if USERNAME:
c.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
c.tls_set()
c.will_set(f"{PREFIX}/bridge/status", "offline", qos=1, retain=True)
c.reconnect_delay_set(1, 60)
c.on_connect = on_connect
c.connect_async(BROKER, PORT, keepalive=60)
c.loop_start()
return c
async def publisher(client):
while True:
now = time.time()
for mac, reading in list(latest.items()):
if now - last_sent.get(mac, 0) >= PUBLISH_EVERY and client.is_connected():
client.publish(f"{PREFIX}/{mac}", json.dumps(reading), qos=1, retain=True)
last_sent[mac] = now
await asyncio.sleep(1)
async def main():
client = make_client()
scanner = BleakScanner(detection_callback=on_advertisement)
await scanner.start()
print("scanning...")
try:
await publisher(client)
finally:
await scanner.stop()
client.publish(f"{PREFIX}/bridge/status", "offline", qos=1, retain=True)
client.loop_stop()
client.disconnect()
if __name__ == "__main__":
asyncio.run(main())
Assine a partir do seu notebook para ver as leituras chegando:
mosquitto_sub -h broker.connectmq.com -p 1883 -i ble-viewer-b4n8r2 -t "connectmq/demo/b4n8r2/ble/#" -v
Cada sensor ganha seu próprio topic com o nome do seu endereço MAC, e a leitura é retida, então um dashboard que conecta depois vê imediatamente o valor mais recente. Se preferir nomes amigáveis, mantenha um pequeno dicionário que mapeia endereços MAC para nomes de cômodos e use-os no topic.
Decisões de projeto que importam
Limite a taxa por dispositivo. Os sensores podem enviar advertisements a cada poucos segundos. Publicar cada um desperdiça banda e, no servidor MQTT público gratuito, atinge rapidamente o limite de 10 mensagens por segundo por cliente quando você tem muitos sensores. Uma janela de 30 segundos por sensor é mais que suficiente para o clima de um ambiente.
Retenha o estado mais recente, não o histórico. Retained messages entregam o valor atual instantaneamente a novos subscribers. O histórico pertence a um banco de dados: um subscriber gravando em SQLite ou InfluxDB, como o do guia de Grafana e InfluxDB no Raspberry Pi, é o lugar certo para ele.
Inclua o RSSI. A intensidade do sinal mostra quando um sensor está prestes a sair do alcance ou quando a ponte precisa mudar de lugar. Também é uma forma barata de detectar um sensor morrendo antes que a leitura da bateria caia.
Fique atento ao silêncio. Um sensor que para de enviar advertisements fica invisível para a ponte. Adicione o campo ts aos dashboards, ou alerte quando ele ficar antigo. As regras de alerta por limite do ConnectMQ funcionam com valores numéricos, então um subscriber que publica "segundos desde a última leitura" por sensor dá a você um alarme simples de dados desatualizados.
Solução de problemas
- Nenhum dispositivo encontrado: rode
bluetoothctl power onerfkill list. Em um Pi em que Wi-Fi e Bluetooth compartilham um único chip, tráfego Wi-Fi intenso em 2,4 GHz pode reduzir a recepção BLE; Ethernet ou Wi-Fi 5 GHz ajudam. - Dispositivos encontrados, mas sem service data: o sensor está com o firmware de fábrica, que criptografa seus advertisements, ou usa outro formato. Imprima
advno callback para inspecionar os campos brutos. org.bluez.Error.InProgress: outro processo já está escaneando. Pare os outros scanners ou rode apenas uma ponte por adaptador.- As leituras param após horas: o BlueZ às vezes precisa ser reiniciado em kernels mais antigos. Rodar a ponte como serviço do systemd com
Restart=alwaysresolve isso, como descrito em como rodar um script Python MQTT no boot. - Alcance fraco: um cabo extensor USB para afastar o dongle da caixa metálica do Pi e das portas USB 3 pode fazer uma diferença perceptível, já que o USB 3 é conhecido por causar interferência em 2,4 GHz.
Lendo características GATT
Alguns dispositivos expõem dados apenas por meio de uma conexão. O bleak resolve isso com BleakClient:
from bleak import BleakClient
async def read_battery(address):
async with BleakClient(address) as client:
value = await client.read_gatt_char("00002a19-0000-1000-8000-00805f9b34fb")
return value[0]
Conexões são caras, então leia valores GATT em um cronograma lento e pare de escanear enquanto conecta se o seu adaptador tiver dificuldade em fazer as duas coisas ao mesmo tempo. A documentação do bleak cobre as notificações, que são a forma eficiente de transmitir dados com conexão.
Indo para produção
Quando a ponte estiver funcionando, saia do servidor público, que pode ser lido por qualquer pessoa. Crie um broker privado no ConnectMQ, defina MQTT_HOST, MQTT_PORT=8883, MQTT_USERNAME, MQTT_PASSWORD e MQTT_CLIENT_ID a partir do portal e troque o prefixo de topic por algo como home/ble. A ponte não precisa de portas de entrada, apenas de uma conexão TLS de saída, então funciona atrás de qualquer roteador doméstico ou modem celular. Um broker MQTT gerenciado também significa que não há processo de broker no Pi para manter.
Se MQTT no Pi for novidade para você, o tutorial de MQTT com Raspberry Pi cobre o básico, e o artigo sobre gateway IoT com Raspberry Pi mostra como combinar BLE com outros protocolos em um único dispositivo.
Conecte seus sensores BLE ao ConnectMQ
Cadastre-se em app.connectmq.com no plano Serverless gratuito, sem cartão de crédito, e você terá credenciais de broker em poucos minutos. Aponte a ponte para elas e abra o explorador de topics ao vivo para ver cada sensor aparecer em seu próprio topic.
Perguntas frequentes
Quantos sensores BLE um Raspberry Pi consegue tratar?
Com scan passivo de advertisements, dezenas de sensores dentro do alcance do rádio são comuns, porque o Pi apenas escuta. O alcance e as paredes costumam limitar antes da CPU.
Preciso parear os sensores?
Não. Sensores baseados em advertisement transmitem para quem estiver ouvindo, então não é preciso pareamento nem conexão. A exceção são os advertisements criptografados, que exigem a chave do dispositivo.
Posso rodar isso em um Raspberry Pi Zero 2 W?
Sim. A ponte usa pouca CPU e memória, e o Zero 2 W tem Bluetooth e Wi-Fi integrados, o que o torna uma boa ponte de baixo custo por cômodo ou prédio.
Devo usar QoS 0 ou QoS 1 para leituras de sensores?
QoS 1 é um bom padrão para leituras que você quer entregues pelo menos uma vez. Para valores muito frequentes, em que a próxima leitura substitui a anterior, QoS 0 serve bem.



