#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 — 1000Hz is clearly audible to the human ear
#define BUZZ_RESOLUTION 8 // PWM resolution in bits — 8 bits gives 256 levels
float USER_THRESHOLD = 30.0; // Default temperature threshold in Celsius
// ─── MEMORY STATE TRACKER ────────────────────────────────────────
bool lastBuzzerState = false; // Remembers if the buzzer was ON or OFF during the last loop cycle to prevent log spamming
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
void buzzerOn() {
ledcWriteTone(BUZZERPIN, BUZZ_FREQ); // generate 1000Hz 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
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) {
USER_THRESHOLD = message.toFloat(); // Convert string to float and update threshold
Serial.print("Threshold updated to: ");
Serial.println(USER_THRESHOLD); // Confirm new threshold value in serial monitor
}
if (String(topic) == mqtt_topic_buzzer) { // Listen for remote override commands from Node-RED
if (message == "ON") {
buzzerOn();
lastBuzzerState = true; // Sync our memory tracking variable to true
Serial.println("🚨 REMOTE COMMAND: Buzzer turned ON via Node-RED");
} else if (message == "OFF") {
buzzerOff();
lastBuzzerState = false; // Sync our memory tracking variable to false
Serial.println("🤫 REMOTE COMMAND: Buzzer turned OFF via Node-RED");
}
}
}
void connectMQTT() {
if (client.connected()) return; // Already connected, exit immediately
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX); // Unique client ID
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);
buzzerOff(); // make sure buzzer starts silent at boot
dht.begin(); // Initialize DHT22 sensor
delay(2500); // Wait 2.5 seconds for DHT22 to stabilize before first read
connectWiFi(); // Connect to WiFi network
client.setServer(mqtt_server, 1883); // Set MQTT broker address and port
client.setCallback(mqttCallback); // Register mqttCallback
connectMQTT(); // Connect to MQTT broker and subscribe to topics
}
void loop() {
client.loop(); // Keeps MQTT connection alive and checks for incoming subscription data
if (!client.connected()) {
connectMQTT(); // Attempt to reconnect immediately if connection dropped
}
float p_sensorTemp = dht.readTemperature(); // Pull current temperature reading from sensor hardware
float p_sensorHumidity = dht.readHumidity(); // Pull current humidity reading from sensor hardware
// Retry once if first read fails
if (isnan(p_sensorTemp) || isnan(p_sensorHumidity)) {
delay(2000);
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; // Evaluate if current temp exceeds allowed user threshold limit
// ─── INSTANT LOCAL HARDWARE CONTROL & LOGGING ────────────────
if (b_alert) {
buzzerOn(); // Turn on buzzer immediately right here on the hardware chip!
if (lastBuzzerState == false) { // Print only once when threshold is breached
Serial.println("Buzzer ON");
lastBuzzerState = true; // Lock memory state
}
} else {
buzzerOff(); // Silence buzzer immediately right here on the hardware chip!
if (lastBuzzerState == true) { // Print only once when system goes back to safe parameters
Serial.println("Buzzer OFF");
lastBuzzerState = false; // Reset memory state
}
}
// Send regular sensor data packet up to Node-RED backend architecture
char sensorPayload[200];
sprintf(sensorPayload, "{\"sensor_temp\":%.1f,\"sensor_humidity\":%.1f,\"threshold\":%.1f}",
p_sensorTemp, p_sensorHumidity, USER_THRESHOLD);
client.publish(mqtt_topic_sensor, sensorPayload); // Publish JSON payload to live sensor topic channel
Serial.println(sensorPayload); // Keep your regular print statement completely intact
// Only publish alert message when threshold is exceeded
if (b_alert) {
char alertPayload[300];
sprintf(alertPayload, "{\"alert\":true,\"sensor_temp\":%.1f,\"threshold\":%.1f,\"message\":\"ALERT: Sensor temp %.1f C exceeds threshold %.1f C\"}",
p_sensorTemp, USER_THRESHOLD, p_sensorTemp, USER_THRESHOLD);
client.publish(mqtt_topic_alert, alertPayload); // Publish to alert topic channel
Serial.println(alertPayload); // Keep your regular print statement completely intact
}
} else {
Serial.println("DHT22 read failed");
}
delay(2050); // Your original 5-second interval delay is fully preserved here
}