Raspberry Pi

Control de relés por MQTT con Raspberry Pi: conmuta una placa multicanal desde cualquier lugar

Conecta una placa de 4 relés a una Raspberry Pi, controla cada canal desde topics MQTT con Python y gpiozero, y usa retained messages para que cada relé vuelva al estado correcto tras un reinicio o un corte de luz.

Raspberry Pi conectada a una placa de cuatro relés con LED indicadores, controlada por mensajes MQTT desde la nube en un proyecto de relé MQTT con Raspberry Pi
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

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:

  1. Cuando tu dashboard publica ON en relays/1/set con el flag retain, el broker lo conserva. Cuando la Pi se reinicia y se suscribe a relays/+/set, recibe el estado deseado de cada canal y lo restaura.
  2. Cuando la Pi publica relays/1/state con 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

  1. 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.
  2. 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-VCC desde una fuente de 5 V independiente.
  3. 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.
  4. 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.
  5. 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.

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.