Raspberry Pi

Gateway IoT com Raspberry Pi: Colete Dados BLE, Modbus e Serial e Envie via MQTT

Um guia prático para transformar um Raspberry Pi em gateway IoT: adaptadores de protocolo para BLE, Modbus e serial, um modelo padronizado de topics e payloads, um buffer store-and-forward e um uplink seguro para um broker MQTT na nuvem.

Raspberry Pi em gabinete para trilho DIN atuando como gateway IoT entre sensores BLE, um medidor Modbus e um broker MQTT na nuvem
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

Um gateway IoT com Raspberry Pi fica entre os dispositivos que não conseguem chegar à internet sozinhos e os sistemas que precisam dos dados deles. Neste guia você vai projetar um gateway que coleta leituras de sensores Bluetooth Low Energy, dispositivos Modbus e instrumentos seriais, padroniza tudo em um único modelo de topics e payloads, armazena os dados em disco quando a rede falha e os encaminha com segurança para um broker MQTT na nuvem. O código é um esqueleto funcional que você pode expandir, não um brinquedo.

O que um gateway IoT realmente faz

Muitos dispositivos de campo falam protocolos que nunca foram pensados para a internet: um termômetro BLE tem alcance de poucos metros, um medidor de energia Modbus RTU conversa por um par RS-485 e uma balança de laboratório imprime linhas por uma porta serial. Um gateway faz quatro trabalhos por eles:

  1. Adaptação de protocolo. Lê cada dispositivo no seu protocolo nativo.
  2. Padronização. Transforma tudo em topics e payloads JSON consistentes, com unidades e timestamps.
  3. Confiabilidade. Guarda os dados quando o uplink cai e os entrega depois, em ordem.
  4. Uplink seguro. Faz uma única conexão de saída autenticada e criptografada, em vez de expor os dispositivos.

Um Raspberry Pi é uma boa plataforma para isso: roda Linux completo e Python, tem USB para adaptadores RS-485 e seriais, Bluetooth integrado e armazenamento suficiente para dias de dados em buffer. Para ambientes mais agressivos, como o chão de fábrica de uma planta em Joinville ou uma subestação, controladores industriais baseados no Pi, como o Revolution Pi, seguem a mesma abordagem de software em um gabinete para trilho DIN.

Arquitetura do gateway

 BLE sensors ----> [ble adapter]    \
 Modbus RTU/TCP -> [modbus adapter]  >--> normalise --> SQLite outbox --> MQTT uplink --> cloud broker
 Serial devices -> [serial adapter] /                                         ^
                                                                              | commands (optional)

Cada adaptador roda de forma independente e entrega as leituras a uma outbox compartilhada. Uma única tarefa de uplink esvazia a outbox para o broker e só apaga as linhas depois que o broker confirma o recebimento. Essa separação é a decisão de design mais importante de todo o gateway: um dispositivo Modbus lento ou um sensor BLE instável nunca bloqueia o uplink, e uma conexão de internet morta nunca bloqueia a coleta de dados.

Camada Biblioteca ou ferramenta Observações
BLE bleak Varredura assíncrona; funciona com o BlueZ no Pi OS
Modbus RTU/TCP pymodbus Adaptador USB RS-485 para RTU
Serial pyserial Instrumentos baseados em linhas, GPS, leitores de código de barras
Buffer SQLite (biblioteca padrão) Sobrevive a reboots, fácil de inspecionar
Uplink paho-mqtt 2.x TLS, QoS 1, last will
Gerenciador de processos systemd Reinicia em caso de falha, sobe no boot

Escolhendo topics e payloads

Defina o modelo de dados antes de escrever os adaptadores, porque mudá-lo depois significa mudar todos os consumidores. Uma hierarquia que espelha o local físico funciona bem e escala para nomenclaturas industriais como a ISA-95:

<site>/<area>/<device>/telemetry   {"ts": 1760600000, "values": {"temp_c": 21.4}, "src": "ble"}
<site>/<area>/<device>/status      "online" / "offline" (retained)
<site>/gateway/<gateway-id>/status "online" / "offline" (retained, last will)

Coloque o timestamp no payload, definido pelo gateway no momento da leitura. Quando os dados em buffer forem entregues com uma hora de atraso, os consumidores ainda saberão quando cada leitura foi feita. Para orientações sobre hierarquias em sites maiores, as convenções do Sparkplug B e do Unified Namespace são uma boa referência.

