Un abridor de puerta de garaje MQTT te permite abrir, cerrar y, sobre todo, comprobar tu puerta de garaje desde cualquier lugar usando el motor que ya tienes. En este tutorial conectarás un ESP32 a un relé que "pulsa" el botón de pared y a un sensor reed que informa de si la puerta está realmente cerrada, y publicarás ese estado como mensaje MQTT retenido para que cualquier aplicación vea la verdad en el momento en que se conecta.
Cómo funciona el abridor
Casi todos los motores de puerta de garaje residenciales tienen bornes para un pulsador de pared. Puentear esos dos bornes brevemente equivale a pulsar el botón: la puerta arranca, se detiene o invierte el sentido según dónde se encuentre. Eso hace que la parte de hardware sea sencilla y no invasiva:
- Un relé conectado en paralelo con el botón de pared da una "pulsación" de 500 ms.
- Un sensor reed en el marco, con un imán en la puerta, se cierra cuando la puerta está completamente cerrada.
- El ESP32 escucha los comandos, activa el relé y publica el estado del sensor reed.
La decisión de diseño clave es que el abridor nunca supone lo que ha hecho la puerta. Publica lo que ve el sensor. Un comando es una petición; el sensor reed es el hecho.
Lista de materiales
| Pieza | Función |
|---|---|
| ESP32 DevKit | Wi-Fi y cliente MQTT |
| Módulo de relé de 1 canal compatible con 3,3 V (o módulo de 5 V con optoacoplador) | Puentea los bornes del botón de pared |
| Sensor reed magnético, normalmente abierto, de superficie | Detecta la puerta completamente cerrada |
| Segundo sensor reed opcional | Detecta la puerta completamente abierta |
| Fuente USB de 5 V y caja | Alimentación y protección contra el polvo |
| Cable de timbre de dos hilos | Del relé a los bornes del motor y del reed al ESP32 |
Antes de cablear nada, consulta el manual de tu motor. Algunos motores modernos usan un panel de pared digital en lugar de un contacto seco; necesitan una interfaz compatible o un mando a distancia de repuesto con su botón conectado al relé.
Cableado
| Desde | Hasta | Notas |
|---|---|---|
| Relé IN | GPIO 26 | El nivel activo depende de tu módulo |
| Relé VCC / GND | 5V / GND | Los módulos con optoacoplador pueden necesitar un JD-VCC aparte |
| Relé COM y NO | Bornes del botón de pared en el motor | En paralelo con el botón existente |
| Sensor reed (posición cerrada) | GPIO 27 y GND | Usa la resistencia pull-up interna |
| Sensor reed (posición abierta, opcional) | GPIO 25 y GND | Usa la resistencia pull-up interna |
Con INPUT_PULLUP, un sensor reed cerrado lee LOW. Monta el reed de posición cerrada de forma que el imán quede a menos de un centímetro solo cuando la puerta esté completamente bajada.
Diseño de topics para el abridor de puerta de garaje MQTT
Siguiendo las convenciones de nuestra guía sobre la estructura de topics MQTT para el hogar inteligente:
<base>/garage/door/state retained: "closed", "open", "opening/closing", "unknown"
<base>/garage/door/state/set command: "OPEN", "CLOSE", "PRESS" (never retained)
<base>/garage/door/availability retained: "online" / "offline" via last will
Retener el topic de estado hace que el dashboard de tu móvil muestre "closed" en cuanto se conecta, en lugar de esperar al siguiente cambio. No retener nunca el topic de comandos hace que un corte de luz no pueda reproducir un OPEN antiguo y abrir la puerta por sí solo.
El sketch del ESP32
El sketch usa PubSubClient. Pon BENCH_TEST a true mientras pruebas en el servidor MQTT público gratuito de broker.connectmq.com con un LED en lugar del motor; ponlo a false para tu broker privado con TLS.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#define BENCH_TEST true
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* BROKER = "broker.connectmq.com";
#if BENCH_TEST
const char* BASE = "connectmq/demo/g4k9t2"; // prefijo aleatorio, área pública
const char* CLIENT_ID = "garage-g4k9t2";
const char* MQTT_USER = nullptr;
const char* MQTT_PASS = nullptr;
WiFiClient net;
const uint16_t PORT = 1883;
#else
const char* BASE = "home1";
const char* CLIENT_ID = "YOUR_CLIENT_ID";
const char* MQTT_USER = "YOUR_USERNAME";
const char* MQTT_PASS = "YOUR_PASSWORD";
const char* ROOT_CA = R"EOF(
-----BEGIN CERTIFICATE-----
...paste the ISRG Root X1 certificate here...
-----END CERTIFICATE-----
)EOF";
WiFiClientSecure net;
const uint16_t PORT = 8883;
#endif
const int RELAY_PIN = 26;
const int REED_CLOSED_PIN = 27;
const int REED_OPEN_PIN = 25;
const bool RELAY_ACTIVE = HIGH; // cambia a LOW para módulos activos a nivel bajo
const unsigned long PRESS_MS = 500;
const unsigned long TRAVEL_TIMEOUT_MS = 30000;
PubSubClient mqtt(net);
String tState, tSet, tAvail;
String lastState = "";
unsigned long lastPressAt = 0;
String readDoor() {
bool closed = digitalRead(REED_CLOSED_PIN) == LOW;
bool open = digitalRead(REED_OPEN_PIN) == LOW;
if (closed) return "closed";
if (open) return "open";
if (millis() - lastPressAt < TRAVEL_TIMEOUT_MS) return "opening/closing";
return "unknown"; // detenida a medio camino o falta el sensor de apertura
}
void pressButton() {
if (millis() - lastPressAt < 2000) return; // ignora repeticiones rápidas
digitalWrite(RELAY_PIN, RELAY_ACTIVE);
delay(PRESS_MS);
digitalWrite(RELAY_PIN, !RELAY_ACTIVE);
lastPressAt = millis();
}
void onMessage(char* topic, byte* payload, unsigned int len) {
String cmd;
for (unsigned int i = 0; i < len; i++) cmd += (char)payload[i];
cmd.trim();
cmd.toUpperCase();
String door = readDoor();
if (cmd == "OPEN" && door == "closed") pressButton();
else if (cmd == "CLOSE" && door == "open") pressButton();
else if (cmd == "PRESS") pressButton();
// OPEN con la puerta abierta o CLOSE con la puerta cerrada se ignoran a propósito
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(CLIENT_ID, MQTT_USER, MQTT_PASS, tAvail.c_str(), 1, true, "offline")) {
mqtt.publish(tAvail.c_str(), "online", true);
mqtt.subscribe(tSet.c_str(), 1);
lastState = ""; // fuerza un estado retenido nuevo
} else {
delay(5000);
}
}
}
void setup() {
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, !RELAY_ACTIVE);
pinMode(REED_CLOSED_PIN, INPUT_PULLUP);
pinMode(REED_OPEN_PIN, INPUT_PULLUP);
tState = String(BASE) + "/garage/door/state";
tSet = tState + "/set";
tAvail = String(BASE) + "/garage/door/availability";
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
#if !BENCH_TEST
net.setCACert(ROOT_CA);
#endif
mqtt.setServer(BROKER, PORT);
mqtt.setCallback(onMessage);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
static unsigned long lastCheck = 0;
if (millis() - lastCheck > 200) {
lastCheck = millis();
String s = readDoor();
if (s != lastState) {
mqtt.publish(tState.c_str(), s.c_str(), true); // estado retenido
lastState = s;
}
}
}
Algunos detalles que conviene observar:
OPENsolo activa el relé cuando la puerta leeclosed, yCLOSEsolo cuando leeopen. Como una pulsación invierte el motor, enviarOPENa una puerta abierta la cerraría si no fuera así.- Se ignoran las pulsaciones separadas por menos de dos segundos, lo que impide que una doble pulsación del botón o un mensaje QoS 1 reintentado inviertan la puerta.
- El sondeo cada 200 ms actúa como un antirrebote sencillo para el sensor reed.
Pruebas en el banco
Conecta un LED con una resistencia a la salida del relé (o simplemente escucha el clic del relé), deja BENCH_TEST en true y graba la placa. Después, desde una terminal:
B=connectmq/demo/g4k9t2
mosquitto_sub -h broker.connectmq.com -p 1883 -i mon-g4k9t2 -t "$B/garage/#" -v
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-g4k9t2 -q 1 -t $B/garage/door/state/set -m PRESS
Pasa un imán junto al sensor reed y observa cómo cambia el estado. Desenchufa el ESP32 y, cuando venza el tiempo de keep-alive, el broker publicará offline en el topic de disponibilidad.
No conectes el motor real de la puerta mientras uses el broker público. Cualquiera que adivine tu topic puede leer y publicar en él. Cambia primero a credenciales privadas.
Puesta en marcha en un broker privado
Pon BENCH_TEST a false, pega el certificado ISRG Root X1 (la raíz de Let's Encrypt) y rellena el usuario, la contraseña y el client ID generados en el portal de ConnectMQ. Ahora el abridor se conecta por TLS en el puerto 8883 y solo los clientes con tus credenciales pueden enviarle comandos. Nuestro artículo sobre buenas prácticas de seguridad MQTT explica por qué las credenciales y TLS son innegociables para cualquier cosa que abra una puerta.
Para controlarlo desde Home Assistant, añade una entidad MQTT de tipo cover que apunte a los mismos topics, o sigue la guía de Home Assistant MQTT:
mqtt:
cover:
- name: "Garage Door"
command_topic: "home1/garage/door/state/set"
state_topic: "home1/garage/door/state"
availability_topic: "home1/garage/door/availability"
payload_open: "OPEN"
payload_close: "CLOSE"
payload_stop: "PRESS"
state_open: "open"
state_closed: "closed"
state_opening: "opening/closing"
qos: 1
Checklist de seguridad
- Mantén conectadas y en funcionamiento las funciones de seguridad propias del motor (fotocélulas, inversión automática).
- Nunca retengas los mensajes de comando.
- Actúa ante
OPEN/CLOSEsolo cuando el sensor confirme el estado contrario. - Usa TLS y credenciales privadas antes de conectar el motor.
- Añade una notificación cuando la puerta lleve
openmás tiempo del esperado. - Monta el relé dentro de una caja, lejos de las piezas móviles.
- Prueba qué ocurre si el Wi-Fi se cae en mitad del recorrido: la puerta simplemente debe terminar su movimiento.
Para la notificación de "puerta abierta", las reglas de alerta por umbral del portal de ConnectMQ pueden señalar valores en un topic, o una automatización en Home Assistant o Node-RED puede vigilar que open dure más de diez minutos.
Solución de problemas
El relé hace clic pero la puerta no se mueve. Probablemente tu motor usa un panel de pared digital. Conecta el relé al botón de un mando a distancia de repuesto.
El estado oscila entre closed y unknown. El imán está demasiado lejos del sensor reed o la puerta vibra. Acércalos o añade un antirrebote más largo.
La puerta invierte el sentido cuando envías OPEN. Estás enviando PRESS, o el sensor de cierre no lee LOW. Comprueba el cableado del reed con el monitor serie.
El dispositivo nunca vuelve a estar online. Comprueba que el client ID es único; dos dispositivos con el mismo ID se expulsan mutuamente. En un broker privado, confirma también que usas exactamente el client ID emitido con tus credenciales, porque los client IDs están vinculados a ellas.
Los comandos llegan tarde o dos veces. QoS 1 garantiza una entrega de al menos una vez, así que un mensaje reintentado puede llegar tras una reconexión. El bloqueo de dos segundos entre pulsaciones y las comprobaciones de estado de onMessage hacen que los duplicados sean inofensivos.
Controla tu garaje con seguridad con ConnectMQ
Un abridor de puertas merece un broker privado y cifrado en lugar de uno público. ConnectMQ es un broker MQTT gestionado con TLS, credenciales generadas y un explorador de topics en vivo para observar el estado de la puerta. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, cambia BENCH_TEST a false y estarás en marcha. Para más contexto sobre el alojamiento, lee sobre el broker MQTT en la nube, y descubre más proyectos, como la detección de fugas de agua con IoT, en nuestra guía de automatización IoT.
Preguntas frecuentes
¿Funcionará con cualquier motor de puerta de garaje?
Funciona con motores que admiten un pulsador de pared de contacto seco sencillo, lo que cubre la mayoría de los abridores residenciales. Los motores con paneles de pared digitales necesitan en su lugar un mando a distancia de repuesto conectado al relé.
¿Por qué usar un sensor reed en lugar de seguir los comandos?
Los comandos pueden fallar, un obstáculo puede detener la puerta o alguien puede usar el mando a distancia normal. Solo un sensor te dice dónde está realmente la puerta.
¿Es seguro controlar una puerta de garaje por MQTT?
Es razonable si usas TLS, credenciales privadas y comandos no retenidos, y mantienes la inversión de seguridad integrada del motor. Nunca conectes una puerta real a un servidor MQTT público de pruebas.
¿Puedo añadir un segundo sensor para la posición completamente abierta?
Sí. El sketch ya lee un reed opcional de posición abierta en el GPIO 25, que convierte "unknown" en un estado "open" claro.



