Raspberry Pi

Tutorial Raspberry Pi MQTT: publica, suscríbete y automatiza con Python

Un tutorial completo de Raspberry Pi y MQTT: configura un entorno virtual de Python, publica lecturas de la CPU y de sensores, enciende un LED por MQTT, ejecuta el script como servicio y pasa a TLS con tus propias credenciales.

Placa Raspberry Pi conectada a un LED y a un sensor que publica datos en un broker en la nube en un tutorial de Raspberry Pi MQTT
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

Este tutorial de Raspberry Pi MQTT te lleva de una instalación recién hecha de Raspberry Pi OS a un programa en Python que publica lecturas de sensores, escucha comandos y conmuta un pin GPIO desde cualquier lugar. Lo probarás todo contra el servidor MQTT público gratuito broker.connectmq.com y después pasarás el mismo código a un broker privado protegido con TLS con unas pocas variables de entorno. Por el camino aprenderás los patrones que mantienen fiable un dispositivo IoT basado en una Pi durante meses, no minutos.

Qué vas a construir

Al final de esta guía, tu Raspberry Pi ejecutará un único servicio en Python que hace cuatro cosas:

  1. Se conecta a un broker MQTT y se reconecta automáticamente tras caídas del Wi-Fi o reinicios.
  2. Publica cada diez segundos un mensaje de telemetría JSON con la temperatura de la CPU, la carga media y, opcionalmente, un sensor ambiental.
  3. Se suscribe a un topic de comandos y conmuta un LED (o un relé) en el GPIO17.
  4. Publica un estado online/offline retenido, usando un last will de MQTT para que los demás clientes sepan cuándo desaparece la Pi.

La estructura de topics mantiene todo bajo un único prefijo, algo importante en un broker público compartido:

Topic Dirección Payload de ejemplo Retenido
connectmq/demo/<deviceId>/telemetry La Pi publica {"cpu_temp":48.3,"load":0.21} No
connectmq/demo/<deviceId>/led/set La Pi se suscribe ON, OFF, TOGGLE No
connectmq/demo/<deviceId>/led/state La Pi publica ON u OFF Sí
connectmq/demo/<deviceId>/status La Pi publica + last will online u offline Sí

Si términos como topic, retained message o last will te resultan nuevos, echa un vistazo a qué hace un broker MQTT antes de empezar. La versión corta: el broker es una oficina de correos, tu Pi y tu portátil son clientes y los topics son las direcciones.

Requisitos previos

Necesitas muy poco hardware para este tutorial de Raspberry Pi MQTT:

  • Cualquier Raspberry Pi con red: Pi 3, Pi 4, Pi 5 o Zero 2 W. El código es idéntico en todas.
  • Raspberry Pi OS Bookworm (la versión Lite sirve), con SSH activado y acceso a la red.
  • Un LED con una resistencia de 330 ohmios, o un módulo de relé, en el GPIO17 (pin físico 11) y GND (pin 9). Sin él, el código sigue funcionando y simplemente registra los comandos.
  • Opcional: un sensor DHT22 o DS18B20. Empezamos con la temperatura de la CPU integrada, así que no hace falta nada más.
  • Una segunda máquina con mosquitto_sub y mosquitto_pub para las pruebas (instala mosquitto-clients en Linux o usa Homebrew en macOS).

¿Comparando placas antes de comprar? Nuestro análisis Raspberry Pi vs ESP32 explica cuándo un ordenador Linux completo supera a un microcontrolador y cuándo es excesivo.

Paso 1: prepara Python en la Raspberry Pi

Bookworm protege el Python del sistema, así que pip install fuera de un entorno virtual falla con el error externally-managed-environment. Es algo bueno: un entorno virtual mantiene aisladas y reproducibles las dependencias de tu proyecto. Crea uno en una carpeta de proyecto:

sudo apt update
sudo apt install -y python3-venv python3-lgpio mosquitto-clients
mkdir -p ~/pi-mqtt && cd ~/pi-mqtt
python3 -m venv --system-site-packages .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0" gpiozero

La opción --system-site-packages permite que el entorno virtual vea el paquete lgpio instalado con apt, que gpiozero usa como backend de pines en el Raspberry Pi OS actual (y que es obligatorio en la Pi 5, donde la antigua librería RPi.GPIO no funciona). Consulta la documentación oficial de GPIO de Raspberry Pi para ver el pinout de tu modelo.

