Un controlador de relé MQTT con Raspberry Pi te permite conmutar lámparas, bombas, ventiladores o contactores desde un móvil, un dashboard o un script de automatización, sin abrir puertos en tu router. En este tutorial cablearás una placa de relés multicanal, escribirás un servicio en Python que asigna topics MQTT a los canales de relé y usarás retained messages para que cada relé vuelva al estado correcto tras un reinicio.
Qué necesitas
| Elemento | Notas |
|---|---|
| Raspberry Pi (3B+, 4, 5 o Zero 2 W) | Raspberry Pi OS Bookworm, conectada a la red |
| Placa de relés de 2, 4 u 8 canales | Bobina de 5 V con optoacopladores, entradas compatibles con 3,3 V |
| Cables dupont hembra-hembra | Uno por canal más alimentación y tierra |
Python 3 con gpiozero y paho-mqtt 2.x |
Instalados en un entorno virtual |
| Un broker MQTT | broker.connectmq.com para pruebas, credenciales privadas para producción |
Una advertencia de seguridad antes que nada: las placas de relés conmutan lo que les conectes. Si controlas tensión de red, el cableado debe hacerlo una persona cualificada dentro de una caja adecuada, con el relé dimensionado para la carga. Para aprender, conmuta una tira LED de 12 V o un pequeño ventilador de continua.
Cableado de la placa de relés
La mayoría de los módulos de relés para aficionados tienen un conector rotulado VCC, GND e IN1 a IN4. Conéctalos así:
| Pin del relé | Pin de la Raspberry Pi |
|---|---|
| VCC | 5 V (pin físico 2) |
| GND | GND (pin físico 6) |
| IN1 | GPIO17 (pin físico 11) |
| IN2 | GPIO27 (pin físico 13) |
| IN3 | GPIO22 (pin físico 15) |
| IN4 | GPIO23 (pin físico 16) |
Muchas de estas placas son activas a nivel bajo: llevar una entrada a tierra energiza la bobina. Por eso el código de abajo usa active_high=False. Si tu placa se activa cuando esperas que esté apagada, invierte ese flag.
Las placas con un jumper JD-VCC permiten alimentar las bobinas desde una fuente de 5 V independiente para un aislamiento total. Quita el jumper, alimenta JD-VCC desde la fuente externa, mantén VCC en los 3,3 V o 5 V de la Pi según indique la documentación de la placa y comparte la tierra solo si el manual lo exige.
Hay otro detalle de hardware importante para la fiabilidad. Antes de que arranque tu script, los pines GPIO son entradas con resistencias de pull por defecto, así que una placa activa a nivel bajo puede conmutar brevemente durante el arranque. Prueba qué hacen tus relés durante un ciclo de encendido. Si un pulso espurio fuera perjudicial, elige pines cuyo pull por defecto coincida con el nivel de "apagado" o añade resistencias de pull externas.
Diseño del contrato de topics
Una estructura de topics limpia hace que el controlador de relés sea fácil de usar desde cualquier cliente, ya sea Node-RED, Home Assistant o un script:
| Topic | Dirección | Payload | Retained |
|---|---|---|---|
connectmq/demo/a91c4e/relays/<n>/set |
Del controlador a la Pi | ON, OFF (o TOGGLE, no retained) |
Sí para ON/OFF |
connectmq/demo/a91c4e/relays/<n>/state |
De la Pi a todos | ON u OFF |
Sí |
connectmq/demo/a91c4e/relays/status |
La Pi (y su last will) | online u offline |
Sí |
Sustituye a91c4e por tu propia cadena aleatoria. En un broker público compartido todos los topics son legibles por cualquiera, así que un prefijo único mantiene separado tu tráfico de pruebas. Si necesitas repasar por qué separar los topics set y state es una buena práctica, la guía de GPIO de Raspberry Pi por MQTT explica en detalle el patrón de comando y estado.
Cómo sobreviven los estados retained a un reinicio
Los retained messages son la clave de un controlador de relés que se comporta bien tras un corte de luz. El broker guarda el último mensaje retained de cada topic y lo entrega de inmediato a cualquier nuevo suscriptor. Eso te da dos comportamientos útiles:
- Cuando tu dashboard publica
ONenrelays/1/setcon el flag retain, el broker lo conserva. Cuando la Pi se reinicia y se suscribe arelays/+/set, recibe el estado deseado de cada canal y lo restaura. - Cuando la Pi publica
relays/1/statecon retain, cualquier dashboard que se abra después ve el estado real de inmediato en lugar de esperar al siguiente cambio.
Hay una trampa: nunca hagas retained un TOGGLE. Un toggle retained se repetiría en cada reconexión y conmutaría el relé cada vez. Por eso el script de abajo ignora los toggles retained. Para profundizar en el mecanismo, lee nuestra explicación sobre los retained messages de MQTT.
Script de relé MQTT con Raspberry Pi en Python
Crea una carpeta de proyecto y un entorno virtual:
sudo apt update
sudo apt install -y python3-venv python3-lgpio
mkdir -p ~/relay-mqtt && cd ~/relay-mqtt
python3 -m venv --system-site-packages .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0" gpiozero
El flag --system-site-packages permite que el entorno virtual use el backend lgpio que gpiozero necesita en la Raspberry Pi 5 y que también funciona en modelos anteriores. Guarda lo siguiente como relay_mqtt.py:
#!/usr/bin/env python3
"""Control a multi-channel relay board from MQTT topics."""
import os
import signal
import sys
import uuid
import paho.mqtt.client as mqtt
from gpiozero import OutputDevice
BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"rpi-relay-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/a91c4e/relays")
RELAY_PINS = {1: 17, 2: 27, 3: 22, 4: 23}
ACTIVE_HIGH = False # la mayoría de las placas optoaisladas son activas a nivel bajo
relays = {
n: OutputDevice(pin, active_high=ACTIVE_HIGH, initial_value=False)
for n, pin in RELAY_PINS.items()
}
def publish_state(client, n):
state = "ON" if relays[n].value else "OFF"
client.publish(f"{PREFIX}/{n}/state", state, qos=1, retain=True)
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connection refused: {reason_code}")
return
print(f"Connected as {CLIENT_ID}")
client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)
client.subscribe(f"{PREFIX}/+/set", qos=1)
for n in relays:
publish_state(client, n)
def on_message(client, userdata, msg):
try:
n = int(msg.topic.split("/")[-2])
except ValueError:
return
if n not in relays:
print(f"Ignoring unknown channel {n}")
return
command = msg.payload.decode(errors="ignore").strip().upper()
if command in ("ON", "1", "TRUE"):
relays[n].on()
elif command in ("OFF", "0", "FALSE"):
relays[n].off()
elif command == "TOGGLE":
if msg.retain:
print(f"Ignoring retained TOGGLE on channel {n}")
return
relays[n].toggle()
else:
print(f"Unknown command {command!r} for channel {n}")
return
print(f"Relay {n} -> {'ON' if relays[n].value else 'OFF'}")
publish_state(client, n)
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)
client.on_connect = on_connect
client.on_message = on_message
client.on_disconnect = on_disconnect
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
if USERNAME:
client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
client.tls_set()
def shutdown(signum, frame):
for relay in relays.values():
relay.off()
client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.disconnect()
sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
client.connect(BROKER, PORT, keepalive=30)
client.loop_forever(retry_first_connection=True)
Ejecútalo con python relay_mqtt.py. El script apaga todos los relés en un cierre limpio; si prefieres mantener las cargas funcionando cuando se detiene el servicio, elimina el bucle de shutdown.
Pruebas desde tu portátil
Abre una segunda terminal en cualquier equipo con los clientes de Mosquitto instalados y observa los topics de estado:
mosquitto_sub -h broker.connectmq.com -p 1883 -i laptop-a91c4e \
-t 'connectmq/demo/a91c4e/relays/#' -v
Después enciende el canal 1 con un comando retained y conmuta el canal 2:
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-a91c4e \
-t 'connectmq/demo/a91c4e/relays/1/set' -m 'ON' -q 1 -r
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-a91c4e \
-t 'connectmq/demo/a91c4e/relays/2/set' -m 'TOGGLE' -q 1
Deberías oír los clics y ver aparecer relays/1/state ON. Ahora reinicia la Pi. Cuando el script se reconecte, el canal 1 vuelve a ON porque el comando retained sigue en el broker, mientras que el canal 2 arranca en OFF porque los toggles nunca son retained. Para borrar un comando retained, publica un mensaje retained vacío: mosquitto_pub ... -t '.../relays/1/set' -r -n.
Ejecútalo como servicio
Un controlador de relés debe arrancar al encender y reiniciarse tras un fallo. Crea /etc/systemd/system/relay-mqtt.service:
[Unit]
Description=Relay MQTT controller
After=network-online.target
Wants=network-online.target
[Service]
User=pi
WorkingDirectory=/home/pi/relay-mqtt
Environment=MQTT_HOST=broker.connectmq.com
Environment=MQTT_PORT=8883
Environment=MQTT_USERNAME=YOUR_USERNAME
Environment=MQTT_PASSWORD=YOUR_PASSWORD
Environment=MQTT_CLIENT_ID=YOUR_CLIENT_ID
Environment=MQTT_PREFIX=home/garage/relays
ExecStart=/home/pi/relay-mqtt/.venv/bin/python relay_mqtt.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Actívalo con sudo systemctl daemon-reload && sudo systemctl enable --now relay-mqtt. La explicación completa de los archivos de servicio, los logs y los archivos de entorno está en ejecutar un script MQTT de Python al arrancar. Ajusta el nombre de usuario si el tuyo no es pi.
Del servidor de pruebas a producción
El servidor público es perfecto para la primera hora, pero cualquiera que adivine tu topic podría conmutar tus relés. Para todo lo que esté conectado a equipos reales, usa un broker privado con autenticación y TLS. Con un broker MQTT gestionado como ConnectMQ, creas un broker en el portal, copias el nombre de usuario, la contraseña y el client ID generados en el archivo de servicio de arriba y configuras el puerto 8883. El client ID está vinculado a las credenciales emitidas, así que otro dispositivo no puede suplantar a tu controlador. El script no cambia en absoluto.
El portal de ConnectMQ también ayuda durante la puesta en marcha: su cliente MQTT en el navegador puede publicar ON y OFF en cada canal, y el explorador de topics en vivo muestra de un vistazo los estados retained y los valores de estado de conexión. Si quieres una alternativa de hardware a la Pi para el mismo trabajo, compárala con el montaje de relé con ESP32 por MQTT.
Solución de problemas
- Los relés se activan al arrancar y se apagan cuando se ordena encenderlos: tu placa es activa a nivel alto; configura
ACTIVE_HIGH = True. - Los LED de la placa se encienden pero el relé no hace clic: la alimentación de la bobina es demasiado débil; alimenta
JD-VCCdesde una fuente de 5 V independiente. Connection refused: Not authorized: revisa el nombre de usuario, la contraseña y el client ID, o elimínalos si usas el broker público anónimo.- El relé restaura un estado antiguo tras reiniciar: hay un comando retained obsoleto en el broker; bórralo con una publicación retained vacía.
- Errores
GPIO busy: otro script o servicio está usando los mismos pines; detenlo antes de iniciar este.
Empieza con ConnectMQ
¿Listo para controlar equipos reales? Regístrate en el plan gratuito Serverless, sin tarjeta de crédito, crea un broker y apunta tu servicio de relés hacia él por TLS. Tendrás MQTT 3.1.1 y 5.0, retained messages y un portal para observar cada evento de conmutación. ¿Eres nuevo en estos conceptos? El tutorial de MQTT con Raspberry Pi cubre los fundamentos, y nuestro resumen sobre el broker MQTT en la nube explica cuándo un servicio alojado es mejor que ejecutar Mosquitto en la Pi.
Preguntas frecuentes
¿Por qué mi relé cambia de estado después de reiniciar la Raspberry Pi?
Está reproduciendo un comando retained desde el broker, que normalmente es lo que quieres. Si el estado es incorrecto, publica el valor correcto con retain o borra el topic con un mensaje retained vacío.
¿Puedo usar QoS 1 para los comandos de relé?
Sí, y deberías. QoS 1 garantiza que el comando llegue al menos una vez, y como ON y OFF son idempotentes, una entrega duplicada no causa ningún daño.
¿Cuántos relés puede controlar una Raspberry Pi?
Tantos como pines GPIO libres tengas, normalmente hasta ocho en una placa para aficionados. Para más canales, usa un expansor GPIO I2C como el MCP23017 y mantén la misma estructura de topics.
¿Es seguro controlar dispositivos de red eléctrica de esta forma?
Solo con relés dimensionados para la carga, una caja adecuada y un cableado conforme a la normativa eléctrica local. Usa siempre conexiones TLS autenticadas para cualquier cosa que conmute equipos reales.



