ESP32 y Arduino

Tutorial ESP32 IoT: del primer sketch a sensores conectados a la nube

Pasa de un ESP32 recién desempaquetado a un sensor de temperatura seguro y conectado a la nube: configuración de Arduino IDE, Wi-Fi, un DHT22, publicación y suscripción MQTT, control remoto de un LED y TLS en el puerto 8883.

Portada del tutorial ESP32 IoT con una placa ESP32 y un sensor DHT22 enviando lecturas a un broker MQTT en la nube
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

Este tutorial ESP32 IoT te lleva de una placa recién desempaquetada a un sensor conectado a la nube que publica temperatura y humedad, acepta comandos remotos y se comunica con la nube por TLS. Configurarás el Arduino IDE, te conectarás al Wi-Fi, leerás un DHT22, publicarás por MQTT en un servidor MQTT público gratuito y después pasarás el mismo sketch a un broker privado y cifrado para un uso real.

Por qué el ESP32 es el microcontrolador IoT por defecto

La familia ESP32 de Espressif combina un procesador rápido de doble núcleo (o RISC-V de un solo núcleo, según la variante), Wi-Fi y Bluetooth integrados, abundantes GPIO, entradas analógicas y soporte de hardware para TLS en un módulo que cuesta unos pocos dólares. Ejecuta código Arduino, MicroPython y el framework ESP-IDF de Espressif, y tiene una comunidad enorme. Para cualquiera que construya un sensor conectado, un controlador de relés o un pequeño gateway, suele ser la primera placa que probar.

Lo que construirás en este tutorial:

  1. Una placa que se une a tu red Wi-Fi y se reconecta automáticamente.
  2. Un sensor DHT22 que publica lecturas JSON cada diez segundos por MQTT.
  3. Un LED controlado a distancia, que se enciende o apaga publicando ON u OFF en un topic de comandos.
  4. Un topic de estado en línea/fuera de línea que usa la función Last Will de MQTT.
  5. Una versión segura con TLS en el puerto 8883 y credenciales privadas.

Qué necesitas

Elemento Notas
Placa de desarrollo ESP32 Sirve cualquier ESP32 DevKit, ESP32-S3 o ESP32-C3 con USB
Sensor DHT22 (AM2302) Lo más fácil es un módulo con resistencia pull-up integrada
Cables Dupont y protoboard Tres cables para el sensor
Cable USB Debe ser de datos, no solo de carga
Ordenador con Arduino IDE 2.x Windows, macOS o Linux
Clientes de línea de comandos de Mosquitto mosquitto_pub y mosquitto_sub para las pruebas

¿Aún no tienes sensor? El sketch informa de un error y sigue funcionando, así que puedes seguir igualmente las partes de MQTT y del LED.

Paso 1: configura el Arduino IDE para el ESP32

  1. Instala Arduino IDE 2.x desde la web de Arduino.
  2. Abre Tools, Board, Boards Manager, busca esp32 e instala esp32 by Espressif Systems. Si no aparece, añade https://espressif.github.io/arduino-esp32/package_esp32_index.json en File, Preferences, Additional boards manager URLs y vuelve a buscar. La guía oficial de instalación de Arduino-ESP32 incluye capturas para cada plataforma.
  3. Abre Tools, Manage Libraries e instala PubSubClient de Nick O'Leary y DHT sensor library de Adafruit (acepta también la instalación de Adafruit Unified Sensor cuando te lo pida).
  4. Conecta la placa, selecciona tu modelo en Tools, Board (para un DevKit genérico, elige ESP32 Dev Module) y selecciona el puerto serie en Tools, Port.

Prueba la cadena de herramientas con un sketch de parpadeo antes de seguir:

#define LED_PIN 2  // LED integrado en la mayoría de las placas ESP32 DevKit

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(500);
  digitalWrite(LED_PIN, LOW);
  delay(500);
}

Si la carga se queda atascada en "Connecting...", mantén pulsado el botón BOOT de la placa hasta que empiece. Si no aparece ningún puerto, instala el controlador USB-serie del chip de tu placa (a menudo CP210x o CH340) o prueba otro cable. Algunas placas no tienen LED en el GPIO 2; en ese caso, conecta un LED con una resistencia de 220 ohmios al GPIO 2.

Paso 2: conecta el sensor DHT22

Conecta el módulo DHT22 así:

