#include <Arduino.h>
#include "DHT.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include <esp_task_wdt.h> // Watchdog timer
// ── WiFi Credentials ───────────────────────────────────────────
#define WIFI_SSID "Wokwi-GUEST" // WiFi network name
#define WIFI_PASSWORD "" // WiFi password (empty for Wokwi)
// ── Mosquitto MQTT Broker Config ───────────────────────────────
#define MQTT_BROKER "test.mosquitto.org" // Public Mosquitto test broker
#define MQTT_PORT 1883 // Default MQTT port
// ── MQTT Topics ────────────────────────────────────────────────
#define TOPIC_TEMP "home/sensor/temperature" // Topic for temperature
#define TOPIC_HUMIDITY "home/sensor/humidity" // Topic for humidity
#define TOPIC_HUMIDIFIER "home/sensor/humidifier" // Topic for humidifier state
// ── Pin Definitions ────────────────────────────────────────────
#define DHTPIN 4 // GPIO pin connected to DHT22 data line
#define DHTTYPE DHT22 // Sensor type: DHT22
#define HUMIDIFIER_PIN 21 // GPIO pin connected to humidifier relay
// ── Timing & Thresholds ────────────────────────────────────────
#define BAUD_RATE 115200 // Serial Monitor baud rate
#define READ_INTERVAL 2000 // Delay between sensor readings (ms)
#define HUMIDITY_LOW 70.0 // Humidity % below which humidifier may trigger
#define HUMIDITY_HIGH 90.0 // Humidity % at which humidifier turns off
#define TRIGGER_DELAY 10000 // Humidity must stay below 70% for 10s to trigger
#define RECONNECT_COOLDOWN 5000 // Min time between WiFi/MQTT reconnect attempts (ms)
#define WIFI_TIMEOUT 15000 // Max time to wait for WiFi at startup (ms)
#define MQTT_TIMEOUT 15000 // Max time to wait for MQTT at startup (ms)
DHT dht(DHTPIN, DHTTYPE); // DHT22 sensor object
WiFiClient espClient; // WiFi client for MQTT
PubSubClient mqtt(espClient); // MQTT client using WiFi
// ── State Variables ────────────────────────────────────────────
bool humidifierOn = false; // Tracks humidifier state (always FALSE on boot)
unsigned long lowHumidityStartTime = 0; // Timestamp when humidity first dropped below 70%
bool lowHumidityTimerActive = false; // Tracks if 10s countdown is active
unsigned long lastReadTime = 0; // Timestamp of last sensor read
unsigned long lastReconnectAttempt = 0; // Timestamp of last WiFi/MQTT reconnect attempt
// ── Generate Unique MQTT Client ID from MAC Address ────────────
String getMQTTClientId() {
// Uses ESP32's unique MAC address to avoid client ID conflicts on broker
return "ESP32_Humidifier_" + String((uint32_t)ESP.getEfuseMac(), HEX);
}
// ── Non-blocking WiFi + MQTT Reconnection with Cooldown ────────
void maintainConnections() {
// Respect cooldown — don't spam reconnect attempts
if (millis() - lastReconnectAttempt < RECONNECT_COOLDOWN) return;
lastReconnectAttempt = millis();
// If WiFi dropped or never connected, attempt reconnect without blocking
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected — attempting reconnect...");
WiFi.disconnect();
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
// Returns immediately — humidifier logic continues uninterrupted
return;
}
// If MQTT dropped (but WiFi is fine), attempt single reconnect without blocking
if (!mqtt.connected()) {
Serial.print("MQTT not connected — attempting reconnect...");
String clientId = getMQTTClientId();
if (mqtt.connect(clientId.c_str())) {
Serial.println(" Reconnected!");
} else {
Serial.print(" Failed. RC=");
Serial.println(mqtt.state());
// Returns immediately — will retry after cooldown
}
}
}
// ── Publish All Sensor Data to MQTT ────────────────────────────
void publishData(float tempC, float humidity, bool humidifierState) {
char buffer[16]; // Buffer to hold converted values as strings
// Publish temperature
snprintf(buffer, sizeof(buffer), "%.2f", tempC);
mqtt.publish(TOPIC_TEMP, buffer);
// Publish humidity
snprintf(buffer, sizeof(buffer), "%.2f", humidity);
mqtt.publish(TOPIC_HUMIDITY, buffer);
// Publish humidifier state as "ON" or "OFF"
mqtt.publish(TOPIC_HUMIDIFIER, humidifierState ? "ON" : "OFF");
Serial.println(">> Data published to MQTT");
}
// ── Humidifier Control Logic (always runs, network-independent) ─
void controlHumidifier(float humidity) {
if (!humidifierOn) {
// Humidifier OFF — watch for humidity dropping below 70%
if (humidity < HUMIDITY_LOW) {
if (!lowHumidityTimerActive) {
// Humidity just dropped below 70% — start 10s countdown
lowHumidityStartTime = millis();
lowHumidityTimerActive = true;
Serial.println(">> Humidity below 70% — starting 10s timer...");
} else {
// Timer already running — check if 10 seconds have passed
unsigned long elapsed = millis() - lowHumidityStartTime;
if (elapsed >= TRIGGER_DELAY) {
// 10s confirmed low humidity — turn humidifier ON
humidifierOn = true;
digitalWrite(HUMIDIFIER_PIN, HIGH);
lowHumidityTimerActive = false; // Reset timer flag
Serial.println(">> 10s elapsed — HUMIDIFIER TURNED ON");
} else {
// Still within 10s window — show countdown
Serial.print(">> Timer running: ");
Serial.print(elapsed / 1000);
Serial.println("s / 10s");
}
}
} else {
// Humidity back above 70% before 10s elapsed — cancel timer
if (lowHumidityTimerActive) {
lowHumidityTimerActive = false;
Serial.println(">> Humidity recovered — 10s timer reset");
}
}
} else {
// Humidifier ON — turn off only when humidity reaches or exceeds 90%
if (humidity >= HUMIDITY_HIGH) {
humidifierOn = false;
digitalWrite(HUMIDIFIER_PIN, LOW);
Serial.println(">> Humidity reached 90% — HUMIDIFIER TURNED OFF");
}
}
}
void setup() {
Serial.begin(BAUD_RATE);
delay(1000);
// ── Watchdog Timer Setup ──────────────────────────────────────
// Arduino core already initializes the WDT — just register our task with it
// Do NOT call esp_task_wdt_init() — that causes "TWDT already initialized" error
esp_task_wdt_add(NULL);
Serial.println("Watchdog timer registered");
// ── Humidifier Pin Setup ──────────────────────────────────────
// On every boot (including post power-cut), humidifier always starts OFF
// ESP32 waits for humidity to drop below 70% fresh — no assumed state
pinMode(HUMIDIFIER_PIN, OUTPUT);
digitalWrite(HUMIDIFIER_PIN, LOW);
Serial.println("Humidifier set to OFF on boot — waiting for sensor readings");
dht.begin(); // Initialize DHT22 sensor
// ── WiFi Connection with 15s Timeout ─────────────────────────
// If WiFi fails (wrong password, no signal), setup() moves on after 15s
// Humidifier will still run — maintainConnections() retries WiFi in background
Serial.print("Connecting to WiFi: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long wifiStart = millis();
while (WiFi.status() != WL_CONNECTED) {
esp_task_wdt_reset(); // Keep watchdog happy while waiting
if (millis() - wifiStart > WIFI_TIMEOUT) {
Serial.println("\nWiFi timeout — continuing without WiFi.");
Serial.println("Humidifier will still run. Retrying WiFi in background.");
break; // Exit — don't block forever
}
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi Connected! IP: " + WiFi.localIP().toString());
}
// ── MQTT Connection with 15s Timeout ─────────────────────────
// Only attempt if WiFi is connected
// If MQTT fails or is slow, setup() moves on after 15s
if (WiFi.status() == WL_CONNECTED) {
mqtt.setServer(MQTT_BROKER, MQTT_PORT);
String clientId = getMQTTClientId();
Serial.print("Connecting to MQTT as: ");
Serial.println(clientId);
unsigned long mqttStart = millis();
while (!mqtt.connected()) {
esp_task_wdt_reset(); // Keep watchdog happy while waiting
if (millis() - mqttStart > MQTT_TIMEOUT) {
Serial.println("MQTT timeout — continuing without MQTT.");
Serial.println("Humidifier will still run. Retrying MQTT in background.");
break; // Exit — don't block forever
}
mqtt.connect(clientId.c_str());
delay(1000);
Serial.print(".");
}
if (mqtt.connected()) Serial.println("\nMQTT Connected!");
}
Serial.println("--------------------------------------------------");
Serial.println("System Ready — DHT22 + Humidifier + MQTT");
Serial.println("Boot behavior: Humidifier OFF, sensor decides state");
Serial.println("--------------------------------------------------");
}
void loop() {
// Reset watchdog — confirms loop is still running healthy
esp_task_wdt_reset();
// Keep WiFi and MQTT alive — non-blocking, won't pause humidifier
maintainConnections();
if (mqtt.connected()) mqtt.loop();
// Only read sensor every READ_INTERVAL ms (non-blocking)
if (millis() - lastReadTime < READ_INTERVAL) return;
lastReadTime = millis();
float humidity = dht.readHumidity(); // Read humidity (%)
float tempC = dht.readTemperature(); // Read temperature (°C)
// Check if sensor reading failed
if (isnan(humidity) || isnan(tempC)) {
Serial.println("ERROR: Failed to read from DHT22! Check wiring.");
return;
}
// Print sensor data and humidifier state to Serial Monitor
Serial.print("Temp: ");
Serial.print(tempC);
Serial.print(" °C | Humidity: ");
Serial.print(humidity);
Serial.print(" % | Humidifier: ");
Serial.println(humidifierOn ? "ON" : "OFF");
// Run humidifier logic (always executes regardless of network state)
controlHumidifier(humidity);
// Publish to MQTT only if connected — skips silently if offline
if (mqtt.connected()) {
publishData(tempC, humidity, humidifierOn);
} else {
Serial.println(">> MQTT offline — skipping publish, humidifier still running");
}
}Loading
esp32-devkit-v1
esp32-devkit-v1