Comprueba la versión del cliente instalada. Esta guía usa la API de callbacks de paho-mqtt 2.x, que difiere de la de los tutoriales más antiguos que puedes encontrar en internet:

python -c "import paho.mqtt; print(paho.mqtt.__version__)"

Paso 2: tu primer publish y subscribe

Antes de escribir el programa completo, comprueba que la ruta de red funciona. Elige un ID de dispositivo aleatorio para que tus topics no choquen con los de nadie en el broker público. En un terminal (en tu portátil o en la Pi), suscríbete:

mosquitto_sub -h broker.connectmq.com -p 1883 -i sub-laptop-7f3a -v -t "connectmq/demo/pi-7f3a/#"

En un segundo terminal, publica un mensaje de prueba:

mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-pi-7f3a -t "connectmq/demo/pi-7f3a/hello" -m "hello from the pi"

El suscriptor imprime al instante el topic y el mensaje. Cada cliente necesita un client ID único y no vacío; si dos clientes comparten ID, el broker desconecta el más antiguo y verás un confuso bucle de conexión y desconexión. Cualquiera que adivine tu topic puede leer lo que publiques en el servidor público, pensado solo para pruebas y prototipos, así que nunca envíes contraseñas, datos personales, datos de producción ni nada que no publicarías abiertamente.

Paso 3: el cliente MQTT completo en Python

Guarda lo siguiente como ~/pi-mqtt/pi_mqtt.py. Lee todos los ajustes de variables de entorno, así que el mismo archivo sirve hoy para el broker público y más adelante para un broker privado.

#!/usr/bin/env python3
"""Raspberry Pi MQTT client: telemetry, GPIO control and online status."""
import json
import os
import signal
import ssl
import threading
import time
import uuid

import paho.mqtt.client as mqtt

try:
    from gpiozero import LED
    led = LED(int(os.getenv("LED_PIN", "17")))
except Exception as exc:  # sin GPIO (p. ej. pruebas en un portátil)
    print(f"GPIO unavailable, running without LED: {exc}")
    led = None

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USE_TLS = os.getenv("MQTT_TLS", "0") == "1"
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
DEVICE_ID = os.getenv("DEVICE_ID", f"pi-{uuid.uuid4().hex[:6]}")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", DEVICE_ID)
PREFIX = os.getenv("TOPIC_PREFIX", f"connectmq/demo/{DEVICE_ID}")
INTERVAL = float(os.getenv("PUBLISH_INTERVAL", "10"))

T_TELEMETRY = f"{PREFIX}/telemetry"
T_LED_SET = f"{PREFIX}/led/set"
T_LED_STATE = f"{PREFIX}/led/state"
T_STATUS = f"{PREFIX}/status"

stop_event = threading.Event()


def read_cpu_temp() -> float:
    with open("/sys/class/thermal/thermal_zone0/temp") as f:
        return round(int(f.read().strip()) / 1000.0, 1)


def publish_led_state(client: mqtt.Client) -> None:
    state = "ON" if (led and led.is_lit) else "OFF"
    client.publish(T_LED_STATE, state, qos=1, retain=True)


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(T_STATUS, "online", qos=1, retain=True)
    client.subscribe(T_LED_SET, qos=1)
    publish_led_state(client)


def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
    print(f"Disconnected ({reason_code}), paho will retry automatically")


def on_message(client, userdata, msg):
    command = msg.payload.decode("utf-8", errors="ignore").strip().upper()
    print(f"Command on {msg.topic}: {command}")
    if led is None:
        return
    if command == "ON":
        led.on()
    elif command == "OFF":
        led.off()
    elif command == "TOGGLE":
        led.toggle()
    else:
        print("Unknown command, ignoring")
        return
    publish_led_state(client)


def build_client() -> mqtt.Client:
    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
    if USERNAME:
        client.username_pw_set(USERNAME, PASSWORD)
    if USE_TLS:
        client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
    client.will_set(T_STATUS, "offline", qos=1, retain=True)
    client.reconnect_delay_set(min_delay=1, max_delay=60)
    client.on_connect = on_connect
    client.on_disconnect = on_disconnect
    client.on_message = on_message
    return client


