El control Raspberry Pi GPIO MQTT te permite conmutar un relé, un LED o un zumbador desde cualquier lugar y ver el estado de botones y contactos en tiempo real, sin abrir un solo puerto en tu red. En este tutorial usarás gpiozero y paho-mqtt para encender y apagar una salida con comandos MQTT, publicar los cambios de las entradas en el momento en que ocurren e informar de todo como estado retenido. Lo probarás con broker.connectmq.com y un cliente de línea de comandos, y después lo pasarás a credenciales privadas.
Cómo encajan las piezas
La Pi se conecta de salida a un broker MQTT y se suscribe a un topic de comandos. Cualquier cosa que pueda publicar por MQTT (una app móvil, Node-RED, un dashboard web, un script) envía ON u OFF a ese topic, y la Pi conmuta el pin. Cuando el pin cambia o se pulsa un botón, la Pi publica el nuevo estado en un topic de estado separado.
Esa separación entre topics de comando y de estado es la decisión de diseño más importante de este tutorial:
| Topic | Dirección | Retenido | Payload de ejemplo |
|---|---|---|---|
.../gpio/led/set |
Controlador a la Pi | No | ON |
.../gpio/led/state |
Pi a todos | Sí | ON |
.../gpio/button/state |
Pi a todos | Sí | PRESSED |
.../status |
Pi a todos | Sí (Last Will) | online / offline |
Los comandos no son retenidos, así que un "ON" obsoleto nunca vuelve a dispararse cuando la Pi se reinicia. Los estados sí son retenidos, de modo que cualquier dashboard que se conecte más tarde ve de inmediato los valores actuales. Un dashboard debe mostrar el topic de estado y no repetir lo que envió, porque el topic de estado refleja lo que realmente ocurrió en el hardware.
Cableado
Necesitas un LED con una resistencia de 330 ohmios (o un módulo de relé) y un pulsador.
- Ánodo del LED (pata larga) a través de la resistencia a GPIO 17 (pin físico 11), cátodo a tierra.
- Pulsador entre GPIO 27 (pin físico 13) y tierra. gpiozero activa la pull-up interna, así que no hace falta resistencia.
Los pines GPIO son de 3,3 V y solo pueden suministrar unos pocos miliamperios. Acciona relés, motores y cualquier cosa a tensión de red a través de una placa controladora o un módulo de relé adecuados, nunca directamente. El tutorial de relé con Raspberry Pi por MQTT explica cómo hacerlo con seguridad.
Instalar las bibliotecas
gpiozero viene preinstalado en Raspberry Pi OS y usa el backend lgpio, que funciona en todos los modelos, incluida la Raspberry Pi 5 con su chip de E/S RP1. Crea un entorno virtual que pueda ver los paquetes del sistema y añade paho-mqtt:
sudo apt install -y python3-gpiozero python3-lgpio
python3 -m venv --system-site-packages ~/gpio-mqtt/.venv
source ~/gpio-mqtt/.venv/bin/activate
pip install "paho-mqtt>=2.0"
python -c "import gpiozero, paho.mqtt; print('ok')"
Usar --system-site-packages evita compilar bibliotecas GPIO con pip, que es la fuente más común de errores de instalación en los modelos de Pi más recientes. Tu usuario debe pertenecer al grupo gpio, como ya ocurre con el usuario por defecto.
El script Raspberry Pi GPIO MQTT
import os
import signal
import uuid
import paho.mqtt.client as mqtt
from gpiozero import LED, Button
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"pi-gpio-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("TOPIC_PREFIX", "connectmq/demo/g3p6v2/pi1")
led = LED(17)
button = Button(27, bounce_time=0.05)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
if USERNAME:
client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
client.tls_set()
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(1, 30)
def publish_led_state():
client.publish(f"{PREFIX}/gpio/led/state", "ON" if led.is_lit else "OFF", qos=1, retain=True)
def publish_button_state():
state = "PRESSED" if button.is_pressed else "RELEASED"
client.publish(f"{PREFIX}/gpio/button/state", state, qos=1, retain=True)
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print("connect failed:", reason_code)
return
print("connected")
client.subscribe(f"{PREFIX}/gpio/+/set", qos=1)
client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)
publish_led_state()
publish_button_state()
def on_message(client, userdata, msg):
target = msg.topic.split("/")[-2]
command = msg.payload.decode(errors="ignore").strip().upper()
if target != "led":
return
if command == "ON":
led.on()
elif command == "OFF":
led.off()
elif command == "TOGGLE":
led.toggle()
else:
print("ignored command:", command)
return
publish_led_state()
button.when_pressed = publish_button_state
button.when_released = publish_button_state
client.on_connect = on_connect
client.on_message = on_message
client.connect_async(BROKER, PORT, keepalive=30)
client.loop_start()
try:
signal.pause()
except KeyboardInterrupt:
pass
finally:
client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
client.loop_stop()
client.disconnect()
led.off()
Merece la pena señalar algunos detalles. gpiozero ejecuta los callbacks when_pressed en su propio hilo, y publish() de paho es thread-safe, así que llamarlo desde el callback no da problemas. El bounce_time filtra el rebote mecánico del interruptor, que de otro modo produce ráfagas de eventos de pulsación y liberación. on_connect vuelve a suscribirse y republica los estados tras cada reconexión, para que el broker conserve siempre la verdad actual. Los comandos desconocidos se ignoran en lugar de intentar adivinarlos.
Probar desde otra máquina
Ejecuta el script en la Pi y, desde tu portátil, abre una terminal para observarlo todo y otra para enviar comandos:
# terminal 1: observar todos los topics de esta Pi
mosquitto_sub -h broker.connectmq.com -p 1883 -i gpio-watch-g3p6v2 -t "connectmq/demo/g3p6v2/pi1/#" -v
# terminal 2: enviar comandos
mosquitto_pub -h broker.connectmq.com -p 1883 -i gpio-cmd-g3p6v2 -t "connectmq/demo/g3p6v2/pi1/gpio/led/set" -m ON
mosquitto_pub -h broker.connectmq.com -p 1883 -i gpio-cmd-g3p6v2 -t "connectmq/demo/g3p6v2/pi1/gpio/led/set" -m TOGGLE
El LED conmuta y la terminal 1 muestra el nuevo led/state. Pulsa el botón y button/state alterna entre PRESSED y RELEASED. Detén el script con Ctrl+C y el estado cambia a offline; si en cambio desconectas la alimentación, el broker publica el Last Will cuando expira el keepalive.
El servidor MQTT público gratuito es ideal para esta prueba, pero recuerda que cualquiera puede leer y publicar en sus topics públicos. No dejes allí un relé real que se pueda controlar.
Ampliar a más pines
Para controlar varias salidas, mantén un diccionario de dispositivos con nombre y búscalos a partir del topic:
from gpiozero import OutputDevice
outputs = {"led": LED(17), "fan": OutputDevice(22), "pump": OutputDevice(23, active_high=False)}
En on_message, sustituye el LED único por outputs.get(target) y publica el estado con el nombre correspondiente. Usa nombres en lugar de números de pin en los topics: un dashboard no debería romperse cuando recableas la placa. Para entradas como sensores de temperatura, publica con un temporizador en lugar de al cambiar; el tutorial de DHT22 por MQTT muestra ese patrón.
Comportamiento a prueba de fallos para las salidas
Decide de antemano qué debe hacer una salida cuando algo va mal, porque tarde o temprano la red se caerá. Para una luz, mantener el último estado está bien. Para una bomba, un calefactor o una válvula, suele ser más seguro apagarla si no llega ningún comando ni heartbeat durante un tiempo. Un patrón sencillo es una marca de tiempo que se actualiza en on_message y una comprobación en el bucle principal que apaga la salida tras, por ejemplo, diez minutos de silencio y publica el nuevo estado cuando vuelve la conexión.
Piensa también en el arranque. gpiozero pone las salidas en apagado al crear el objeto, así que tras un reinicio todas las salidas empiezan en un estado seguro conocido hasta que llega un comando nuevo. Mantenlo así en lugar de restaurar el estado anterior desde un archivo, salvo que el proceso realmente lo requiera. Si es así, documenta el comportamiento en el dispositivo y en el dashboard para que nadie se lleve sorpresas.
Solución de problemas
GPIO busyolgpio.error: otro proceso tiene el pin. Detén los demás scripts o servicios que lo usen y comprueba que no estás ejecutando dos copias.- No module named lgpio: el venv se creó sin
--system-site-packages. Vuelve a crearlo o instalapython3-lgpio. - Los comandos no hacen nada: revisa el topic exacto y que la Pi haya impreso "connected". Suscríbete a
#bajo tu prefijo para ver qué se publica realmente. - El LED se enciende tras cada reinicio: alguien publicó un mensaje retenido en el topic
set. Bórralo publicando un mensaje retenido vacío en ese topic conmosquitto_pub -r -n. - Eventos aleatorios del botón: aumenta
bounce_timeo acorta el cableado; los cables largos sin apantallar captan ruido.
Configuración de producción
Para cualquier cosa que vaya más allá de una demo, crea un broker privado en ConnectMQ e inicia el script con MQTT_PORT=8883, MQTT_USERNAME=YOUR_USERNAME, MQTT_PASSWORD=YOUR_PASSWORD y MQTT_CLIENT_ID=YOUR_CLIENT_ID obtenidos del portal. TLS impide que los comandos se lean o se falsifiquen en tránsito, y las credenciales por dispositivo garantizan que solo tus clientes puedan publicar en tus topics. Ejecútalo como servicio siguiendo los pasos de ejecutar un script MQTT de Python al arrancar, para que las salidas vuelvan a estar bajo control tras un corte de luz. Con un broker MQTT gestionado no hay nada que instalar ni mantener en la Pi aparte de tu script.
Si estás empezando, el tutorial de MQTT con Raspberry Pi cubre la instalación y lo básico, y la documentación de gpiozero enumera todas las clases de dispositivo compatibles.
Controla tu Pi desde cualquier lugar con ConnectMQ
Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito. El cliente MQTT en el navegador de ConnectMQ te permite enviar comandos ON y OFF y observar los topics de estado sin instalar nada, así que puedes controlar tu Pi desde un teléfono o un portátil minutos después de registrarte.
Preguntas frecuentes
¿Puedo usar RPi.GPIO en lugar de gpiozero?
RPi.GPIO no es compatible con la Raspberry Pi 5. gpiozero con el backend lgpio funciona en todos los modelos actuales y gestiona por ti el antirrebote y los hilos.
¿Por qué usar mensajes retenidos para el estado pero no para los comandos?
El estado retenido permite que los nuevos suscriptores vean el valor actual al instante. Un comando retenido se reproduciría cada vez que la Pi se reconecta y conmutaría las salidas de forma inesperada.
¿Es seguro controlar aparatos de red eléctrica de esta forma?
Solo mediante un módulo de relé o un contactor con la especificación adecuada, instalado por alguien cualificado, y con TLS y credenciales privadas en el broker. Nunca conmutes tensión de red directamente desde los pines GPIO.
¿Con qué rapidez llegan los comandos a la Pi?
Con una conexión sana, los comandos MQTT suelen llegar en bastante menos de un segundo, más que suficiente para luces, relés y la mayoría de las tareas de automatización.



