Ir al contenido

Suscripción MQTT y control de actuadores

Hasta ahora el ESP32 solo publica datos (sensor → broker → Node-RED). En este tutorial lo configuraremos también como suscriptor: Node-RED enviará comandos MQTT y el ESP32 los recibirá para controlar actuadores como LEDs o relés.

[Node-RED]
│ publica en "actuadores/led"
│ payload: {"estado": true}
[Mosquitto broker]
│ entrega a suscriptores del topic
[ESP32]
│ enciende/apaga LED según el comando
│ publica confirmación en "actuadores/led/estado"
[Mosquitto broker]
[Node-RED dashboard] ← muestra el estado actual confirmado

Paso 1: Sketch — publicar y suscribirse simultáneamente

Sección titulada «Paso 1: Sketch — publicar y suscribirse simultáneamente»
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// ── Configuración WiFi ─────────────────────────────────────────────────────
const char* WIFI_SSID = "TU_NOMBRE_DE_RED";
const char* WIFI_PASSWORD = "TU_PASSWORD_WIFI";
// ── Configuración MQTT ─────────────────────────────────────────────────────
const char* MQTT_SERVER = "192.168.1.100";
const int MQTT_PORT = 1883;
const char* MQTT_CLIENT = "esp32-actuador-01";
// Topics
const char* TOPIC_CMD = "actuadores/led"; // recibe comandos
const char* TOPIC_ESTADO = "actuadores/led/estado"; // confirma estado
const char* TOPIC_SENSOR = "sensores/temperatura"; // publica datos
// ── Pines ──────────────────────────────────────────────────────────────────
const int LED_PIN = 2; // LED integrado en la mayoría de placas ESP32
const unsigned long PUBLISH_INTERVAL = 10000;
WiFiClient espClient;
PubSubClient mqtt(espClient);
unsigned long lastPublish = 0;
bool estadoLED = false;
// ──────────────────────────────────────────────────────────────────────────
// Callback: se ejecuta cada vez que llega un mensaje en un topic suscrito
// ──────────────────────────────────────────────────────────────────────────
void onMensaje(char* topic, byte* payload, unsigned int length) {
// Convertir payload a string terminado en null
char msg[length + 1];
memcpy(msg, payload, length);
msg[length] = '\0';
Serial.print("Mensaje recibido [");
Serial.print(topic);
Serial.print("]: ");
Serial.println(msg);
// Parsear JSON
JsonDocument doc;
DeserializationError error = deserializeJson(doc, msg);
if (error) {
Serial.print("Error JSON: ");
Serial.println(error.c_str());
return;
}
// Procesar comando según el topic
if (strcmp(topic, TOPIC_CMD) == 0) {
if (doc["estado"].is<bool>()) {
estadoLED = doc["estado"].as<bool>();
} else if (doc["estado"].is<int>()) {
estadoLED = doc["estado"].as<int>() != 0;
}
digitalWrite(LED_PIN, estadoLED ? HIGH : LOW);
Serial.print("LED -> ");
Serial.println(estadoLED ? "ENCENDIDO" : "APAGADO");
// Publicar confirmación de estado
JsonDocument confirm;
confirm["estado"] = estadoLED;
confirm["sensor"] = MQTT_CLIENT;
confirm["uptime_s"] = millis() / 1000;
char confirmPayload[128];
serializeJson(confirm, confirmPayload);
mqtt.publish(TOPIC_ESTADO, confirmPayload, true); // true = mensaje retenido
}
}
// ──────────────────────────────────────────────────────────────────────────
void conectarWiFi() {
Serial.print("Conectando a WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("WiFi conectado — IP: ");
Serial.println(WiFi.localIP());
}
void conectarMQTT() {
while (!mqtt.connected()) {
Serial.print("Conectando a MQTT...");
if (mqtt.connect(MQTT_CLIENT)) {
Serial.println(" conectado!");
// Suscribirse al topic de comandos después de cada (re)conexión
mqtt.subscribe(TOPIC_CMD);
Serial.print("Suscrito a: ");
Serial.println(TOPIC_CMD);
} else {
Serial.print(" error rc=");
Serial.print(mqtt.state());
Serial.println(" — reintentando en 5s");
delay(5000);
}
}
}
// ──────────────────────────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
conectarWiFi();
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setCallback(onMensaje); // registrar la función de callback
}
// ──────────────────────────────────────────────────────────────────────────
void loop() {
if (!mqtt.connected()) conectarMQTT();
mqtt.loop(); // ← procesa mensajes entrantes y mantiene la conexión viva
// Publicar sensor periódicamente
unsigned long ahora = millis();
if (ahora - lastPublish >= PUBLISH_INTERVAL) {
lastPublish = ahora;
float temperatura = 20.0 + (random(0, 100) / 10.0);
JsonDocument doc;
doc["valor"] = temperatura;
doc["unidad"] = "C";
doc["sensor"] = MQTT_CLIENT;
doc["uptime_s"] = millis() / 1000;
char payload[128];
serializeJson(doc, payload);
mqtt.publish(TOPIC_SENSOR, payload);
}
}