O esqueleto do gateway IoT com Raspberry Pi

Instale as dependências em um ambiente virtual:

sudo apt update && sudo apt install -y python3-venv bluetooth sqlite3
python3 -m venv ~/gw-env
~/gw-env/bin/pip install "paho-mqtt>=2.0" "pymodbus>=3.10" pyserial bleak
sudo usermod -aG dialout $USER   # acesso à porta serial; saia e entre de novo na sessão

Salve o núcleo como ~/gateway.py. Ele inclui a outbox, o uplink e um adaptador de exemplo por protocolo.

#!/usr/bin/env python3
"""Raspberry Pi IoT gateway: BLE + Modbus + serial -> SQLite outbox -> MQTT."""
import asyncio
import json
import os
import socket
import sqlite3
import ssl
import time

import paho.mqtt.client as mqtt

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")            # YOUR_USERNAME
PASSWORD = os.getenv("MQTT_PASSWORD")            # YOUR_PASSWORD
GW_ID = socket.gethostname()
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"gw-{GW_ID}-91b6f0")  # YOUR_CLIENT_ID
SITE = os.getenv("SITE", "connectmq/demo/91b6f0/site1")
DB_PATH = os.getenv("OUTBOX", "/var/lib/gateway/outbox.db")

# ---------- outbox ----------
db = sqlite3.connect(DB_PATH, isolation_level=None, check_same_thread=False)
db.execute("PRAGMA journal_mode=WAL")
db.execute("CREATE TABLE IF NOT EXISTS outbox (id INTEGER PRIMARY KEY, topic TEXT, payload TEXT)")


def enqueue(area, device, values, src):
    payload = json.dumps({"ts": int(time.time()), "values": values, "src": src})
    db.execute("INSERT INTO outbox (topic, payload) VALUES (?, ?)",
               (f"{SITE}/{area}/{device}/telemetry", payload))


# ---------- uplink ----------
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID,
                     protocol=mqtt.MQTTv5)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
status_topic = f"{SITE}/gateway/{GW_ID}/status"
client.will_set(status_topic, "offline", qos=1, retain=True)
client.on_connect = lambda c, u, f, rc, p: (not rc.is_failure) and c.publish(
    status_topic, "online", qos=1, retain=True)
client.reconnect_delay_set(1, 60)
client.connect_async(HOST, PORT, keepalive=60)
client.loop_start()


async def uplink(batch=50):
    while True:
        if not client.is_connected():
            await asyncio.sleep(2)
            continue
        rows = db.execute("SELECT id, topic, payload FROM outbox ORDER BY id LIMIT ?",
                          (batch,)).fetchall()
        if not rows:
            await asyncio.sleep(1)
            continue
        for row_id, topic, payload in rows:
            info = client.publish(topic, payload, qos=1)
            # Aguarda o PUBACK sem bloquear o event loop
            for _ in range(100):
                if info.is_published():
                    break
                await asyncio.sleep(0.05)
            if not info.is_published():
                break                      # mantém a linha, tenta de novo depois
            db.execute("DELETE FROM outbox WHERE id = ?", (row_id,))
            await asyncio.sleep(0.11)      # fica abaixo de ~10 msg/s no broker público


# ---------- adapters ----------
async def modbus_adapter():
    from pymodbus.client import AsyncModbusTcpClient
    mb = AsyncModbusTcpClient(os.getenv("MODBUS_HOST", "192.168.1.60"), port=502)
    while True:
        try:
            if not mb.connected:
                await mb.connect()
            rr = await mb.read_holding_registers(0, count=2, device_id=1)
            if not rr.isError():
                enqueue("utilities", "meter1",
                        {"voltage_v": rr.registers[0] / 10, "current_a": rr.registers[1] / 100},
                        "modbus")
        except Exception as exc:
            print("modbus:", exc)
        await asyncio.sleep(10)


async def serial_adapter():
    import serial
    port = os.getenv("SERIAL_PORT", "/dev/ttyUSB1")
    while True:
        try:
            with serial.Serial(port, 9600, timeout=1) as ser:
                while True:
                    line = await asyncio.to_thread(ser.readline)
                    text = line.decode(errors="ignore").strip()
                    if text:
                        enqueue("lab", "scale1", {"reading": text}, "serial")
        except Exception as exc:
            print("serial:", exc)
            await asyncio.sleep(5)


