Um abridor de portão de garagem MQTT permite abrir, fechar e, principalmente, conferir o seu portão de garagem de qualquer lugar, usando o motor que você já tem. Neste tutorial você vai ligar um ESP32 a um relé que "aperta" o botão de parede e a um sensor reed que informa se o portão está realmente fechado, e depois publicar esse estado como uma retained message MQTT, para que todo aplicativo veja a verdade no momento em que se conecta.
Como o abridor funciona
Quase todo motor de portão de garagem residencial tem terminais para um botão de parede. Fechar esses dois terminais por um instante equivale a apertar o botão: o portão começa a se mover, para ou inverte o sentido, dependendo de onde está. Isso torna a parte de hardware simples e não invasiva:
- Um relé ligado em paralelo com o botão de parede dá um "aperto" de 500 ms.
- Um sensor reed no batente, com um ímã no portão, fecha o contato quando o portão está totalmente fechado.
- O ESP32 escuta os comandos, pulsa o relé e publica o estado do sensor reed.
A principal escolha de design é que o abridor nunca presume o que o portão fez. Ele publica o que o sensor vê. Um comando é um pedido; o sensor reed é o fato.
Lista de componentes
| Componente | Finalidade |
|---|---|
| ESP32 DevKit | Wi-Fi e client MQTT |
| Módulo de relé de 1 canal compatível com 3,3 V (ou módulo de 5 V com optoacoplador) | Fecha os terminais do botão de parede |
| Sensor reed magnético normalmente aberto, de sobrepor | Detecta o portão totalmente fechado |
| Segundo sensor reed opcional | Detecta o portão totalmente aberto |
| Fonte USB de 5 V e caixa | Alimentação e proteção contra poeira |
| Fio paralelo de campainha | Relé até os terminais do motor, reed até o ESP32 |
Antes de ligar qualquer coisa, consulte o manual do seu motor. Alguns motores mais novos usam um painel de parede digital em vez de um contato seco; esses precisam de uma interface compatível ou de um controle remoto reserva com o botão ligado ao relé.
Ligações
| De | Para | Observações |
|---|---|---|
| IN do relé | GPIO 26 | O nível ativo depende do seu módulo |
| VCC / GND do relé | 5V / GND | Módulos com optoacoplador podem precisar de um JD-VCC separado |
| COM e NO do relé | Terminais do botão de parede no motor | Em paralelo com o botão existente |
| Sensor reed (posição fechada) | GPIO 27 e GND | Usa o pull-up interno |
| Sensor reed (posição aberta, opcional) | GPIO 25 e GND | Usa o pull-up interno |
Com INPUT_PULLUP, um sensor reed fechado lê LOW. Instale o reed da posição fechada de modo que o ímã fique a cerca de um centímetro dele somente quando o portão estiver totalmente abaixado.
Design de topics para o abridor de portão de garagem MQTT
Seguindo as convenções do nosso guia de estrutura de topics MQTT para casa 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
Reter o topic de estado faz com que o dashboard do seu celular mostre "closed" imediatamente ao conectar, em vez de esperar pela próxima mudança. Nunca reter o topic de comando significa que uma queda de energia não consegue reproduzir um OPEN antigo e abrir o portão sozinha.
O sketch do ESP32
O sketch usa a PubSubClient. Defina BENCH_TEST como true enquanto testa no servidor MQTT público (broker.connectmq.com) com um LED no lugar do motor; defina como false para o seu broker MQTT gerenciado privado com 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"; // prefixo aleatório, á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; // mude para LOW em módulos ativos em nível baixo
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"; // parado no meio do caminho ou sem o sensor de aberto
}
void pressButton() {
if (millis() - lastPressAt < 2000) return; // ignora repetições 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 com o portão aberto ou CLOSE com ele fechado é ignorado de 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 = ""; // força um novo estado retido
} 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 retido
lastState = s;
}
}
}
Alguns detalhes que merecem atenção:
OPENsó pulsa o relé quando o portão lêclosed, eCLOSEsó quando lêopen. Como um aperto alterna o motor, enviarOPENpara um portão aberto acabaria fechando-o.- Apertos com menos de dois segundos de intervalo são ignorados, o que impede que um toque duplo no botão ou uma mensagem QoS 1 reenviada inverta o portão.
- A leitura a cada 200 ms funciona como um debounce simples para o sensor reed.
Testando na bancada
Ligue um LED com resistor na saída do relé (ou apenas escute o clique do relé), mantenha BENCH_TEST como true e grave a placa. Depois, em um 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
Passe um ímã perto do sensor reed e veja o estado mudar. Desconecte o ESP32 e, depois do tempo limite do keep-alive, o broker publica offline no topic de disponibilidade.
Não conecte o motor real do portão enquanto estiver usando o servidor MQTT público. Ele serve apenas para testes e protótipos, não para dados de produção. Qualquer pessoa que adivinhe o seu topic pode ler e publicar nele. Mude primeiro para credenciais privadas.
Entrando em operação em um broker privado
Defina BENCH_TEST como false, cole o certificado ISRG Root X1 (a raiz do Let's Encrypt) e preencha o usuário, a senha e o client ID gerados no portal do ConnectMQ. Agora o abridor se conecta via TLS na porta 8883 e apenas clients com as suas credenciais podem comandá-lo. Nosso artigo de boas práticas de segurança MQTT explica por que credenciais e TLS são inegociáveis para qualquer coisa que abra uma porta.
Para controlá-lo pelo Home Assistant, adicione uma entidade MQTT cover apontando para os mesmos topics ou siga o guia de MQTT no Home Assistant:
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 segurança
- Mantenha os recursos de segurança do próprio motor (fotocélulas, reversão automática) conectados e funcionando.
- Nunca retenha mensagens de comando.
- Só aja em
OPEN/CLOSEquando o sensor confirmar o estado oposto. - Use TLS e credenciais privadas antes de conectar o motor.
- Adicione uma notificação para quando o portão ficar
openpor mais tempo do que o esperado. - Instale o relé dentro de uma caixa, longe das partes móveis.
- Teste o que acontece quando o Wi-Fi cai no meio do percurso: o portão deve simplesmente terminar o movimento.
Para a notificação de "portão esquecido aberto", as regras de alerta por limite do portal do ConnectMQ podem sinalizar valores em um topic, ou uma automação no Home Assistant ou no Node-RED pode observar se o open dura mais de dez minutos.
Solução de problemas
O relé clica, mas o portão não se move. Seu motor provavelmente usa um painel de parede digital. Ligue o relé no botão de um controle remoto reserva.
O estado oscila entre closed e unknown. O ímã está longe demais do sensor reed ou o portão trepida. Aproxime-os ou aumente o tempo de debounce.
O portão inverte o sentido quando você envia OPEN. Você está enviando PRESS ou o sensor de fechado não está lendo LOW. Verifique a ligação do reed com o monitor serial.
O dispositivo nunca volta a ficar online. Verifique se o client ID é único; dois dispositivos com o mesmo ID derrubam um ao outro. Em um broker privado, confirme também que você está usando exatamente o client ID emitido com as suas credenciais, porque os client IDs ficam vinculados a elas.
Os comandos chegam atrasados ou duas vezes. O QoS 1 garante entrega pelo menos uma vez, então uma mensagem reenviada pode chegar depois de uma reconexão. O bloqueio de dois segundos entre apertos e as verificações de estado em onMessage tornam as duplicatas inofensivas.
Controle sua garagem com segurança com o ConnectMQ
Um abridor de portão merece um broker privado e criptografado, e não o servidor MQTT público. O ConnectMQ é um broker MQTT gerenciado com TLS, credenciais geradas e um explorador de topics ao vivo para acompanhar o estado do seu portão. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, mude BENCH_TEST para false e pronto, está em operação. Para entender a hospedagem, leia sobre o broker MQTT na nuvem e veja mais projetos, como detecção de vazamento de água com IoT, no nosso guia de automação IoT.
Perguntas frequentes
Isso funciona com qualquer motor de portão de garagem?
Funciona com motores que aceitam um botão de parede simples de contato seco, o que abrange a maioria dos abridores residenciais. Motores com painéis de parede digitais precisam de um controle remoto reserva ligado ao relé.
Por que usar um sensor reed em vez de acompanhar os comandos?
Os comandos podem falhar, o portão pode ser parado por um obstáculo ou alguém pode usar o controle remoto comum. Só um sensor diz onde o portão realmente está.
É seguro controlar um portão de garagem via MQTT?
É razoável quando você usa TLS, credenciais privadas, comandos não retidos e mantém a reversão de segurança embutida no motor. Nunca conecte um portão real ao servidor MQTT público, que é só para testes.
Posso adicionar um segundo sensor para a posição totalmente aberta?
Sim. O sketch já lê um reed opcional de posição aberta no GPIO 25, o que transforma "unknown" em um estado "open" claro.