La línea mqtt.loop() en el loop() principal es obligatoria. Es la que:

  • Procesa los mensajes entrantes y llama al callback onMensaje
  • Envía los pings de keepalive al broker para mantener la conexión activa
  • Sin ella, el ESP32 se desconectará del broker en ~60 segundos

[ui-switch] ──→ [change] ──→ [mqtt out]
  1. En Node-RED, arrastra un nodo ui-switch (del Dashboard)

    • Label: LED
    • On payload: {"estado": true} (tipo JSON)
    • Off payload: {"estado": false} (tipo JSON)
  2. Conecta a un nodo mqtt out:

    • Server: mosquitto:1883
    • Topic: actuadores/led
    • QoS: 1
    • Retain: desactivado (el comando no debe ser retenido)
  3. Para mostrar el estado confirmado de vuelta:

    • Nodo mqtt in suscrito a actuadores/led/estado
    • Conecta a un nodo ui-text o ui-led

También puedes enviar comandos manualmente desde tu PC:

Ventana de terminal
# Encender el LED
docker exec -it mosquitto mosquitto_pub \
-h localhost -t "actuadores/led" \
-m '{"estado": true}'
# Apagar el LED
docker exec -it mosquitto mosquitto_pub \
-h localhost -t "actuadores/led" \
-m '{"estado": false}'
# Ver la confirmación del ESP32
docker exec -it mosquitto mosquitto_sub \
-h localhost -t "actuadores/led/estado" -v

Para controlar cargas de 220V (luces, electrodomésticos) usa un módulo relé de 5V controlado por el ESP32.

Módulo Relé ESP32
─────────── ──────
VCC ──→ VIN (5V) ← usa el pin de 5V, no 3.3V
GND ──→ GND
IN ──→ GPIO5 (cualquier GPIO digital)
Carga 220V va en los terminales COM/NO del relé
const int RELAY_PIN = 5;
// En setup()
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, HIGH); // HIGH = relé apagado (lógica invertida en la mayoría de módulos)
// En el callback, reemplaza la lógica del LED:
bool activar = doc["estado"].as<bool>();
digitalWrite(RELAY_PIN, activar ? LOW : HIGH); // LOW = relé encendido

// En loop(), antes de mqtt.loop()
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi perdido — reconectando...");
WiFi.reconnect();
delay(5000);
return;
}
if (!mqtt.connected()) {
conectarMQTT();
}

Si el ESP32 se reinicia, puede recuperar el último estado guardado usando Preferences:

#include <Preferences.h>
Preferences prefs;
// En setup(), después de conectar MQTT:
prefs.begin("estado", false);
estadoLED = prefs.getBool("led", false); // leer último estado
digitalWrite(LED_PIN, estadoLED ? HIGH : LOW);
// Cuando cambia el estado, guardar:
prefs.putBool("led", estadoLED);

Si el ESP32 se reinicia y el relé debe mantener su estado anterior, publica el comando con retain = true desde Node-RED. Mosquitto reenviará el último mensaje retenido al ESP32 cuando se reconecte y suscriba.