def main() -> None:
    client = build_client()
    client.connect_async(HOST, PORT, keepalive=30)
    client.loop_start()

    signal.signal(signal.SIGTERM, lambda *_: stop_event.set())
    signal.signal(signal.SIGINT, lambda *_: stop_event.set())

    while not stop_event.is_set():
        if client.is_connected():
            payload = {
                "cpu_temp": read_cpu_temp(),
                "load": round(os.getloadavg()[0], 2),
                "ts": int(time.time()),
            }
            client.publish(T_TELEMETRY, json.dumps(payload), qos=0)
        stop_event.wait(INTERVAL)

    client.publish(T_STATUS, "offline", qos=1, retain=True).wait_for_publish(2)
    client.loop_stop()
    client.disconnect()


if __name__ == "__main__":
    main()

Ejecútalo con un ID de dispositivo fijo para saber qué topics observar:

cd ~/pi-mqtt && source .venv/bin/activate
DEVICE_ID=pi-7f3a python pi_mqtt.py

Cómo funciona el código

Merece la pena entender algunas decisiones de diseño, porque es ahí donde fallan la mayoría de los proyectos con Pi:

  • connect_async más loop_start ejecuta el bucle de red en un hilo en segundo plano y permite que paho reintente también la primera conexión. Un connect() simple lanza una excepción y mata el script si la red aún no está disponible, algo habitual justo después del arranque.
  • El last will (will_set) lo guarda el broker en el momento de la conexión. Si la Pi pierde la alimentación, el broker publica offline por ti cuando expira el keepalive. La ruta de apagado limpio publica offline explícitamente.
  • Los topics de estado retenidos hacen que un dashboard que se conecta más tarde vea de inmediato el estado actual del LED y el estado online, sin esperar al siguiente cambio.
  • Suscribirse dentro de on_connect restaura la suscripción tras cada reconexión. Suscribirse una sola vez después de connect() es un error clásico que detiene los comandos en silencio tras el primer corte de red.
  • QoS 1 para comandos y estado, QoS 0 para la telemetría es un valor por defecto sensato. Perder una lectura periódica es inofensivo; perder un comando OFF, no. Nuestra guía de niveles de QoS en MQTT explica en profundidad las ventajas e inconvenientes.

Paso 4: controla GPIO por MQTT

Con el script en marcha, envía comandos desde tu portátil:

mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-laptop-7f3a -q 1 -t "connectmq/demo/pi-7f3a/led/set" -m "ON"
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-laptop-7f3a -q 1 -t "connectmq/demo/pi-7f3a/led/set" -m "TOGGLE"

El LED cambia y la ventana del suscriptor muestra el nuevo estado retenido en led/state. El mismo patrón sirve para accionar un relé de una bomba, un ventilador o una cerradura eléctrica; basta con cambiar LED por gpiozero.OutputDevice y plantearse invertir la lógica en placas de relés activas a nivel bajo. Para pulsadores, regulación PWM y varios pines, continúa con la guía específica de GPIO de Raspberry Pi por MQTT, y para hardware que conmuta tensión de red lee control de relés con Raspberry Pi y MQTT por la seguridad del cableado.

Una regla práctica para los actuadores: la verdad la tiene el dispositivo, no el dashboard. Publica siempre el estado que el hardware ha alcanzado realmente, y haz que las interfaces de usuario muestren ese topic de estado en lugar de suponer que el comando funcionó.

Paso 5: añade un sensor real

La temperatura de la CPU demuestra que la canalización funciona, pero la mayoría de los proyectos miden la estancia. Sustituye read_cpu_temp() por una función que lea el sensor y añade los valores al diccionario del payload. La tabla siguiente resume las opciones habituales y adónde ir después:

Sensor Interfaz Qué mide Guía detallada
DHT22 / AM2302 Un GPIO, sensible a la temporización Temperatura, humedad DHT22 con MQTT
DS18B20 1-Wire Temperatura, sondas impermeables DS18B20 con MQTT
Sense HAT HAT I2C Temperatura, humedad, presión, IMU Sense HAT a MQTT
HC-SR04 Dos GPIO Distancia, nivel de depósito Sensor de distancia a MQTT

Mantén las lecturas de sensores fuera de los callbacks de MQTT. Los callbacks se ejecutan en el hilo de red, y una lectura lenta ahí retrasa la gestión del keepalive y las confirmaciones. El bucle principal del script anterior es el lugar adecuado para todo lo que bloquee.

