ESP32 e Arduino

ESP32 Relé MQTT: Acione Cargas da Rede Elétrica com Comandos e Estado Retido

Ligue um módulo relé a um ESP32, acione-o por um topic de comando MQTT e publique o estado retido para que todos os dashboards e apps sempre saibam se a carga está ligada ou desligada.

Placa ESP32 ligada a um módulo relé de dois canais em um projeto ESP32 relé MQTT
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

Um controlador ESP32 com relé via MQTT permite acionar lâmpadas, bombas (como as de irrigação de uma fazenda), ventiladores ou contatores a partir de qualquer app, script ou dashboard capaz de publicar uma mensagem. Neste tutorial, você vai ligar um módulo relé a um ESP32, inscrever-se em um topic de comando, publicar o estado do relé como retained message e restaurar o último estado conhecido após uma reinicialização. Tudo é testado na área pública gratuita do broker.connectmq.com e depois migrado para credenciais em produção.

Como Funciona o Projeto ESP32 Relé MQTT

Um nó de relé é o atuador útil mais simples da IoT, mas também é onde muitos projetos dão errado. O erro comum é tratar o MQTT como um botão remoto: algo publica ON, o ESP32 aciona o relé e ninguém nunca sabe se o comando realmente chegou. Um projeto melhor separa o que alguém quer do que o dispositivo está realmente fazendo.

Topic Direção Retido Payload
connectmq/demo/<id>/relay/1/set App para o ESP32 Não ON, OFF, TOGGLE
connectmq/demo/<id>/relay/1/state ESP32 para os apps Sim ON ou OFF
connectmq/demo/<id>/status ESP32 para os apps Sim online ou offline (Last Will)

O topic de comando não é retido. Se fosse, cada reconexão repetiria um comando antigo, e um relé que alguém desligou manualmente poderia voltar a ligar de repente. O topic de estado é retido, então um dashboard aberto um minuto depois recebe imediatamente o estado atual, sem esperar pela próxima mudança. O topic status usa o Last Will do MQTT para que os assinantes saibam quando o nó sai da rede.

Se retained messages e Last Will são novidade para você, a visão geral sobre o que um broker MQTT faz explica os dois em poucos minutos.

Hardware e Ligações

Você vai precisar de:

  • Qualquer placa de desenvolvimento ESP32 (DevKitC, NodeMCU-32S, módulos WROOM ou WROVER funcionam).
  • Um módulo relé de 1, 2 ou 4 canais com entrada por optoacoplador. Módulos de 5 V com jumpers de "high/low level trigger" são os mais flexíveis.
  • Jumpers e um cabo USB. Para qualquer coisa ligada à rede elétrica, um gabinete adequado e bornes.

Ligação típica para um módulo de 5 V:

Pino do módulo relé Pino do ESP32 Observação
VCC 5V (VIN) Alimenta a bobina do relé
GND GND Terra comum
IN1 GPIO 26 Sinal de controle do canal 1
IN2 GPIO 27 Sinal de controle do canal 2 (opcional)

A maioria das placas de relé baratas é ativa em nível baixo: o relé é energizado quando a entrada é levada ao GND. O sketch abaixo tem uma única constante para inverter essa lógica. Evite os GPIOs 0, 2, 12 e 15 para relés, porque são pinos de strapping, e um relé estalando durante o boot é incômodo e, com algumas cargas, perigoso. Os GPIOs 34 a 39 são somente entrada e não vão funcionar.

Um aviso de segurança: se você for acionar tensão da rede elétrica, o módulo relé precisa ser dimensionado para a carga, o lado de alta tensão precisa ficar fechado em um gabinete e o trabalho deve seguir as normas elétricas locais. Enquanto desenvolve o firmware, acione uma fita de LED de baixa tensão, ou nada, e apenas escute o clique.

Instale as Bibliotecas

