/*
* esp32_mqtt_led_dth22.c
* ========================
*
* Firmware para ESP32 que:
* 1) Lee temperatura y humedad de un sensor DHT22.
* 2) Publica esas lecturas por MQTT cada 5 segundos.
* 3) Escucha comandos MQTT ("ON" / "OFF") para encender o apagar un LED.
* 4) Confirma el estado real del LED publicando en otro tópico.
*
* No se conecta directamente con la PC: tanto este firmware como el
* cliente Python (ver cliente_iot.py) se conectan a un broker MQTT
* (un servidor intermediario) y se comunican publicando/suscribiéndose
* a "tópicos" (canales con nombre). Ver el README.md del proyecto para
* un diagrama y la tabla completa de tópicos usados.
*
* CONFIGURACIÓN REQUERIDA ANTES DE COMPILAR:
* Este archivo NO contiene ningún usuario/contraseña. Las credenciales
* viven en "secrets.h", un archivo que vos creás localmente y que NUNCA
* se sube al repositorio (está en .gitignore). Pasos:
* 1) Copiá "secrets.h.example" y renombrá la copia a "secrets.h".
* 2) Completá tus propios datos (ver instrucciones dentro del archivo
* y en el README.md, sección "Configuración de credenciales").
*/
#include <WiFi.h>
#include <DHTesp.h> // Librería para manejar el sensor DHT22
#include <WiFiClientSecure.h>
#include <PubSubClient.h> // Cliente MQTT para Arduino/ESP32
#include "secrets.h" // Credenciales locales (WIFI_*, MQTT_*) — no versionado
// GPIO para el LED y el sensor
#define PIN_LED 2
#define PIN_DHT 42
// MQTT topics
// IMPORTANTE: estos nombres deben ser IDÉNTICOS a los que usa
// cliente_iot.py, o los dos programas "no se van a escuchar" entre sí.
#define TOPIC_LED "esp32/led" // Comandos ON/OFF que recibimos
#define TOPIC_LED_STATUS "esp32/led/status" // Estado real que nosotros publicamos
#define TOPIC_PUBLISH_TEMPERATURE "topic_sensor_temperature"
#define TOPIC_PUBLISH_HUMIDITY "topic_sensor_humidity"
// --- Configuración de WiFi y del broker MQTT ---
// Todos estos valores se definen en secrets.h (ver arriba). Acá solo los
// tomamos y los guardamos en variables con el tipo que espera cada librería.
//
// NOTA: el puerto 1883 no usa cifrado (TLS). Para producción real conviene
// migrar a un broker con TLS (puerto 8883) y WiFiClientSecure — ver el
// bloque de ejemplo comentado en secrets.h.example.
const char* ssid = WIFI_SSID;
const char* password = WIFI_PASSWORD;
const char* mqtt_broker = MQTT_BROKER;
const int mqtt_port = MQTT_PORT;
const char* mqtt_username = MQTT_USERNAME;
const char* mqtt_password = MQTT_PASSWORD;
// Buffers para formatear los valores del sensor como texto antes de
// enviarlos por MQTT (los mensajes MQTT en este proyecto son texto plano,
// no números binarios).
static char strTemperature[10] = {0};
static char strHumidity[10] = {0};
// Guardamos el estado del LED en una variable propia (además del pin
// físico) para poder volver a publicarlo cada vez que nos reconectamos al
// broker, sin depender de leer el pin de vuelta.
static bool ledState = true; // Estado inicial del LED (HIGH)
// WiFiClientSecure espClient; // Se usaría junto con el puerto 8883 (TLS)
WiFiClient espClient; // Conexión de red "cruda", sin cifrar (puerto 1883)
PubSubClient mqttClient(espClient); // El cliente MQTT se apoya sobre la conexión de red anterior
DHTesp dht;
/* Conecta el ESP32 a la red WiFi configurada arriba.
Se queda esperando (bloqueando) hasta lograr la conexión. */
void connectWiFi() {
Serial.println("Conectando a WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi conectado");
Serial.println("Dirección IP: ");
Serial.println(WiFi.localIP());
}
/* Configura el cliente MQTT: a qué servidor conectarse y qué función
("callback") llamar cuando llegue un mensaje nuevo. */
void setupMQTT() {
mqttClient.setServer(mqtt_broker, mqtt_port);
mqttClient.setCallback(mqttCallback);
}
/* Callback de mensajes entrantes.
La librería PubSubClient llama a esta función automáticamente cada vez
que llega un mensaje a alguno de los tópicos a los que estamos
suscritos (en este firmware, solo TOPIC_LED). No la llamamos nosotros
directamente en ningún lado del código. */
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("]: ");
// El payload llega como un arreglo de bytes sin terminador, hay que
// armar el String manualmente byte a byte.
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// Interpretamos el comando recibido y actuamos sobre el LED físico.
if (message == "ON") {
Serial.println("Turning on LED");
digitalWrite(PIN_LED, HIGH);
ledState = true;
// Publicamos la confirmación en un tópico DISTINTO al que escuchamos,
// para que el cliente Python sepa que el cambio realmente ocurrió
// (y no solo que se envió el comando).
mqttClient.publish(TOPIC_LED_STATUS, "ON");
} else if (message == "OFF") {
Serial.println("Turning off LED");
digitalWrite(PIN_LED, LOW);
ledState = false;
mqttClient.publish(TOPIC_LED_STATUS, "OFF");
}
}
/* Se asegura de que haya conexión con el broker MQTT, reintentando cada
5 segundos si hace falta. Se llama desde loop() antes de cada ciclo. */
void reconnect() {
Serial.println("Connecting to MQTT Broker...");
while (!mqttClient.connected()) {
Serial.println("Reconnecting to MQTT Broker...");
// Cada cliente MQTT necesita un identificador único frente al broker.
// Le agregamos un número aleatorio para evitar colisiones si hay más
// de un ESP32 (o reconexiones) usando el mismo prefijo.
String clientId = "ESP32Client-";
clientId += String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str(), mqtt_username, mqtt_password)) {
Serial.println("Connected to MQTT Broker.");
// Nos suscribimos solo al tópico de comandos. No hace falta
// suscribirnos a TOPIC_LED_STATUS: ese lo publicamos nosotros mismos,
// suscribirnos sería escucharnos a nosotros mismos sin necesidad.
mqttClient.subscribe(TOPIC_LED);
// Publicamos el estado actual del LED ahora que la conexión es real.
// (Si esto se hiciera en setup() antes de conectar, el mensaje se
// perdería silenciosamente porque todavía no habría nadie del otro
// lado para recibirlo).
mqttClient.publish(TOPIC_LED_STATUS, ledState ? "ON" : "OFF");
} else {
Serial.print("Failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
connectWiFi();
// Configuramos el pin del LED como salida y lo encendemos por defecto.
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, HIGH);
ledState = true;
// El estado inicial se publicará recién en reconnect(), una vez que la
// conexión MQTT exista de verdad (ver comentario más arriba).
// Inicializa el sensor de temperatura/humedad
dht.setup(PIN_DHT, DHTesp::DHT22);
// Initialize secure WiFiClient
// espClient.setInsecure(); // Necesario solo si se usa WiFiClientSecure (TLS)
setupMQTT();
}
void loop() {
// mqttClient.loop() debe llamarse constantemente para que la librería
// pueda procesar mensajes entrantes y mantener viva la conexión. Si no
// hay conexión, primero reconectamos.
if (!mqttClient.connected()) {
reconnect();
}
mqttClient.loop();
// Publicación de temperatura/humedad cada 5 segundos.
// Usamos millis() (tiempo transcurrido desde que arrancó la placa) en
// vez de delay() largo, para no bloquear mqttClient.loop() ni la
// recepción de comandos del LED mientras esperamos.
static unsigned long lastPublishTime = 0;
if (millis() - lastPublishTime > 5000) {
lastPublishTime = millis();
// Una sola lectura del DHT22 por ciclo: el sensor necesita ~2
// segundos entre lecturas para funcionar bien, así que leer
// temperatura y humedad por separado (dos llamadas seguidas) podía
// hacer que la segunda lectura devolviera NaN ("Not a Number").
TempAndHumidity data = dht.getTempAndHumidity();
float temperature = isnan(data.temperature) ? -99.99 : data.temperature;
float humidity = isnan(data.humidity) ? -99.99 : data.humidity;
// MQTT en este proyecto viaja como texto plano, así que convertimos
// los números a string antes de publicarlos.
sprintf(strTemperature, "%.2fC", temperature);
sprintf(strHumidity, "%.2f", humidity);
mqttClient.publish(TOPIC_PUBLISH_TEMPERATURE, strTemperature);
mqttClient.publish(TOPIC_PUBLISH_HUMIDITY, strHumidity);
}
delay(10); // Pequeña pausa para no saturar el procesador
}