Usa una estructura JSON coherente en todos los dispositivos: valores numéricos, unidades del SI en el nombre de la clave o en un campo unit aparte, y una marca de tiempo Unix. Todos los consumidores que añadas después, de Grafana a Node-RED, te lo agradecerán.

Paso 6: ejecuta el script MQTT al arrancar con systemd

Un script lanzado desde una sesión SSH muere cuando cierras la sesión. Conviértelo en un servicio de systemd para que arranque con el sistema y se reinicie si falla. Crea /etc/systemd/system/pi-mqtt.service:

[Unit]
Description=Raspberry Pi MQTT client
After=network-online.target
Wants=network-online.target

[Service]
User=pi
WorkingDirectory=/home/pi/pi-mqtt
EnvironmentFile=/home/pi/pi-mqtt/.env
ExecStart=/home/pi/pi-mqtt/.venv/bin/python /home/pi/pi-mqtt/pi_mqtt.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Sustituye pi por tu nombre de usuario, añade el usuario al grupo gpio si hace falta (sudo usermod -aG gpio pi) y crea el archivo .env:

cat > ~/pi-mqtt/.env <<'EOF'
DEVICE_ID=pi-7f3a
PUBLISH_INTERVAL=10
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now pi-mqtt
journalctl -u pi-mqtt -f

Como el script gestiona SIGTERM, systemctl stop pi-mqtt publica un estado offline limpio antes de salir. Para la rotación de logs, los watchdogs y alternativas como @reboot en cron, consulta cómo ejecutar un script MQTT de Python al arrancar.

Paso 7: pasa del broker público a un broker privado

El broker público es perfecto para aprender, pero es compartido, anónimo y con límites de frecuencia (por ejemplo, 10 mensajes por segundo por cliente y 64 KB por mensaje). Para todo aquello de lo que dependas, querrás tus propias credenciales, cifrado y aislamiento. Tienes dos opciones:

Opción Adecuada para Te encargas de
Mosquitto en la propia Pi LAN sin internet, automatización solo local Actualizaciones, certificados TLS, copias de seguridad, acceso remoto
Un broker MQTT gestionado Dispositivos en muchos lugares, dashboards remotos, equipos Solo el código de tus clientes

Ejecutar Mosquitto en local se explica en instalar Mosquitto en una Raspberry Pi. Si eliges un broker MQTT gestionado, ConnectMQ te da un broker privado en un par de minutos: regístrate, elige un plan, crea un broker y el portal te mostrará un nombre de usuario, una contraseña y un client ID generados. Después edita solo el archivo .env:

cat > ~/pi-mqtt/.env <<'EOF'
MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_TLS=1
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
DEVICE_ID=pi-7f3a
TOPIC_PREFIX=home/livingroom/pi-7f3a
EOF
sudo systemctl restart pi-mqtt

El puerto 8883 usa TLS con un certificado de Let's Encrypt de confianza pública, así que tls_set() lo valida con el paquete de CA que incluye Raspberry Pi OS; no necesitas copiar archivos de certificado a la Pi. En ConnectMQ, los client IDs están vinculados a las credenciales emitidas, así que usa el client ID del portal en lugar de uno aleatorio. Prueba desde tu portátil con las mismas credenciales:

mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID-cli -v -t "home/#"

Si el portal emitió un único client ID, crea un segundo juego de credenciales para el portátil en lugar de reutilizar el de la Pi; dos conexiones con el mismo ID se expulsan mutuamente. El portal incluye además un cliente MQTT en el navegador y un explorador de topics en tiempo real, que a menudo es la forma más rápida de ver llegar los mensajes de tu Pi sin instalar nada.

Checklist del tutorial Raspberry Pi MQTT para dispositivos fiables

La mayoría de los problemas de "ayer funcionaba" salen de la misma lista corta. Antes de dejar una Pi en un armario, repasa esto:

  1. Client ID único por conexión. Los duplicados causan bucles de reconexión interminables.
  2. Suscripciones dentro de on_connect. Si no, los comandos dejan de llegar tras la primera reconexión.
  3. Last will y estado retenido. Los dashboards deben poder distinguir "en silencio" de "muerto".
  4. TLS en el puerto 8883 con credenciales para todo lo que esté fuera de un laboratorio. Consulta la lista completa de buenas prácticas de seguridad en MQTT.
  5. systemd con Restart=on-failure y logs en journald.
  6. Almacena lecturas en buffer sin conexión si los huecos importan. Paho encola los mensajes QoS 1 mientras está desconectado, pero solo en memoria; buffer de mensajes MQTT sin conexión en Python muestra un enfoque con SQLite que sobrevive a los reinicios.
  7. Una buena fuente de alimentación y una tarjeta SD de calidad, o arranque desde SSD. La subtensión provoca más desconexiones aleatorias que cualquier error de software.
  8. Bastiona el sistema operativo: solo claves SSH, actualizaciones de seguridad automáticas, ninguna contraseña por defecto. Proteger un dispositivo IoT con Raspberry Pi lo explica paso a paso.