async def ble_adapter():
    from bleak import BleakScanner
    wanted = {m.strip().upper() for m in os.getenv("BLE_MACS", "").split(",") if m.strip()}

    def on_adv(device, adv):
        if device.address.upper() in wanted:
            enqueue("rooms", device.address.replace(":", "").lower(),
                    {"rssi": adv.rssi,
                     "mfr": {str(k): v.hex() for k, v in adv.manufacturer_data.items()}},
                    "ble")

    async with BleakScanner(on_adv):
        while True:
            await asyncio.sleep(3600)


async def main():
    tasks = [uplink()]
    if os.getenv("ENABLE_MODBUS") == "1":
        tasks.append(modbus_adapter())
    if os.getenv("ENABLE_SERIAL") == "1":
        tasks.append(serial_adapter())
    if os.getenv("ENABLE_BLE") == "1":
        tasks.append(ble_adapter())
    await asyncio.gather(*tasks)


if __name__ == "__main__":
    asyncio.run(main())

Cada adaptador é propositalmente mínimo. Dispositivos reais precisam de decodificação: sensores BLE empacotam a temperatura em dados de fabricante ou de serviço em formatos específicos de cada fornecedor, e registradores Modbus precisam da escala e da ordem de palavras indicadas no manual do equipamento. O tutorial Raspberry Pi BLE para MQTT e o gateway Modbus para MQTT com Raspberry Pi se aprofundam em cada um. Observe que a pymodbus 3.10 e posteriores chamam o argumento de unidade de device_id; versões 3.x anteriores o chamam de slave.

Crie a pasta de dados e teste primeiro com o Modbus desativado, só o uplink, inserindo uma linha manualmente:

sudo mkdir -p /var/lib/gateway && sudo chown $USER /var/lib/gateway
ENABLE_MODBUS=0 ~/gw-env/bin/python ~/gateway.py &
sqlite3 /var/lib/gateway/outbox.db \
  "INSERT INTO outbox(topic,payload) VALUES('connectmq/demo/91b6f0/site1/test/dev/telemetry','{\"ok\":true}')"

Veja a mensagem chegar no servidor MQTT público gratuito com mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-91b6f0 -t 'connectmq/demo/91b6f0/#' -v. Ele serve para testes e protótipos, não para dados de produção.

Por que o store-and-forward importa

O padrão outbox significa que os dados são gravados em disco no momento em que são lidos e só são apagados depois que o broker confirma o recebimento. Isso traz várias propriedades de graça:

  • Reboots não perdem nada. As linhas persistem no SQLite.
  • As quedas são invisíveis para os adaptadores. Eles continuam enfileirando; a outbox cresce e esvazia depois.
  • A ordem é preservada. As linhas são enviadas por ID crescente.
  • Você pode inspecionar o backlog com um único SELECT COUNT(*).

Dois cuidados. Primeiro, limite a outbox: adicione um DELETE periódico das linhas mais antigas acima de um limite, para que uma queda de uma semana não encha o disco. Segundo, cuidado com o desgaste do cartão SD. O modo WAL reduz as gravações, mas um gateway que usa muito o buffer deve ter um SSD ou um cartão de grau industrial. Mais detalhes no guia de buffer offline.

Alternativa: Mosquitto local mais uma bridge

Algumas equipes preferem adaptadores que publicam em um broker Mosquitto local no Pi, com uma bridge encaminhando para a nuvem. Isso funciona bem quando ferramentas existentes (Node-RED, Telegraf, software de fornecedores) já falam MQTT e quando consumidores locais precisam dos dados mesmo sem internet. A contrapartida é que o buffer passa a depender dos limites de fila do Mosquitto, e não da sua própria outbox. O tutorial de configuração de bridge no Mosquitto mostra esse design de ponta a ponta.

O gateway é a única coisa no local que fala com a internet, então trate-o como uma fronteira de segurança:

  1. Use TLS na porta 8883 com verificação de certificado. Nunca desative a checagem de hostname.
  2. Dê a cada gateway credenciais e client ID próprios, para que um Pi comprometido possa ser revogado isoladamente.
  3. Mantenha as redes de campo (RS-485, BLE) e o uplink logicamente separados; o gateway não deve rotear tráfego entre eles.
  4. Rode o serviço com um usuário sem privilégios e mantenha o Pi OS atualizado.
  5. Desative o login SSH por senha em favor de chaves e remova os serviços que você não usa.

