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:
- Se conecta a un broker MQTT y se reconecta automáticamente tras caídas del Wi-Fi o reinicios.
- Publica cada diez segundos un mensaje de telemetría JSON con la temperatura de la CPU, la carga media y, opcionalmente, un sensor ambiental.
- Se suscribe a un topic de comandos y conmuta un LED (o un relé) en el GPIO17.
- 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_subymosquitto_pubpara las pruebas (instalamosquitto-clientsen 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_asyncmásloop_startejecuta el bucle de red en un hilo en segundo plano y permite que paho reintente también la primera conexión. Unconnect()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 publicaofflinepor ti cuando expira el keepalive. La ruta de apagado limpio publicaofflineexplí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_connectrestaura la suscripción tras cada reconexión. Suscribirse una sola vez después deconnect()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:
- Client ID único por conexión. Los duplicados causan bucles de reconexión interminables.
- Suscripciones dentro de
on_connect. Si no, los comandos dejan de llegar tras la primera reconexión. - Last will y estado retenido. Los dashboards deben poder distinguir "en silencio" de "muerto".
- 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.
- systemd con
Restart=on-failurey logs en journald. - 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.
- 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.
- 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:
- Convierte la Pi en un traductor de protocolos con la guía del gateway IoT con Raspberry Pi, incluidas fuentes Modbus y BLE.
- ¿Necesitas algo más pequeño y barato para proyectos con batería? Prueba MQTT en la Raspberry Pi Pico W con MicroPython.
- Guarda y representa tus lecturas con Grafana e InfluxDB en una Raspberry Pi.
- Publica los datos de salud de toda una flota con estadísticas del sistema de la Raspberry Pi por MQTT.
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.