Pin del DHT22 Pin del ESP32
VCC (+) 3V3
DATA (out) GPIO 4
GND (-) GND

Si usas un sensor suelto de cuatro patillas en lugar de un módulo, añade una resistencia pull-up de 10 kilohmios entre DATA y 3V3 y deja la tercera patilla sin conectar. Evita los GPIO 6 a 11, reservados para la memoria flash en los módulos ESP32 clásicos, y evita para los sensores los pines de arranque (strapping) como los GPIO 0, 2, 12 y 15. El DHT22 no debe leerse más de una vez cada dos segundos.

Paso 3: entiende las piezas de MQTT

MQTT es un protocolo de publicación/suscripción. Tu ESP32 no envía datos a una aplicación de servidor concreta; publica mensajes en topics de un broker, y cualquier cliente suscrito a esos topics los recibe. Si quieres todo el contexto, lee primero qué hace un broker MQTT.

En este tutorial ESP32 IoT usamos el servidor MQTT público gratuito en broker.connectmq.com, pensado para pruebas y prototipos, no para datos de producción. Acepta conexiones anónimas en el puerto 1883 siempre que cada cliente use un client ID único y no vacío. Cualquiera puede leer todo lo que se publica en el área pública, así que nunca envíes contraseñas ni datos personales, y usa un prefijo de topic único para que tus mensajes no choquen con los de otros. El área pública permite 64 KB por mensaje, 10 mensajes por segundo y 10 suscripciones por cliente, mucho más de lo que necesita un sensor.

Nuestros topics, con k3v9 sustituido por tu propia cadena aleatoria:

connectmq/demo/esp32-k3v9/telemetry   lecturas JSON, publicadas cada 10 s
connectmq/demo/esp32-k3v9/status      "online" / "offline" (retenido, Last Will)
connectmq/demo/esp32-k3v9/led/set     comandos: ON u OFF
connectmq/demo/esp32-k3v9/led/state   estado actual del LED (retenido)

Paso 4: el sketch ESP32 IoT completo

Crea un sketch nuevo, pega el código siguiente y configura el nombre y la contraseña de tu Wi-Fi y el prefijo de topic.

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

// ---------- configuración ----------
const char* WIFI_SSID     = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* TOPIC_PREFIX = "connectmq/demo/esp32-k3v9";  // cambia k3v9 por tu propia cadena

#define DHT_PIN 4
#define DHT_TYPE DHT22
#define LED_PIN 2
const unsigned long PUBLISH_INTERVAL_MS = 10000;
// -----------------------------------

DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient net;
PubSubClient mqtt(net);

String clientId;
String topicTelemetry, topicStatus, topicLedSet, topicLedState;
unsigned long lastPublish = 0;
unsigned long lastReconnectAttempt = 0;

void connectWiFi() {
  if (WiFi.status() == WL_CONNECTED) return;
  Serial.printf("Connecting to Wi-Fi %s", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < 20000) {
    delay(500);
    Serial.print(".");
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\nWi-Fi connected, IP %s\n", WiFi.localIP().toString().c_str());
  } else {
    Serial.println("\nWi-Fi connection failed, will retry");
  }
}

void setLed(bool on) {
  digitalWrite(LED_PIN, on ? HIGH : LOW);
  mqtt.publish(topicLedState.c_str(), on ? "ON" : "OFF", true);  // estado retenido
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  String message;
  for (unsigned int i = 0; i < length; i++) message += (char)payload[i];
  message.trim();
  Serial.printf("Received [%s] %s\n", topic, message.c_str());

  if (topicLedSet == topic) {
    if (message == "ON") setLed(true);
    else if (message == "OFF") setLed(false);
  }
}

bool connectMqtt() {
  Serial.printf("Connecting to MQTT %s:%u as %s\n", MQTT_HOST, MQTT_PORT, clientId.c_str());
  // Last Will: el broker publica "offline" (retenido) si desaparecemos
  bool ok = mqtt.connect(clientId.c_str(), topicStatus.c_str(), 1, true, "offline");
  if (ok) {
    Serial.println("MQTT connected");
    mqtt.publish(topicStatus.c_str(), "online", true);
    mqtt.subscribe(topicLedSet.c_str(), 1);
  } else {
    Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
  }
  return ok;
}