No lado da nuvem, um broker MQTT na nuvem como o ConnectMQ elimina a necessidade de operar e blindar um broker por conta própria; por ser um broker MQTT gerenciado, atualizações e operação ficam com o serviço. Crie um broker no portal, copie usuário, senha e client ID e defina MQTT_PORT=8883, MQTT_USERNAME=YOUR_USERNAME, MQTT_PASSWORD=YOUR_PASSWORD e MQTT_CLIENT_ID=YOUR_CLIENT_ID no ambiente do serviço. O ConnectMQ vincula os client IDs às credenciais, aplica cotas por plano e proteção contra flood e bloqueia IPs após repetidas falhas de login. Se os seus topics seguem uma estrutura ISA-95, a visão de dispositivos do portal os agrupa automaticamente por empresa, site, área e linha.

Fique atento aos limites do plano ao esvaziar um backlog. O plano Serverless gratuito permite QoS até 1 e mensagens de 256 KB; o servidor público permite cerca de 10 mensagens por segundo por cliente, e é por isso que o uplink faz uma pequena pausa entre as publicações.

Rode como serviço

sudo tee /etc/systemd/system/gateway.service >/dev/null <<'EOF'
[Unit]
Description=Raspberry Pi IoT gateway
After=network-online.target bluetooth.target
Wants=network-online.target

[Service]
User=pi
EnvironmentFile=/etc/gateway.env
ExecStart=/home/pi/gw-env/bin/python /home/pi/gateway.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now gateway

Coloque as variáveis MQTT_*, SITE e ENABLE_* em /etc/gateway.env com chmod 600.

Comece com o ConnectMQ

Dê ao seu gateway um uplink seguro em minutos: crie um broker em app.connectmq.com no plano Serverless gratuito, sem cartão de crédito, e cole as credenciais geradas em /etc/gateway.env. O tutorial MQTT com Raspberry Pi traz os fundamentos, se você precisar.

Perguntas frequentes

Qual modelo de Raspberry Pi é melhor para um gateway IoT?

Um Pi 4 ou Pi 5 com 2 GB ou mais dá conta com folga de vários adaptadores de protocolo e de um buffer local. Um Pi Zero 2 W aguenta um único adaptador leve, mas tem pouca margem.

O gateway deve publicar direto na nuvem ou por um Mosquitto local?

Publique direto quando o gateway for o único produtor e você quiser controle total do buffer. Use um Mosquitto local com bridge quando várias ferramentas locais já falam MQTT ou precisam dos dados durante as quedas.

Como envio comandos da nuvem para os dispositivos de campo?

Faça o gateway assinar um topic de comando como <site>/<area>/<device>/set e traduza as mensagens em escritas Modbus ou comandos seriais. Valide cada comando antes de executá-lo.

Por quanto tempo o gateway consegue armazenar dados offline?

Pelo tempo que o seu disco e o limite da outbox permitirem. Dimensione o limite a partir da sua taxa de mensagens e da queda mais longa que você precisa suportar.

Artigos relacionados

Registro de Acessos Raspberry Pi RFID MQTT com o Leitor RC522

Ligue um leitor RFID RC522 a um Raspberry Pi, leia tags em Python, confira-as em uma lista de permissões local e publique cada tentativa de acesso como um evento MQTT que loggers, dashboards e ferramentas de alerta podem consumir.

8 min de leitura

Confiabilidade do Raspberry Pi: Rodando um Pi 24/7 como Gateway IoT

Um guia prático para manter um gateway MQTT com Raspberry Pi rodando por meses: desgaste do armazenamento, sistemas de arquivos somente leitura, alimentação, refrigeração, watchdogs de hardware e software e monitoramento remoto de saúde.

9 min de leitura

Pico W vs Pico 2 W para MQTT: Desempenho, Memória e TLS do RP2040 vs RP2350

O Pico 2 W mantém o formato e o chip Wi-Fi do Pico W, mas troca o RP2040 pelo RP2350, mais rápido. Esta comparação mostra o que isso significa para dispositivos MQTT: CPU, memória, TLS, recursos de segurança, suporte de software e qual placa comprar para cada projeto.

9 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.