Na Arduino IDE 2.x, instale o pacote de placas esp32 da Espressif pelo Boards Manager e a biblioteca PubSubClient, de Nick O'Leary, pelo Gerenciador de Bibliotecas. Nenhuma outra biblioteca é necessária. Se você nunca montou um sketch MQTT no ESP32, o tutorial IoT com ESP32 mostra com mais detalhes a configuração da placa e a sua primeira publicação.

O Sketch Completo ESP32 Relé MQTT

O sketch abaixo suporta qualquer quantidade de relés, trata reconexões sem bloquear o loop e publica o estado retido após cada mudança.

#include <WiFi.h>
#include <PubSubClient.h>

// ---------- Configuração ----------
const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* DEVICE_ID = "relay-7f3a91";          // torne-o único
const String BASE = String("connectmq/demo/") + DEVICE_ID;

const bool ACTIVE_LOW = true;                     // a maioria das placas de relé
const uint8_t RELAY_PINS[] = {26, 27};
const uint8_t RELAY_COUNT = sizeof(RELAY_PINS);
bool relayState[RELAY_COUNT] = {false};
bool restoreWindow = true;                        // aceita o estado retido uma vez no boot
unsigned long connectedAt = 0;

WiFiClient net;
PubSubClient mqtt(net);
unsigned long lastAttempt = 0;

void applyRelay(uint8_t i, bool on) {
  relayState[i] = on;
  digitalWrite(RELAY_PINS[i], (on ^ ACTIVE_LOW) ? HIGH : LOW);
}

void publishState(uint8_t i) {
  String topic = BASE + "/relay/" + String(i + 1) + "/state";
  mqtt.publish(topic.c_str(), relayState[i] ? "ON" : "OFF", true);
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  String t(topic);
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
  msg.trim();
  msg.toUpperCase();

  // Esperado: <BASE>/relay/<n>/set ou <BASE>/relay/<n>/state
  int start = BASE.length() + 7;                  // pula "/relay/"
  int slash = t.indexOf('/', start);
  if (slash < 0) return;
  int n = t.substring(start, slash).toInt();
  if (n < 1 || n > RELAY_COUNT) return;
  uint8_t i = n - 1;
  String leaf = t.substring(slash + 1);

  if (leaf == "state") {
    // Estado retido de antes da reinicialização: restaura uma vez e depois ignora
    if (restoreWindow) applyRelay(i, msg == "ON");
    return;
  }
  if (leaf != "set") return;

  if (msg == "ON")          applyRelay(i, true);
  else if (msg == "OFF")    applyRelay(i, false);
  else if (msg == "TOGGLE") applyRelay(i, !relayState[i]);
  else return;                                    // ignora comandos desconhecidos

  publishState(i);
  Serial.printf("Relay %d -> %s\n", n, relayState[i] ? "ON" : "OFF");
}

bool connectMqtt() {
  String willTopic = BASE + "/status";
  bool ok = mqtt.connect(DEVICE_ID, nullptr, nullptr,
                         willTopic.c_str(), 1, true, "offline");
  if (!ok) {
    Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
    return false;
  }
  mqtt.publish(willTopic.c_str(), "online", true);
  for (uint8_t i = 0; i < RELAY_COUNT; i++) {
    String base = BASE + "/relay/" + String(i + 1);
    mqtt.subscribe((base + "/set").c_str(), 1);
    if (restoreWindow) mqtt.subscribe((base + "/state").c_str(), 1);
  }
  connectedAt = millis();
  Serial.println("MQTT connected");
  return true;
}

void setup() {
  Serial.begin(115200);
  for (uint8_t i = 0; i < RELAY_COUNT; i++) {
    pinMode(RELAY_PINS[i], OUTPUT);
    applyRelay(i, false);                         // padrão seguro antes do Wi-Fi
  }
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(30);
  mqtt.setSocketTimeout(5);
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) return;

  if (!mqtt.connected()) {
    if (millis() - lastAttempt > 5000) {
      lastAttempt = millis();
      connectMqtt();
    }
    return;
  }
  mqtt.loop();

  // Fecha a janela de restauração dois segundos após a primeira conexão
  if (restoreWindow && millis() - connectedAt > 2000) {
    restoreWindow = false;
    for (uint8_t i = 0; i < RELAY_COUNT; i++) {
      String s = BASE + "/relay/" + String(i + 1) + "/state";
      mqtt.unsubscribe(s.c_str());
      publishState(i);                            // confirma o estado real
    }
  }
}

