Publicar las estadísticas del sistema Raspberry Pi por MQTT es la forma más sencilla de saber, de un vistazo, si todas las Pi que gestionas están sanas. En este tutorial escribirás un servicio en Python que informa de la temperatura de la CPU, la carga, la memoria, el uso de disco, el tiempo en marcha y los flags de throttling de alimentación a intervalos fijos, se anuncia como online con un estado retained y deja que el broker la marque como offline mediante un last will. El mismo script funciona sin cambios en una Pi o en cincuenta.
Por qué monitorizar las Raspberry Pi por MQTT
Las Pi desplegadas en campo fallan de formas previsibles: las tarjetas SD se llenan de logs, las cajas acumulan calor y la CPU reduce su frecuencia, las fuentes baratas provocan subtensión y las caídas del Wi-Fi dejan un dispositivo inaccesible sin que nadie se entere. Las pilas de monitorización tradicionales consultan cada máquina, lo que implica abrir puertos o ejecutar un agente que necesita acceso entrante. MQTT le da la vuelta. Cada Pi abre una sola conexión saliente hacia un broker y envía sus propios números, así que funciona detrás de NAT, en routers móviles y en redes que no controlas.
El broker además te da la presencia gratis. Con un estado online retained y un last will offline, cualquier dashboard que se suscriba ve de inmediato el estado actual de cada Pi, y el broker lo actualiza cuando un dispositivo desaparece. Si la mecánica no te resulta familiar, lee primero qué hace un broker MQTT.
Las métricas y de dónde salen
| Métrica | Origen | Por qué importa |
|---|---|---|
cpu_temp |
/sys/class/thermal/thermal_zone0/temp |
El throttling empieza a medida que sube la temperatura |
load1, load5 |
os.getloadavg() |
Una carga sostenida por encima del número de núcleos indica trabajo acumulado |
cpu_pct |
psutil.cpu_percent() |
Uso de CPU a corto plazo |
mem_pct |
psutil.virtual_memory() |
Fugas de memoria en servicios de larga duración |
disk_pct |
psutil.disk_usage("/") |
Un sistema de archivos raíz lleno lo rompe todo |
uptime_s |
psutil.boot_time() |
Los reinicios inesperados aparecen como reseteos |
throttled |
vcgencmd get_throttled |
Flags de subtensión y de throttling térmico |
wifi_rssi |
/proc/net/wireless |
Una señal débil explica las desconexiones |
El valor de get_throttled es un campo de bits. El bit 0 indica subtensión en este momento, el bit 2 indica throttling actual, y los bits 16 y 18 indican que esas condiciones se han producido desde el arranque. Un valor distinto de cero merece una alerta por sí solo, porque casi siempre apunta a un problema de la fuente de alimentación que acabará corrompiendo la tarjeta SD. La documentación de Raspberry Pi detalla cada bit.
Diseño de topics para una flota
Usa un subárbol por dispositivo, con el hostname como ID del dispositivo. Para hacer pruebas en el servidor MQTT público gratuito, añade un segmento aleatorio para que los datos de nadie más choquen con los tuyos:
connectmq/demo/a9e217/fleet/<hostname>/stats JSON, cada 30 s, no retained
connectmq/demo/a9e217/fleet/<hostname>/status "online" / "offline", retained, last will
Un cliente de monitorización se suscribe entonces a connectmq/demo/a9e217/fleet/+/status y connectmq/demo/a9e217/fleet/+/stats y descubre automáticamente las Pi nuevas en cuanto publican por primera vez.
Instala las dependencias
En cada Pi, crea un entorno virtual para que el servicio no dependa de los paquetes de Python del 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
El publicador de estadísticas del sistema Raspberry Pi por MQTT
Guárdalo 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 en producción
PASSWORD = os.getenv("MQTT_PASSWORD") # YOUR_PASSWORD en producción
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 el 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()
Algunas decisiones deliberadas: las estadísticas usan QoS 0 porque perder una muestra de vez en cuando no importa y la siguiente llega enseguida. El estado usa QoS 1 y retain porque la presencia debe ser correcta. connect_async con loop_start hace que el script arranque aunque la red todavía no esté disponible al iniciar, y paho sigue reintentando en segundo plano. En un apagado limpio, el bloque finally publica offline explícitamente, porque el last will solo se dispara ante una desconexión inesperada.
Ejecútalo una vez a mano y observa la salida desde otro equipo:
~/pistats-env/bin/python ~/pistats.py
mosquitto_sub -h broker.connectmq.com -p 1883 -i watcher-a9e217 \
-t 'connectmq/demo/a9e217/fleet/#' -v
Desconecta el cable de red de la Pi y, tras aproximadamente intervalo y medio de keepalive, el broker publica offline en su nombre. Es el last will de MQTT haciendo su trabajo.
Ejecútalo como servicio systemd
Crea un archivo de entorno para que las credenciales queden fuera del 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
Ajusta el usuario y las rutas a tu cuenta. Para más patrones sobre scripts que arrancan con el sistema, consulta ejecutar un script MQTT de Python al arrancar.
Pasar la flota a un broker privado
Las estadísticas del sistema revelan hostnames, tiempo en marcha y detalles de red, así que una flota real no debería informar a un broker público. Con ConnectMQ, un broker MQTT en la nube, es decir, un broker MQTT gestionado, creas un broker en el portal y recibes credenciales. Cambia solo el archivo de entorno:
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
ConnectMQ vincula los client IDs a las credenciales emitidas, así que cada Pi necesita su propio juego de credenciales del portal. El puerto 8883 usa TLS con un certificado de Let's Encrypt en el que la Pi ya confía. El plan gratuito Serverless permite hasta 25 conexiones, suficiente para una flota doméstica o de laboratorio considerable; el plan Serverless de pago llega mucho más lejos.
El portal de ConnectMQ también ayuda en la parte de monitorización sin software adicional. El explorador de topics en vivo muestra las últimas estadísticas de cada Pi, y las reglas de alerta por umbral pueden señalar que un valor como cpu_temp cruza un límite, con un registro de alertas para revisarlas después.
Elegir umbrales de alerta que signifiquen algo
Recoger números es fácil; decidir cuáles merecen una notificación es donde la monitorización de flotas demuestra su valor. Las alertas que saltan constantemente acaban ignorándose, así que empieza con una lista corta de condiciones que casi siempre indican un problema real:
- Subtensión detectada desde el arranque. Cualquier
undervoltage_seendistinto de cero apunta a una fuente débil o a un cable USB demasiado fino. Corrígelo antes de que corrompa la tarjeta SD. - Temperatura de la CPU alta de forma sostenida. Los picos cortos durante las actualizaciones son normales. Una temperatura que se mantiene cerca del punto de throttling durante muchos minutos indica una ventilación bloqueada, un disipador ausente o una caja a pleno sol.
- Uso de disco por encima del 90 por ciento aproximadamente. Los logs y las imágenes de contenedores crecen en silencio. Un sistema de archivos raíz lleno rompe las actualizaciones de paquetes, las bases de datos y a veces el propio arranque.
- Tiempo en marcha que se reinicia inesperadamente. Si
uptime_scae sin un reinicio planificado, la Pi se colgó o perdió la alimentación, y la causa suele ser la alimentación o el calor. - Offline durante más de unos pocos intervalos. Combina el estado retained con la antigüedad del último mensaje de estadísticas para que un breve corte del Wi-Fi no despierte a nadie.
La carga media y la memoria se vigilan mejor como tendencias que como alertas. Una subida lenta a lo largo de días suele revelar un servicio con fugas mucho antes de que cause problemas.
Uso de los datos
Una vez que los números fluyen, elige cuántas herramientas quieres:
- Vistazo rápido: el explorador de topics de ConnectMQ o cualquier cliente MQTT de escritorio.
- Pantalla de pared: el dashboard Raspberry Pi en modo kiosko puede mostrar un panel por Pi.
- Histórico y gráficas: Telegraf hacia InfluxDB con Grafana encima, como en el tutorial de Raspberry Pi con Grafana e InfluxDB.
Solución de problemas
| Síntoma | Causa | Solución |
|---|---|---|
throttled es null |
Falta vcgencmd o no hay permisos |
Instala libraspberrypi-bin, añade el usuario al grupo video |
cpu_pct siempre 0.0 en la primera muestra |
Contador sin inicializar | El script lo inicializa antes del bucle |
| El estado alterna online y offline repetidamente | Dos Pi con el mismo client ID | Da a cada Pi un client ID único |
offline nunca aparece tras desconectar |
Esperando al timeout de keepalive | Espera aproximadamente 1,5 veces el keepalive |
wifi_rssi es null con Ethernet |
No hay interfaz inalámbrica | Es lo esperado; ignora o elimina el campo |
Empieza con ConnectMQ
Reúne la salud de todas tus Pi en un solo lugar: regístrate en app.connectmq.com, crea un broker Serverless gratuito sin tarjeta de crédito y coloca las credenciales en /etc/pistats.env. Para la serie completa de proyectos con Pi, empieza por el tutorial de MQTT con Raspberry Pi.
Preguntas frecuentes
¿Con qué frecuencia debe publicar una Raspberry Pi sus estadísticas del sistema?
Cada 30 a 60 segundos es más que suficiente para monitorizar la salud. Intervalos más cortos añaden tráfico sin revelar mucho más, ya que la temperatura y el uso de disco cambian despacio.
¿Publicar estadísticas ralentiza la Raspberry Pi?
No de forma apreciable. Leer unos pocos archivos y enviar un pequeño mensaje JSON cada 30 segundos es una carga insignificante incluso en una Pi Zero 2 W.
¿Cómo sé cuándo una Pi se queda offline?
Suscríbete al topic de estado retained. El script define un last will offline, así que el broker lo publica automáticamente cuando la Pi se desconecta de forma inesperada.
¿Puedo añadir la temperatura de la GPU o métricas personalizadas?
Sí, añade campos al diccionario de collect(). En los modelos actuales de Pi, la CPU y la GPU comparten el mismo chip, así que el valor de la zona térmica ya refleja ambas.



