Publicar estatísticas do Raspberry Pi via MQTT é a forma mais simples de saber, num relance, se cada Pi que você opera está saudável. Neste tutorial você vai escrever um serviço Python que informa temperatura da CPU, carga, memória, uso de disco, uptime e flags de throttling de energia em intervalos fixos, anuncia que está online com um status retained e deixa o broker marcá-lo como offline por meio de um last will. O mesmo script roda sem alterações em um Pi ou em cinquenta.
Por que monitorar Raspberry Pis via MQTT
Pis em campo falham de formas previsíveis: cartões SD enchem de logs, gabinetes retêm calor e a CPU entra em throttling, fontes baratas causam subtensão e quedas de Wi-Fi deixam um dispositivo silenciosamente inacessível. Stacks de monitoramento tradicionais consultam cada máquina, o que significa abrir portas ou rodar um agente que precisa de acesso de entrada. O MQTT inverte essa lógica. Cada Pi abre uma única conexão de saída com um broker e envia seus próprios números, então funciona atrás de NAT, em roteadores 4G e em redes que você não controla.
O broker também oferece presença de graça. Com um status online retained e um last will offline, qualquer dashboard que assine os topics vê na hora o estado atual de cada Pi, e o broker o atualiza quando um dispositivo some. Se esse mecanismo não for familiar, leia primeiro o que faz um broker MQTT.
As métricas e de onde elas vêm
| Métrica | Origem | Por que importa |
|---|---|---|
cpu_temp |
/sys/class/thermal/thermal_zone0/temp |
O throttling começa conforme a temperatura sobe |
load1, load5 |
os.getloadavg() |
Carga sustentada acima do número de núcleos indica fila acumulada |
cpu_pct |
psutil.cpu_percent() |
Uso de CPU de curto prazo |
mem_pct |
psutil.virtual_memory() |
Vazamentos de memória em serviços de longa duração |
disk_pct |
psutil.disk_usage("/") |
Um sistema de arquivos raiz cheio quebra tudo |
uptime_s |
psutil.boot_time() |
Reinicializações inesperadas aparecem como zeragens |
throttled |
vcgencmd get_throttled |
Flags de subtensão e de throttling térmico |
wifi_rssi |
/proc/net/wireless |
Sinal fraco explica as quedas |
O valor de get_throttled é um campo de bits. O bit 0 indica subtensão neste momento, o bit 2 indica throttling ativo e os bits 16 e 18 indicam que essas condições ocorreram desde o boot. Um valor diferente de zero já merece um alerta por si só, porque quase sempre aponta para um problema de fonte que acabará corrompendo o cartão SD. A documentação do Raspberry Pi lista todos os bits.
Desenho de topics para uma frota
Use uma subárvore por dispositivo, com o hostname como ID do dispositivo. Para testes no servidor MQTT público gratuito, adicione um segmento aleatório para que os dados de outras pessoas não colidam com os seus:
connectmq/demo/a9e217/fleet/<hostname>/stats JSON, every 30 s, not retained
connectmq/demo/a9e217/fleet/<hostname>/status "online" / "offline", retained, last will
Um cliente de monitoramento então assina connectmq/demo/a9e217/fleet/+/status e connectmq/demo/a9e217/fleet/+/stats e descobre novos Pis automaticamente quando eles publicam pela primeira vez.
Instale as dependências
Em cada Pi, crie um ambiente virtual para que o serviço não dependa dos pacotes Python do sistema:
sudo apt update && sudo apt install -y python3-venv
python3 -m venv ~/pistats-env
~/pistats-env/bin/pip install "paho-mqtt>=2.0" psutil
O publicador de estatísticas do Raspberry Pi via MQTT
Salve isto como ~/pistats.py:
#!/usr/bin/env python3
"""Publish Raspberry Pi health metrics over MQTT."""
import json
import os
import socket
import ssl
import subprocess
import time
import paho.mqtt.client as mqtt
import psutil
HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME") # YOUR_USERNAME em produção
PASSWORD = os.getenv("MQTT_PASSWORD") # YOUR_PASSWORD em produção
HOSTNAME = socket.gethostname()
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"pistats-{HOSTNAME}-a9e217")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/a9e217/fleet")
INTERVAL = int(os.getenv("INTERVAL", "30"))
STATS_TOPIC = f"{PREFIX}/{HOSTNAME}/stats"
STATUS_TOPIC = f"{PREFIX}/{HOSTNAME}/status"
def cpu_temp():
try:
with open("/sys/class/thermal/thermal_zone0/temp") as f:
return round(int(f.read().strip()) / 1000, 1)
except (OSError, ValueError):
return None
def throttled():
try:
out = subprocess.run(["vcgencmd", "get_throttled"],
capture_output=True, text=True, timeout=2).stdout
return int(out.strip().split("=")[1], 16)
except (OSError, IndexError, ValueError, subprocess.TimeoutExpired):
return None
def wifi_rssi():
try:
with open("/proc/net/wireless") as f:
lines = f.readlines()[2:]
for line in lines:
parts = line.split()
if parts and parts[0].startswith("wlan"):
return int(float(parts[3]))
except (OSError, IndexError, ValueError):
pass
return None
def collect():
load1, load5, _ = os.getloadavg()
t = throttled()
return {
"ts": int(time.time()),
"cpu_temp": cpu_temp(),
"cpu_pct": psutil.cpu_percent(interval=None),
"load1": round(load1, 2),
"load5": round(load5, 2),
"mem_pct": psutil.virtual_memory().percent,
"disk_pct": psutil.disk_usage("/").percent,
"uptime_s": int(time.time() - psutil.boot_time()),
"throttled": t,
"undervoltage_now": bool(t & 0x1) if t is not None else None,
"undervoltage_seen": bool(t & 0x10000) if t is not None else None,
"wifi_rssi": wifi_rssi(),
}
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connect failed: {reason_code}")
return
print(f"Connected to {HOST}:{PORT} as {CLIENT_ID}")
client.publish(STATUS_TOPIC, "online", qos=1, retain=True)
def on_disconnect(client, userdata, flags, reason_code, properties):
print(f"Disconnected: {reason_code}, paho will reconnect")
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)
client.will_set(STATUS_TOPIC, "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.reconnect_delay_set(min_delay=2, max_delay=60)
client.connect_async(HOST, PORT, keepalive=60)
client.loop_start()
psutil.cpu_percent(interval=None) # inicializa o contador
try:
while True:
time.sleep(INTERVAL)
if client.is_connected():
client.publish(STATS_TOPIC, json.dumps(collect()), qos=0)
finally:
client.publish(STATUS_TOPIC, "offline", qos=1, retain=True).wait_for_publish(3)
client.disconnect()
client.loop_stop()
Algumas escolhas são deliberadas: as estatísticas usam QoS 0 porque perder uma amostra de vez em quando não importa e a próxima chega logo. O status usa QoS 1 e retain porque a presença precisa estar correta. connect_async com loop_start permite que o script inicie mesmo que a rede ainda não esteja disponível no boot, e o paho continua tentando em segundo plano. Em um desligamento limpo, o bloco finally publica offline explicitamente, porque o last will só dispara em uma desconexão inesperada.
Rode o script uma vez manualmente e acompanhe a saída de outra máquina:
~/pistats-env/bin/python ~/pistats.py
mosquitto_sub -h broker.connectmq.com -p 1883 -i watcher-a9e217 \
-t 'connectmq/demo/a9e217/fleet/#' -v
Desconecte o cabo de rede do Pi e, depois de cerca de um intervalo e meio de keepalive, o broker publica offline em nome dele. É o last will do MQTT fazendo o seu trabalho.
Execute como serviço systemd
Crie um arquivo de ambiente para manter as credenciais fora do script:
sudo tee /etc/pistats.env >/dev/null <<'EOF'
MQTT_HOST=broker.connectmq.com
MQTT_PORT=1883
INTERVAL=30
EOF
sudo chmod 600 /etc/pistats.env
sudo tee /etc/systemd/system/pistats.service >/dev/null <<'EOF'
[Unit]
Description=Publish Raspberry Pi stats over MQTT
After=network-online.target
Wants=network-online.target
[Service]
User=pi
EnvironmentFile=/etc/pistats.env
ExecStart=/home/pi/pistats-env/bin/python /home/pi/pistats.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now pistats
journalctl -u pistats -f
Ajuste o usuário e os caminhos à sua conta. Para mais padrões de scripts executados no boot, veja como executar um script Python MQTT na inicialização.
Migrando a frota para um broker privado
Estatísticas do sistema revelam hostnames, uptime e detalhes de rede, então uma frota real não deve reportar a um broker público; o servidor MQTT público serve apenas para testes e protótipos. Com o ConnectMQ, um broker MQTT na nuvem e também um broker MQTT gerenciado, você cria um broker no portal e recebe as credenciais. Altere apenas o arquivo de ambiente:
MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
MQTT_PREFIX=site1/fleet
O ConnectMQ vincula os client IDs às credenciais emitidas, então cada Pi precisa do seu próprio conjunto gerado no portal. A porta 8883 usa TLS com um certificado Let's Encrypt em que o Pi já confia. O plano Serverless (Free) permite até 25 conexões, o que cobre uma frota considerável em casa ou no laboratório; o plano Serverless (Paid) vai muito além.
O portal do ConnectMQ também ajuda no lado do monitoramento sem software extra. O explorador de topics ao vivo mostra as estatísticas mais recentes de cada Pi, e regras de alerta por limite podem sinalizar quando um valor como cpu_temp ultrapassa um patamar, com um log de alertas para revisar depois.
Escolhendo limites de alerta que façam sentido
Coletar números é fácil; decidir quais merecem uma notificação é onde o monitoramento de frota mostra seu valor. Alertas que disparam o tempo todo acabam ignorados, então comece com uma lista curta de condições que quase sempre indicam um problema real:
- Subtensão registrada desde o boot. Qualquer
undervoltage_seendiferente de zero aponta para uma fonte fraca ou um cabo USB fino. Corrija antes que isso corrompa o cartão SD. - Temperatura da CPU que continua alta. Picos curtos durante atualizações são normais. Uma temperatura que fica perto do ponto de throttling por muitos minutos indica uma ventilação obstruída, um dissipador ausente ou um gabinete exposto ao sol.
- Uso de disco acima de cerca de 90 por cento. Logs e imagens de contêiner crescem silenciosamente. Um sistema de arquivos raiz cheio quebra atualizações de pacotes, bancos de dados e às vezes o próprio boot.
- Uptime zerando inesperadamente. Se
uptime_scai sem uma reinicialização planejada, o Pi travou ou perdeu energia, e a causa costuma ser alimentação ou calor. - Offline por mais de alguns intervalos. Combine o status retained com a idade da última mensagem de estatísticas para que uma breve oscilação do Wi-Fi não acione ninguém.
Carga média e memória são melhor acompanhadas como tendências do que como alertas. Uma subida lenta ao longo de dias geralmente revela um serviço com vazamento muito antes de ele causar problemas.
Usando os dados
Com os números fluindo, escolha quanta ferramenta você quer:
- Olhada rápida: o explorador de topics do ConnectMQ ou qualquer cliente MQTT de desktop.
- Painel na parede: o dashboard em modo quiosque no Raspberry Pi pode mostrar um bloco por Pi.
- Histórico e gráficos: Telegraf gravando no InfluxDB com Grafana por cima, como no tutorial de Raspberry Pi com Grafana e InfluxDB.
Solução de problemas
| Sintoma | Causa | Correção |
|---|---|---|
throttled é null |
vcgencmd ausente ou sem permissão |
Instale libraspberrypi-bin e adicione o usuário ao grupo video |
cpu_pct sempre 0.0 na primeira amostra |
Contador não inicializado | O script o inicializa antes do loop |
| O status alterna entre online e offline repetidamente | Dois Pis com o mesmo client ID | Dê a cada Pi um client ID único |
offline nunca aparece após desconectar |
Aguardando o timeout do keepalive | Espere cerca de 1,5 vez o keepalive |
wifi_rssi é null no Ethernet |
Sem interface sem fio | Esperado; ignore ou remova o campo |
Comece a usar o ConnectMQ
Reúna a saúde de todos os Pis em um só lugar: cadastre-se em app.connectmq.com, crie um broker Serverless gratuito sem cartão de crédito e coloque as credenciais em /etc/pistats.env. Para a série completa de projetos com Pi, comece pelo tutorial de MQTT com Raspberry Pi.
Perguntas frequentes
Com que frequência um Raspberry Pi deve publicar estatísticas do sistema?
A cada 30 a 60 segundos é mais que suficiente para monitorar a saúde. Intervalos menores aumentam o tráfego sem revelar muito mais, já que temperatura e uso de disco mudam devagar.
Publicar estatísticas deixa o Raspberry Pi mais lento?
Não de forma significativa. Ler alguns arquivos e enviar uma pequena mensagem JSON a cada 30 segundos é uma carga desprezível, mesmo em um Pi Zero 2 W.
Como sei quando um Pi fica offline?
Assine o topic de status retained. O script define um last will offline, então o broker o publica automaticamente quando o Pi se desconecta de forma inesperada.
Posso adicionar a temperatura da GPU ou métricas personalizadas?
Sim, adicione campos ao dicionário de collect(). Nos modelos atuais de Pi, CPU e GPU compartilham o mesmo chip, então o valor da zona térmica já reflete ambos.