O que a janela de restauração faz

Quando o ESP32 reinicia após uma queda de energia, os relés começam na posição segura OFF. Na primeira conexão, o sketch se inscreve por um instante nos seus próprios topics state retidos. O broker entrega imediatamente o último estado que tem guardado, o sketch o aplica e, depois de dois segundos, cancela a inscrição e publica novamente o estado real. Se você prefere que os relés sempre comecem desligados, defina restoreWindow como false. Para cargas críticas, como aquecedores, "sempre desligado após reiniciar" costuma ser a política mais segura.

Lembre-se de que isso depende de o broker ainda guardar a retained message. As retained messages existem enquanto o broker as mantém, então trate a restauração como uma conveniência, e não como uma garantia, e projete a carga de modo que OFF seja sempre um estado aceitável.

Teste o Relé a Partir do Seu Computador

Abra um terminal e acompanhe tudo sob o prefixo do seu dispositivo:

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-7f3a91 -v -t "connectmq/demo/relay-7f3a91/#"

Em um segundo terminal, envie comandos:

mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-7f3a91 -t "connectmq/demo/relay-7f3a91/relay/1/set" -m ON
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-7f3a91 -t "connectmq/demo/relay-7f3a91/relay/1/set" -m TOGGLE

Você deve ouvir o clique e ver o estado retido ecoado de volta:

connectmq/demo/relay-7f3a91/status online
connectmq/demo/relay-7f3a91/relay/1/state ON
connectmq/demo/relay-7f3a91/relay/1/state OFF

Agora desconecte o ESP32. Em aproximadamente um intervalo e meio de keep alive, o broker publica o Last Will, e o assinante imprime status offline. Esse é exatamente o sinal de que um dashboard precisa para deixar o interruptor acinzentado. Como o broker.connectmq.com é um servidor MQTT público (gratuito), qualquer pessoa que adivinhe o seu topic pode acionar o seu relé, então nunca deixe uma carga real ligada à área pública.

Checklist de Solução de Problemas

  1. O relé estala no boot. Você está em um pino de strapping, ou o módulo é ativo em nível baixo e o pino fica flutuando antes de setup() rodar. Passe para os GPIOs 26/27/32/33 e defina o nível do pino antes do pinMode quando possível.
  2. O relé nunca comuta, embora o LED do módulo acenda. Muitos módulos precisam de 5 V no VCC para a bobina; 3,3 V acende o LED, mas não consegue atracar os contatos. Algumas placas também precisam do jumper JD-VCC instalado.
  3. Os comandos funcionam uma vez e depois param. Procure state=-3 (conexão perdida) no log serial. Usar o mesmo client ID em dois dispositivos faz com que um derrube o outro repetidamente.
  4. O ESP32 reinicia quando o relé comuta. Cargas indutivas causam quedas na alimentação e ruído. Alimente a bobina do relé com uma fonte de 5 V separada, com terra compartilhado, e use um snubber ou proteção flyback no lado da carga.
  5. O topic de estado mostra o valor errado após reiniciar. Outra coisa está publicando retained messages no topic state. Somente o próprio dispositivo deve publicar ali.
  6. Nada chega. Os nomes de topics diferenciam maiúsculas de minúsculas, e uma barra sobrando no final cria um topic diferente.

Vá para Produção com Credenciais e TLS

Em uma instalação real, crie um broker no portal do ConnectMQ e use o usuário, a senha e o client ID gerados. Troque WiFiClient por WiFiClientSecure, conecte-se à porta 8883 e passe as credenciais para connect():

