Raspberry Pi

Publica las estadísticas del sistema de la Raspberry Pi por MQTT para monitorizar la salud de tu flota

Un pequeño servicio en Python que publica en MQTT la temperatura de la CPU, la carga, la memoria, el uso de disco y los flags de subtensión de cada Raspberry Pi, con un last will para saber cuándo una Pi se queda offline.

Varias placas Raspberry Pi enviando la temperatura de la CPU y la memoria como estadísticas del sistema Raspberry Pi por MQTT a un broker central
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

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_seen distinto 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_s cae 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:

  1. Vistazo rápido: el explorador de topics de ConnectMQ o cualquier cliente MQTT de escritorio.
  2. Pantalla de pared: el dashboard Raspberry Pi en modo kiosko puede mostrar un panel por Pi.
  3. 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.

Artículos relacionados

Fiabilidad de la Raspberry Pi: cómo usar una Pi 24/7 como gateway IoT

Una guía práctica para mantener un gateway MQTT con Raspberry Pi funcionando durante meses: desgaste del almacenamiento, sistemas de archivos de solo lectura, alimentación, refrigeración, watchdogs de hardware y software y monitorización remota de la salud.

9 min de lectura

Pico W vs Pico 2 W para MQTT: rendimiento, memoria y TLS de RP2040 frente a RP2350

La Pico 2 W mantiene el formato y el chip Wi-Fi de la Pico W, pero sustituye el RP2040 por el RP2350, más rápido. Esta comparativa analiza qué significa para los dispositivos MQTT: CPU, memoria, TLS, funciones de seguridad, soporte de software y qué placa comprar para cada proyecto.

9 min de lectura

Registro de accesos Raspberry Pi RFID MQTT con el lector RC522

Conecta un lector RFID RC522 a una Raspberry Pi, lee etiquetas en Python, compáralas con una lista blanca local y publica cada intento de acceso como un evento MQTT que pueden consumir registradores, dashboards y herramientas de alertas.

8 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.