Resolución de problemas

Connect failed: Not authorized significa que el nombre de usuario, la contraseña o el client ID no coinciden con lo que emitió el broker. Vuelve a copiarlos del portal y revisa si hay espacios al final en .env.

ConnectionRefusedError o tiempos de espera agotados en el 8883 suelen significar que un firewall o una red de invitados bloquea los puertos salientes. Prueba con nc -vz broker.connectmq.com 8883. Algunas redes corporativas solo permiten el 443; en ese caso MQTT sobre WebSocket seguro en el puerto 8084 también puede estar bloqueado, así que pruébalo antes de comprometerte.

ssl.SSLCertVerificationError casi siempre significa que el reloj de la Pi está mal. TLS necesita una fecha correcta; revisa timedatectl y asegúrate de que NTP está activado.

El LED no reacciona pero los mensajes llegan: apunta a los permisos de GPIO o al cableado. Ejecuta pinctrl get 17 en un Raspberry Pi OS reciente para ver el estado del pin y confirma que el usuario pertenece al grupo gpio.

Bucle de reconexión cada pocos segundos: es un client ID duplicado o una segunda copia del script todavía en ejecución. Compruébalo con ps aux | grep pi_mqtt.

Próximos pasos

Este tutorial de Raspberry Pi MQTT te da una base sólida, y el resto de nuestra serie sobre Raspberry Pi se construye exactamente sobre este script:

Empieza con ConnectMQ

Cuando tu proyecto con Pi se quede grande para el servidor público de pruebas, crea un broker privado en ConnectMQ. El plan Serverless gratuito no requiere tarjeta de crédito, admite hasta 25 conexiones y te ofrece de serie TLS en el puerto 8883, MQTT 5 y acceso por WebSocket, más que suficiente para un laboratorio casero o un primer piloto. Las flotas más grandes pueden pasar a los planes Serverless de pago o Dedicated sin cambiar una sola línea del código de los clientes. Crea tu broker gratuito en app.connectmq.com y apunta a él tu archivo .env. Si antes quieres una visión general de las opciones de alojamiento, lee nuestra guía para elegir un broker MQTT en la nube.

Preguntas frecuentes

¿Qué librería MQTT debo usar en una Raspberry Pi?

Para Python, paho-mqtt 2.x es la más utilizada y la que usa esta guía. Quienes usen Node.js pueden recurrir a MQTT.js, y quienes prefieran el low-code pueden ejecutar Node-RED en la Pi con la misma configuración de broker.

¿Puede una Raspberry Pi ser a la vez broker y cliente MQTT?

Sí. Puedes ejecutar Mosquitto en la Pi y conectar los scripts locales a localhost, pero entonces te encargas tú de la seguridad, las actualizaciones y el acceso remoto. Muchos proyectos mantienen la Pi como cliente y usan un broker MQTT gestionado para que dispositivos y dashboards en distintas ubicaciones puedan comunicarse.

¿Por qué mi Pi se desconecta y se reconecta continuamente?

Las causas más habituales son un client ID duplicado, una segunda copia del script en ejecución o una fuente de alimentación inestable. Comprueba que cada conexión usa un ID único y busca avisos de subtensión con dmesg.

¿Es seguro el broker público para mi proyecto con Raspberry Pi?

Es seguro para aprender y para demos, pero cualquier cliente puede leer los topics públicos, así que nunca envíes datos personales ni sensibles. Usa un broker privado con TLS y credenciales para los despliegues reales.

¿Este código funciona en la Raspberry Pi 5?

Sí. El script usa gpiozero con el backend lgpio, que es compatible con la Pi 5. Las librerías que escriben en los antiguos registros de memoria de GPIO, como RPi.GPIO, no funcionan en la Pi 5.

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.