#include <WiFiClientSecure.h>
WiFiClientSecure net;
// no setup(): net.setCACert(ROOT_CA_PEM);   // ISRG Root X1 (Let's Encrypt)
mqtt.setServer("broker.connectmq.com", 8883);
mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD",
             willTopic.c_str(), 1, true, "offline");

Como os client IDs ficam vinculados às credenciais emitidas, use o client ID que o portal gerou em vez de inventar um. A configuração completa do certificado, incluindo a sincronização de hora e as considerações de memória, está em ESP32 MQTT com SSL/TLS. Se o seu nó de relé precisa sobreviver a um Wi-Fi instável, também vale acrescentar a estratégia de backoff de lógica de reconexão MQTT no ESP32.

Um broker MQTT gerenciado importa mais para atuadores: um relé que você não consegue alcançar é pior do que um sensor que você não consegue ler. Com o ConnectMQ, você tem TLS na 8883, credenciais por dispositivo e um portal onde pode acompanhar relay/+/state no explorador de topics ao vivo ou publicar um comando de teste pelo cliente no navegador, sem abrir um terminal.

Ideias para Expandir o Nó de Relé

  • Temporizadores. Aceite {"state":"ON","seconds":600} em JSON e desligue automaticamente após dez minutos. Mantenha o temporizador no dispositivo, para que ele desligue mesmo se a rede cair.
  • Botão local. Adicione um botão que alterna o relé e publica o novo estado, para que o interruptor físico e o app nunca discordem.
  • Intertravamentos. Para reversão de motores ou válvulas de duas vias, recuse ligar os dois canais ao mesmo tempo no firmware, e não no app.
  • Projetos irmãos com servo ou LED. O mesmo padrão de comando/estado funciona para controle de servo via MQTT e fitas de LED endereçáveis.

Comece a Usar o ConnectMQ

Crie um broker Serverless gratuito em app.connectmq.com, sem precisar de cartão de crédito. Você recebe as credenciais em menos de um minuto, TLS pronto para uso e um explorador de topics ao vivo para ver seus relés comutando. Quando o plano gratuito ficar pequeno, o mesmo firmware passa para um plano pago ou dedicado sem mudar nada além das credenciais.

Perguntas frequentes

O topic de comando do relé deve ser retido?

Não. Retenha o topic de estado, não o de comando. Um comando retido seria repetido a cada reconexão e poderia acionar uma carga de forma inesperada.

Qual QoS devo usar para comandos de relé?

QoS 1 é a escolha prática: o comando é entregue pelo menos uma vez, e como ON e OFF são idempotentes, uma duplicata não causa problema. O PubSubClient pode se inscrever com QoS 0 ou 1.

Posso controlar vários relés com um ESP32?

Sim. Adicione os pinos a RELAY_PINS, e cada um ganha seus próprios topics relay/<n>/set e relay/<n>/state. Uma única inscrição com curinga em relay/+/set também funciona se você extrair o número do canal do topic.

Como sei se o ESP32 está offline?

Inscreva-se no topic status retido. O ESP32 publica online quando se conecta, e o broker publica a mensagem de Last Will offline se a conexão cair sem uma desconexão limpa.

Artigos relacionados

Exemplo de MQTT com ESP-IDF: Monte um Cliente esp-mqtt do Zero

Monte um projeto ESP-IDF a partir de uma pasta vazia com o componente esp-mqtt: configuração do Wi-Fi em modo station, o handler de eventos MQTT, publicação e inscrição, propriedades do MQTT 5 e TLS com o bundle de certificados.

8 min de leitura

Usando os Recursos do MQTT 5 do esp-mqtt no ESP32 com ESP-IDF

Um projeto ESP-IDF prático que ativa o MQTT 5.0 no esp-mqtt e usa user properties, expiração de mensagens, topic aliases, expiração de sessão e response topics com um broker MQTT 5 real.

8 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.