void publishReading() {
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  if (isnan(temperature) || isnan(humidity)) {
    Serial.println("DHT22 read failed, check wiring");
    mqtt.publish(topicTelemetry.c_str(), "{\"error\":\"sensor_read_failed\"}");
    return;
  }
  char payload[128];
  snprintf(payload, sizeof(payload),
           "{\"temperature\":%.2f,\"humidity\":%.1f,\"rssi\":%d,\"uptime\":%lu}",
           temperature, humidity, WiFi.RSSI(), millis() / 1000);
  mqtt.publish(topicTelemetry.c_str(), payload);
  Serial.printf("Published %s\n", payload);
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  dht.begin();

  // Client ID único derivado de la dirección MAC del chip
  uint64_t mac = ESP.getEfuseMac();
  char id[32];
  snprintf(id, sizeof(id), "esp32-%04X%08X", (uint16_t)(mac >> 32), (uint32_t)mac);
  clientId = id;

  topicTelemetry = String(TOPIC_PREFIX) + "/telemetry";
  topicStatus    = String(TOPIC_PREFIX) + "/status";
  topicLedSet    = String(TOPIC_PREFIX) + "/led/set";
  topicLedState  = String(TOPIC_PREFIX) + "/led/state";

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

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

  if (!mqtt.connected()) {
    unsigned long now = millis();
    if (now - lastReconnectAttempt > 5000) {  // reintento no bloqueante cada 5 s
      lastReconnectAttempt = now;
      if (connectMqtt()) lastReconnectAttempt = 0;
    }
    return;
  }

  mqtt.loop();

  if (millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
    lastPublish = millis();
    publishReading();
  }
}

Sube el sketch y abre el Serial Monitor a 115200 baudios. Deberías ver la conexión Wi-Fi, la conexión MQTT y una lectura publicada cada diez segundos.

Cómo está estructurado el sketch

  • Reconexiones no bloqueantes. El bucle nunca se queda esperando en un ciclo de reintentos cerrado. Si cae el Wi-Fi o MQTT, reintenta cada pocos segundos mientras el resto del código sigue respondiendo. Los bucles bloqueantes largos son una causa habitual de reinicios por watchdog; la guía de reconexión MQTT en ESP32 explica estrategias más robustas.
  • Last Will y estado retenido. El broker guarda online como estado retenido y lo sustituye automáticamente por offline si la placa pierde la alimentación o la conectividad sin una desconexión limpia.
  • Estado del LED retenido. Cualquier dashboard que se suscriba más tarde sabe de inmediato si el LED está encendido.
  • Payloads pequeños. El buffer de paquetes por defecto de PubSubClient es de 256 bytes, en los que este payload cabe con holgura. Para documentos JSON más grandes, llama a mqtt.setBufferSize(1024) antes de conectar, como se explica en tamaño del buffer de PubSubClient.

Paso 5: prueba desde tu ordenador

Observa todo lo que publica tu placa:

mosquitto_sub -h broker.connectmq.com -p 1883 -i laptop-k3v9-sub -t "connectmq/demo/esp32-k3v9/#" -v

Deberías ver de inmediato los mensajes retenidos status y led/state, y después telemetría cada diez segundos:

connectmq/demo/esp32-k3v9/status online
connectmq/demo/esp32-k3v9/telemetry {"temperature":23.40,"humidity":48.2,"rssi":-58,"uptime":42}

Ahora enciende y apaga el LED desde un segundo terminal:

mosquitto_pub -h broker.connectmq.com -p 1883 -i laptop-k3v9-pub -t "connectmq/demo/esp32-k3v9/led/set" -m ON
mosquitto_pub -h broker.connectmq.com -p 1883 -i laptop-k3v9-pub -t "connectmq/demo/esp32-k3v9/led/set" -m OFF

El LED cambia y aparece un nuevo mensaje led/state en el suscriptor. Desenchufa la placa, espera unos 45 segundos (1,5 veces el keepalive) y el broker publicará offline en el topic de estado. Ese es tu ciclo IoT completo: medición, publicación, control remoto y detección de presencia.

No publiques comandos con el flag de retención (-r). Un comando retenido se volvería a entregar cada vez que el ESP32 se reconectara.

Paso 6: pasa a un broker privado con TLS

El servidor público es perfecto para aprender, pero un dispositivo real necesita privacidad, autenticación y cifrado. Con ConnectMQ obtienes un broker privado con credenciales generadas y un certificado de Let's Encrypt en el puerto 8883.

  1. Regístrate en el portal de ConnectMQ, elige el plan Serverless gratuito, crea un broker y copia el nombre de usuario, la contraseña y el client ID generados.
  2. Descarga el certificado raíz de Let's Encrypt (ISRG Root X1) del que depende la cadena del certificado del broker:
curl -o isrgrootx1.pem https://letsencrypt.org/certs/isrgrootx1.pem
  1. Cambia la parte superior del sketch como sigue. Solo cambian los includes, la configuración, el objeto cliente y la llamada de conexión; todo lo demás sigue igual.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <DHT.h>

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 8883;
const char* MQTT_USERNAME  = "YOUR_USERNAME";
const char* MQTT_PASSWORD  = "YOUR_PASSWORD";
const char* MQTT_CLIENT_ID = "YOUR_CLIENT_ID";
const char* TOPIC_PREFIX = "home/office/esp32-01";

// Pega el contenido completo de isrgrootx1.pem entre los marcadores
const char* ROOT_CA = R"EOF(
-----BEGIN CERTIFICATE-----
...paste the certificate body from isrgrootx1.pem here...
-----END CERTIFICATE-----
)EOF";

WiFiClientSecure net;
PubSubClient mqtt(net);

En setup(), sustituye el client ID basado en la MAC por el del portal y carga el certificado antes de conectar:

  clientId = MQTT_CLIENT_ID;  // debe coincidir con el client ID emitido junto con tus credenciales
  net.setCACert(ROOT_CA);

Y en connectMqtt(), pasa las credenciales junto con el Last Will:

  bool ok = mqtt.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASSWORD,
                         topicStatus.c_str(), 1, true, "offline");

Vuelve a subir el sketch. El Serial Monitor debería mostrar una conexión correcta en el puerto 8883. Verifícalo desde tu ordenador con las mismas credenciales, o abre en el portal el cliente MQTT del navegador y el explorador de topics en tiempo real para ver llegar las lecturas. Ten en cuenta que ConnectMQ vincula los client IDs a las credenciales que emite, así que cada dispositivo necesita su propio juego de credenciales; reutilizar uno en dos placas hará que se desconecten mutuamente. El tutorial de ESP32 MQTT sobre SSL trata con más profundidad la gestión de certificados, incluido por qué setInsecure() nunca debe llegar a producción.

Paso 7: prepara el sensor para el mundo real

Un sketch que funciona en tu mesa durante una tarde no es lo mismo que un dispositivo que funciona sin supervisión durante un año. Antes de instalar la placa en algún sitio de difícil acceso, repasa esta lista:

  1. Alimentación estable. Usa una fuente de 5 V de calidad de al menos 500 mA. La transmisión Wi-Fi provoca picos de corriente, y los adaptadores baratos causan reinicios por caída de tensión que parecen errores de software.
  2. Ubicación del sensor. Mantén el DHT22 alejado del propio ESP32 y de los reguladores de tensión, que se calientan y desvían las lecturas un grado o más. Un cable corto y una caja ventilada ayudan.
  3. Un juego de credenciales por dispositivo. Nunca copies el mismo usuario, contraseña y client ID en varias placas. Emite un juego por dispositivo para poder revocar uno sin tocar los demás.
  4. Marcas de tiempo reales. El campo uptime es útil para depurar, pero el análisis necesita la hora real. Sincroniza el reloj por NTP al arrancar y añade una marca de tiempo epoch a cada lectura, como se muestra en marcas de tiempo NTP en ESP32 para MQTT.
  5. Actualizaciones remotas. Subirse a una escalera para reflashear un sensor cansa pronto. Planifica desde el principio las actualizaciones por aire con actualizaciones OTA del ESP32 por MQTT.
  6. Configuración sin recompilar. Las credenciales Wi-Fi escritas en el código valen para una placa. Para varias, usa una librería de portal cautivo para que cada dispositivo pueda configurarse in situ.
  7. Informes de salud. Sigue publicando el RSSI y el uptime. Una intensidad de señal que baja poco a poco o los reinicios frecuentes son avisos tempranos de que un dispositivo pronto se desconectará.

Ninguno de estos pasos cambia la lógica MQTT que ya has escrito. Marcan la diferencia entre una demo y un sensor del que puedes olvidarte porque simplemente sigue informando.

Resolución de problemas

Si mqtt.state() devuelve un valor distinto de cero, esta tabla suele indicar la causa:

state() Significado Solución típica
-4 Tiempo de conexión agotado Revisa la red, el firewall, el número de puerto y el keepalive
-2 Falló la conexión TCP o TLS Host o puerto incorrectos, falta el certificado de la CA, Wi-Fi no preparado
-3 / -1 Conexión perdida / desconectado Client ID duplicado, Wi-Fi inestable, código bloqueante en el loop
2 Client ID rechazado Client ID vacío o incorrecto; usa el emitido con tus credenciales
4 Usuario o contraseña incorrectos Vuelve a copiar las credenciales del portal y revisa si hay espacios
5 No autorizado Las credenciales no coinciden con el client ID o el broker

Otros problemas habituales:

  • Las lecturas son nan. Revisa el cableado del DHT22, la resistencia pull-up y que no leas más de una vez cada dos segundos.
  • La publicación falla sin avisar. El mensaje es mayor que el buffer de PubSubClient; amplíalo con setBufferSize().
  • La placa se reinicia al azar. Alimentación USB insuficiente durante la transmisión Wi-Fi; prueba otro puerto o un hub alimentado.
  • Funciona en el 1883 pero no en el 8883. Falta el certificado de la CA o está incompleto, o la red bloquea el puerto saliente 8883.

Para una lista más larga, consulta el ESP32 no se conecta al broker MQTT.

Próximos pasos

Con esta base puedes construir casi cualquier sensor o actuador conectado. Buenos siguientes pasos:

  1. Sustituye las cadenas construidas a mano por ArduinoJson, como se muestra en ESP32 con ArduinoJson y MQTT.
  2. Cambia el DHT22 por un sensor más preciso con BME280 por MQTT.
  3. Funciona con baterías gracias a deep sleep del ESP32 y MQTT.
  4. Conmuta cargas reales de forma segura con un relé ESP32 controlado por MQTT.
  5. Compara este enfoque con el tutorial ESP32 MQTT, más breve y centrado en el LED, y con la vía en Python de MicroPython umqtt en el ESP32.

Pon tu ESP32 en línea con ConnectMQ

Cuando tu prototipo esté listo para datos reales, dale un hogar privado. Crea una cuenta gratuita en app.connectmq.com con el plan Serverless, sin tarjeta de crédito, crea un broker y graba en tu sketch las credenciales generadas. El plan gratuito admite hasta 25 conexiones, suficientes para una pequeña flota de sensores más un par de dashboards, y cuando se te quede pequeño un plan de broker MQTT gestionado escala a cientos de dispositivos sin cambiar tu código.

Preguntas frecuentes

¿Qué placa ESP32 es la mejor para principiantes?

Un ESP32 DevKit estándar (a menudo etiquetado como ESP32-WROOM-32 o ESP32 Dev Module) es el inicio más fácil, porque casi todos los tutoriales y librerías lo admiten. Las placas ESP32-S3 y ESP32-C3 también funcionan con el código de este tutorial.

¿Necesito una cuenta para seguir este tutorial ESP32 IoT?

No. La primera parte usa el área pública de broker.connectmq.com, que acepta conexiones anónimas con cualquier client ID único y no vacío. Solo necesitas una cuenta cuando pasas a un broker privado con TLS.

¿Por qué mi ESP32 se desconecta continuamente del broker?

Las causas más habituales son dos dispositivos que usan el mismo client ID, código bloqueante que impide que se ejecute mqtt.loop() y una señal Wi-Fi débil. Revisa el valor RSSI en la telemetría y asegúrate de que cada placa tiene su propio client ID.

¿PubSubClient admite MQTT 5?

No. PubSubClient implementa MQTT 3.1.1, totalmente compatible con el broker. Si necesitas funciones de MQTT 5 en el ESP32, usa en su lugar el componente esp-mqtt de ESP-IDF.

¿Es seguro usar setInsecure() para TLS en el ESP32?

Solo para experimentos rápidos. setInsecure() cifra el tráfico pero omite la verificación del servidor, así que no protege frente a la suplantación. Carga el certificado raíz de la CA con setCACert() para cualquier cosa que vaya más allá de una prueba.

Artículos relacionados

Ejemplo de ESP-IDF MQTT: crea un cliente esp-mqtt desde cero

Construye un proyecto ESP-IDF desde una carpeta vacía con el componente esp-mqtt: configuración de Wi-Fi en modo estación, el manejador de eventos MQTT, publicación y suscripción, propiedades de MQTT 5 y TLS con el bundle de certificados.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.