El ESP8266 sigue siendo una de las formas más baratas de conectar un sensor por Wi-Fi, y los proyectos ESP8266 MQTT encajan de forma natural con su reducido tamaño. En este tutorial conectarás un NodeMCU o un Wemos D1 mini a un broker con PubSubClient, publicarás telemetría JSON, encenderás el LED integrado desde tu ordenador y después pasarás a TLS sin quedarte sin memoria.
Qué necesitas
- Un NodeMCU v2/v3, un Wemos D1 mini o cualquier placa ESP8266 con USB.
- Arduino IDE 2.x con el paquete de placas "esp8266 by ESP8266 Community" (añade
https://arduino.esp8266.com/stable/package_esp8266com_index.jsonen Archivo → Preferencias → Gestor de URLs adicionales de tarjetas). - La librería PubSubClient de Nick O'Leary, desde el Gestor de librerías.
- Las herramientas de línea de comandos de Mosquitto en tu ordenador para hacer pruebas.
- Una red Wi-Fi de 2,4 GHz. El ESP8266 no es compatible con 5 GHz.
Para los primeros pasos usarás broker.connectmq.com, el servidor MQTT público gratuito que opera ConnectMQ. Acepta clientes anónimos en el puerto 1883 con cualquier client ID único y no vacío. Trátalo como un tablón de anuncios público para pruebas y prototipos, no para datos de producción: cualquiera puede leer lo que publiques ahí.
Ajustes de placa que importan
Antes de escribir código, configura esto en el menú Herramientas:
| Ajuste | Valor recomendado | Por qué |
|---|---|---|
| Placa | NodeMCU 1.0 (ESP-12E Module) o LOLIN(WEMOS) D1 mini | Distribución de flash y nombres de pines correctos |
| CPU Frequency | 160 MHz para TLS, 80 MHz en otro caso | Los handshakes TLS son mucho más rápidos a 160 MHz |
| SSL Support | All SSL ciphers (por defecto) o Basic SSL ciphers | Basic ahorra flash y sigue funcionando con brokers modernos |
| lwIP Variant | v2 Lower Memory | Deja más heap para tu sketch |
| Upload Speed | 921600 (baja a 115200 si fallan las subidas) | Iteraciones más rápidas |
Paso 1: tu primer sketch ESP8266 MQTT
El sketch siguiente usa el LED integrado en GPIO2 (LED_BUILTIN), que en la mayoría de los módulos es activo a nivel bajo. Publica el tiempo de funcionamiento, la intensidad de señal y el heap libre cada cinco segundos, y escucha los comandos ON u OFF.
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
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;
char deviceId[24];
char topicTelemetry[64];
char topicLedSet[64];
char topicLedState[64];
WiFiClient netClient;
PubSubClient mqtt(netClient);
unsigned long lastPublish = 0;
unsigned long lastAttempt = 0;
void setLed(bool on) {
digitalWrite(LED_BUILTIN, on ? LOW : HIGH); // activo a nivel bajo
mqtt.publish(topicLedState, on ? "ON" : "OFF", true);
}
void onMessage(char* topic, byte* payload, unsigned int length) {
char msg[16];
unsigned int n = length < sizeof(msg) - 1 ? length : sizeof(msg) - 1;
memcpy(msg, payload, n);
msg[n] = '\0';
Serial.printf("[%s] %s\n", topic, msg);
if (strcmp(msg, "ON") == 0) setLed(true);
if (strcmp(msg, "OFF") == 0) setLed(false);
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.printf(" connected, IP %s\n", WiFi.localIP().toString().c_str());
}
bool connectMqtt() {
if (mqtt.connect(deviceId, nullptr, nullptr, topicLedState, 1, true, "offline")) {
Serial.println("MQTT connected");
mqtt.subscribe(topicLedSet, 1);
setLed(false);
return true;
}
Serial.printf("MQTT failed, rc=%d\n", mqtt.state());
return false;
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
// ID único a partir del chip ID, p. ej. esp8266-3f2a1b
snprintf(deviceId, sizeof(deviceId), "esp8266-%06x", ESP.getChipId());
snprintf(topicTelemetry, sizeof(topicTelemetry), "connectmq/demo/%s/telemetry", deviceId);
snprintf(topicLedSet, sizeof(topicLedSet), "connectmq/demo/%s/led/set", deviceId);
snprintf(topicLedState, sizeof(topicLedState), "connectmq/demo/%s/led/state", deviceId);
Serial.printf("\nDevice ID: %s\n", deviceId);
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(30);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) {
if (millis() - lastAttempt > 5000) {
lastAttempt = millis();
connectMqtt();
}
return;
}
mqtt.loop();
if (millis() - lastPublish > 5000) {
lastPublish = millis();
char payload[96];
snprintf(payload, sizeof(payload), "{\"uptime\":%lu,\"rssi\":%d,\"heap\":%u}",
millis() / 1000, WiFi.RSSI(), ESP.getFreeHeap());
mqtt.publish(topicTelemetry, payload);
}
}
Abre el monitor serie a 115200 baudios, anota el ID del dispositivo y suscríbete desde tu ordenador (sustituye el ID por el tuyo):
mosquitto_sub -h broker.connectmq.com -p 1883 -i laptop-3f2a1b -t "connectmq/demo/esp8266-3f2a1b/#" -v
Enciende el LED:
mosquitto_pub -h broker.connectmq.com -p 1883 -i ctl-3f2a1b -t "connectmq/demo/esp8266-3f2a1b/led/set" -m ON
El topic led/state es retenido, así que cualquier suscriptor nuevo sabe al instante si el LED está encendido, y el Last Will lo pone en offline si la placa se queda sin alimentación.
Paso 2: entiende el presupuesto de memoria del ESP8266
Cuando el sketch sin cifrar funcione, fíjate en el valor heap que publica. Un sketch sencillo suele dejar bastante heap libre, pero TLS cambia el panorama. Una sesión BearSSL con los buffers por defecto necesita un bloque contiguo de memoria grande para sus buffers de recepción y transmisión, además del trabajo de validación de certificados, y el ESP8266 tiene solo una fracción de la RAM de un ESP32.
Consecuencias prácticas:
- Mantén el buffer de PubSubClient tan pequeño como te permitan tus mensajes. Llamar a
setBufferSize(4096)"por si acaso" puede marcar la diferencia entre un handshake que funciona y uno que falla. - Evita el uso intensivo de la clase
Stringen sketches de larga duración; las asignaciones repetidas fragmentan el heap y el handshake TLS puede fallar días después aunque el heap libre parezca suficiente.ESP.getMaxFreeBlockSize()muestra el bloque contiguo más grande, que es la cifra que importa. - Construye el JSON con
snprintfo con un documento ArduinoJson de tamaño fijo en lugar de concatenar cadenas.
Si necesitas payloads grandes con frecuencia, lee el tamaño del buffer de PubSubClient para conocer técnicas de streaming que evitan buffers grandes.
Paso 3: ESP8266 MQTT sobre TLS
En producción, tu dispositivo debería conectarse a un broker privado por TLS en el puerto 8883 con sus propias credenciales. Con un broker MQTT gestionado como ConnectMQ, obtienes un usuario, una contraseña y un client ID desde el portal. El certificado del servidor lo emite Let's Encrypt, así que confías en el certificado raíz ISRG Root X1.
El núcleo del ESP8266 usa BearSSL. A diferencia del ESP32, comprueba las fechas de validez del certificado, así que el reloj debe ser correcto antes del handshake.
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>
// ISRG Root X1 (raíz de Let's Encrypt), pega el PEM completo
static const char ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
...ISRG Root X1...
-----END CERTIFICATE-----
)EOF";
BearSSL::X509List trustAnchors(ROOT_CA);
BearSSL::WiFiClientSecure tlsClient;
PubSubClient mqtt(tlsClient);
void syncClock() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
Serial.print("Waiting for NTP");
time_t now = time(nullptr);
while (now < 1700000000) { // cualquier marca de tiempo claramente pasada
delay(250);
Serial.print(".");
now = time(nullptr);
}
Serial.println(" done");
}
void setupTls() {
syncClock();
tlsClient.setTrustAnchors(&trustAnchors);
// Opcional: buffers más pequeños si el servidor admite max fragment length
if (tlsClient.probeMaxFragmentLength("broker.connectmq.com", 8883, 1024)) {
tlsClient.setBufferSizes(1024, 1024);
}
mqtt.setServer("broker.connectmq.com", 8883);
}
bool connectMqtt() {
return mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD");
}
Llama a setupTls() en setup() después de que se conecte el Wi-Fi. Pon la frecuencia de CPU a 160 MHz; a 80 MHz el primer handshake puede tardar un rato apreciable, y al watchdog no le gustará un connect() lento dentro de un bucle ocupado.
Paso 4: ESP8266 MQTT con batería y deep sleep
Muchos sensores ESP8266 funcionan con baterías y solo necesitan informar cada pocos minutos. La radio es, con diferencia, lo que más consume, así que el patrón ganador es: despertar, conectar, publicar, desconectar y dormir. El deep sleep del ESP8266 requiere un puente físico entre GPIO16 (D0 en NodeMCU y D1 mini) y el pin RST, porque el chip se despierta reiniciándose. Desconecta ese cable durante la subida si tu placa se comporta de forma extraña al grabarla.
void publishAndSleep() {
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
if (mqtt.connect(deviceId)) {
char payload[64];
snprintf(payload, sizeof(payload), "{\"a0\":%d,\"rssi\":%d}", analogRead(A0), WiFi.RSSI());
mqtt.publish(topicTelemetry, payload);
// da tiempo a la pila para enviar el paquete antes de cortar la radio
for (int i = 0; i < 10; i++) { mqtt.loop(); delay(10); }
mqtt.disconnect();
}
ESP.deepSleep(5 * 60 * 1000000ULL); // 5 minutos, en microsegundos
}
Llama a publishAndSleep() al final de setup() y deja loop() vacío. Algunas notas prácticas: un handshake TLS en cada despertar cuesta bastante más tiempo y energía que MQTT sin cifrar, así que sopesa el requisito de seguridad frente a la autonomía y plantéate intervalos de sueño más largos con TLS. Usa un retained message para la última lectura, de modo que los dashboards que se abran entre despertares sigan viendo un valor. Y no uses un Last Will para la presencia en dispositivos que duermen: un disconnect() limpio nunca lo dispara y, de todos modos, un estado "offline" sería engañoso.
Solución de problemas de ESP8266 MQTT
- rc=-2 con TLS pero todo bien en el 1883: reloj sin sincronizar, certificado raíz incorrecto o heap contiguo insuficiente. Imprime
tlsClient.getLastSSLError()para ver el motivo de BearSSL. Nuestra guía de PubSubClient rc=-2 cubre todos los códigos. - Reinicios aleatorios (
wdt reset): algo bloquea demasiado tiempo. No pongas nunca llamadas largas adelay()en el callback de MQTT y llama ayield()en los bucles largos. - La placa nunca se une al Wi-Fi: red solo de 5 GHz, contraseña incorrecta o un portal cautivo en una red de invitados.
- La placa se desconecta cada vez que se conecta una segunda placa: ambas usan el mismo client ID. El enfoque basado en el chip ID evita este problema.
- Los mensajes de más de unos cientos de bytes desaparecen: el buffer por defecto de 256 bytes de PubSubClient. Auméntalo con moderación.
- Los mensajes dejan de llegar al cabo de horas: fragmentación del heap. Vigila
getMaxFreeBlockSize()a lo largo del tiempo.
¿ESP8266 o ESP32?
El ESP8266 es perfectamente capaz para nodos de un solo sensor, relés y pantallas pequeñas, sobre todo con MQTT sin cifrar o conexiones TLS ocasionales. Si necesitas muchas sesiones TLS simultáneas, Bluetooth, más GPIO o espacio para actualizaciones OTA además de payloads grandes, pasa al ESP32. Nuestra comparativa ESP32 vs ESP8266 repasa las ventajas e inconvenientes, y el tutorial de IoT con ESP32 es el siguiente paso cuando esta placa se te quede pequeña.
Lleva tu flota de ESP8266 a ConnectMQ
Cuando el prototipo funcione, pásalo a un broker MQTT en la nube con credenciales privadas y TLS. ConnectMQ admite MQTT 3.1.1 y 5.0 sobre TCP 1883 y TLS 8883, y el portal incluye un cliente MQTT en el navegador y un explorador de topics en vivo para comprobar lo que publican tus placas. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito.
Preguntas frecuentes
¿Puede el ESP8266 usar MQTT sobre TLS?
Sí. El núcleo del ESP8266 incluye BearSSL, y PubSubClient funciona con BearSSL::WiFiClientSecure. Sincroniza primero el reloj, confía en el certificado raíz del broker y mantén moderado el resto del uso de memoria.
¿Qué pin usa el LED integrado del NodeMCU?
En la mayoría de las placas NodeMCU y Wemos D1 mini es GPIO2 (LED_BUILTIN, también rotulado como D4), y es activo a nivel bajo, así que escribir LOW lo enciende.
¿Puedo usar el ESP8266 con MQTT 5?
PubSubClient solo admite MQTT 3.1.1, que basta para la mayoría de los sensores. El broker puede seguir atendiendo a clientes MQTT 5, como dashboards y backends, en los mismos topics.
¿Por qué necesita la hora mi ESP8266 para TLS?
BearSSL comprueba que el certificado del servidor esté dentro de su periodo de validez. Sin un reloj correcto, el dispositivo cree que está en 1970 y rechaza el certificado.



