#include <WiFi.h> // Library to connect ESP32 to WiFi
#include <PubSubClient.h> // Library to connect ESP32 to MQTT broker
#include <DHT.h> // Library to read temperature and humidity from DHT22 sensor
const char* ssid = "Wokwi-GUEST"; // WiFi network name
const char* password = ""; // WiFi password, empty for Wokwi
const char* mqtt_server = "broker.hivemq.com"; // Public MQTT broker address
const char* mqtt_topic_alert = "alert/threshold"; // ESP32 publishes alert here when threshold is exceeded
const char* mqtt_topic_sensor = "alert/sensor"; // ESP32 publishes live sensor readings here every 5 seconds
const char* mqtt_topic_threshold = "alert/set"; // Node-RED publishes new threshold value here when slider moves
const char* mqtt_topic_buzzer = "alert/buzzer"; // Node-RED publishes ON or OFF here to control buzzer remotely
#define DHTPIN 26 // GPIO pin connected to DHT22 data pin
#define DHTTYPE DHT22 // Sensor type
DHT dht(DHTPIN, DHTTYPE); // Create DHT sensor object
#define BUZZERPIN 15 // GPIO pin connected to buzzer positive leg
#define BUZZ_FREQ 1000 // Buzzer frequency in Hz — 2000Hz is clearly audible to the human ear
// Human hearing range is 20Hz to 20000Hz, 1000-4000Hz is most sensitive
#define BUZZ_RESOLUTION 8 // PWM resolution in bits — 8 bits gives 256 levels
// Controls how precisely the duty cycle can be set
float USER_THRESHOLD = 30.0; // Default temperature threshold in Celsius
// Node-RED dashboard slider updates this live via MQTT
WiFiClient espClient; // Creates a WiFi client object for network communication
PubSubClient client(espClient); // Creates an MQTT client using the WiFi client
// buzzerOn() generates a PWM tone at BUZZ_FREQ on the buzzer pin
// uses new ESP32 Arduino core API — ledcWriteTone generates frequency directly on the pin
void buzzerOn() {
ledcWriteTone(BUZZERPIN, BUZZ_FREQ); // generate 2000Hz tone on pin 15 — audible alert
}
// buzzerOff() stops the PWM signal and silences the buzzer
void buzzerOff() {
ledcWriteTone(BUZZERPIN, 0); // 0Hz stops the tone completely — buzzer goes silent
}
void connectWiFi() {
WiFi.begin(ssid, password); // Start connecting to WiFi
while (WiFi.status() != WL_CONNECTED) { // Keep looping until connected
delay(1000); // Wait 1 second between each check
Serial.println("Connecting to WiFi..."); // Print status to serial monitor
}
Serial.println("WiFi Connected"); // Confirm connection
}
// mqttCallback fires automatically every time a message arrives on a subscribed topic
// topic = which topic the message came from
// payload = the message content as raw bytes
// length = how many bytes the message contains
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message = "";
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i]; // Convert each byte to a character and build the full message string
}
if (String(topic) == mqtt_topic_threshold) {
// Node-RED dashboard slider sent a new threshold value
USER_THRESHOLD = message.toFloat(); // Convert string to float and update threshold
Serial.print("Threshold updated to: ");
Serial.println(USER_THRESHOLD); // Confirm new threshold in serial monitor
}
if (String(topic) == mqtt_topic_buzzer) {
// Node-RED sent a remote buzzer command
if (message == "ON") {
buzzerOn(); // Generate 2000Hz tone — audible alert
Serial.println("Buzzer ON");
} else if (message == "OFF") {
buzzerOff(); // Stop tone — buzzer silent
Serial.println("Buzzer OFF");
}
}
}
void connectMQTT() {
if (client.connected()) return; // Already connected, exit immediately
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX); // Random unique client ID to avoid broker conflicts
if (client.connect(clientId.c_str())) {
Serial.println("Connected to MQTT broker");
client.subscribe(mqtt_topic_threshold); // Listen for threshold updates from Node-RED slider
client.subscribe(mqtt_topic_buzzer); // Listen for buzzer ON/OFF commands from Node-RED
} else {
Serial.print("Failed, state: ");
Serial.println(client.state()); // Print error code if connection failed
}
}
void setup() {
Serial.begin(115200); // Start serial monitor at 115200 baud rate
// Setup buzzer using new ESP32 Arduino core LEDC API
ledcAttach(BUZZERPIN, BUZZ_FREQ, BUZZ_RESOLUTION); // attach pin 15, set 2000Hz frequency, 8 bit resolution
// new API combines ledcSetup and ledcAttachPin into one call
buzzerOff(); // make sure buzzer starts silent at boot
dht.begin(); // Initialize DHT22 sensor
delay(2500); // Wait 3 seconds for DHT22 to stabilize before first read
connectWiFi(); // Connect to WiFi
client.setServer(mqtt_server, 1883); // Set MQTT broker address and port
client.setCallback(mqttCallback); // Register mqttCallback so it fires when messages arrive
connectMQTT(); // Connect to MQTT broker and subscribe to topics
}
void loop() {
client.loop(); // Must be called every loop to keep MQTT alive and trigger mqttCallback
// Without this the ESP32 never receives threshold or buzzer commands from Node-RED
if (!client.connected()) {
connectMQTT(); // Reconnect only if connection dropped
}
float p_sensorTemp = dht.readTemperature(); // Read temperature from DHT22 in Celsius
float p_sensorHumidity = dht.readHumidity(); // Read humidity from DHT22 in percentage
// Retry once if first read fails — DHT22 sometimes needs a second attempt
if (isnan(p_sensorTemp) || isnan(p_sensorHumidity)) { // isnan returns true if value is not a valid number
delay(2000); // Wait 2 seconds before retrying
p_sensorTemp = dht.readTemperature();
p_sensorHumidity = dht.readHumidity();
}
if (!isnan(p_sensorTemp) && !isnan(p_sensorHumidity)) { // Only proceed if both reads succeeded
bool b_alert = p_sensorTemp > USER_THRESHOLD; // true if sensor temp exceeds user threshold
// false if temperature is safe
// Trigger buzzer immediately on ESP32 without waiting for Node-RED
// This ensures instant response even if MQTT is slow
if (b_alert) {
buzzerOn(); // Generate 2000Hz tone — threshold exceeded
} else {
buzzerOff(); // Silence buzzer — temperature is safe
}
// Publish live sensor readings every 5 seconds regardless of alert status
// Node-RED uses this for live gauge and dashboard display
char sensorPayload[200];
sprintf(sensorPayload,
"{\"sensor_temp\":%.1f," // Current temperature as a raw number
"\"sensor_humidity\":%.1f," // Current humidity as a raw number
"\"threshold\":%.1f}", // Current threshold so Node-RED can display it
p_sensorTemp, p_sensorHumidity, USER_THRESHOLD);
client.publish(mqtt_topic_sensor, sensorPayload); // Publish to alert/sensor topic
Serial.println(sensorPayload); // Print to serial monitor
// Only publish alert message when threshold is exceeded
// Node-RED uses this to log the event and send WhatsApp notification
if (b_alert) {
char alertPayload[300];
sprintf(alertPayload,
"{\"alert\":true," // Boolean flag so Node-RED knows this is an alert message
"\"sensor_temp\":%.1f," // Temperature that triggered the alert
"\"threshold\":%.1f," // Threshold that was exceeded
"\"message\":\"ALERT: Sensor temp %.1f C exceeds threshold %.1f C\"}", // Human readable message
p_sensorTemp, USER_THRESHOLD, p_sensorTemp, USER_THRESHOLD);
client.publish(mqtt_topic_alert, alertPayload); // Publish to alert/threshold topic
Serial.println(alertPayload); // Print to serial monitor
}
} else {
Serial.println("DHT22 read failed"); // Both reads failed — sensor likely disconnected
}
delay(2050); // Wait 5 seconds before next read
// DHT22 needs minimum 2 seconds between reads, 5 seconds gives safe margin
}