#pragma once
// Include necessary libraries for hardware and communication
#include <WiFi.h> // For WiFi connectivity
#include <PubSubClient.h> // For MQTT communication
#include <Wire.h> // For I2C communication (LCD)
#include <LiquidCrystal_I2C.h> // For LCD display
#include <DHT.h> // For DHT temperature/humidity sensor
#include <time.h> // For time synchronization
#include <esp_system.h> // For ESP32 system functions (restart etc.)
// Pin definitions - mapping hardware components to ESP32 pins
#define BUTTON_PIN 15 // Restart button pin
#define LED1_PIN 19 // Alarm indicator LED
#define LED2_PIN 18 // Status indicator LED
#define DHTPIN 26 // DHT sensor data pin
#define DHTTYPE DHT22 // DHT sensor type (DHT22)
#define BUZZER_PIN 4 // Buzzer for audio alerts
#define GAS_PIN 34 // Gas sensor analog input pin
#define PIR_PIN 27 // PIR motion sensor digital input pin
// Threshold values for alarm triggers
const float TEMP_THRESHOLD_C = 20.0; // Temperature threshold in Celsius
const float TEMP_THRESHOLD_F = 68.0; // Temperature threshold in Fahrenheit
const float HUMIDITY_THRESHOLD = 70.0; // Humidity threshold (%)
const int GAS_THRESHOLD = 900; // Gas level threshold
const unsigned long MOTION_DELAY = 5000;// Motion detection timeout (5 seconds)
// WiFi and MQTT configuration parameters
const char* ssid = "Wokwi-GUEST"; // WiFi network name
const char* password = ""; // WiFi password
const char* mqtt_server = "broker.hivemq.com"; // MQTT broker address
const char* mqtt_topic_pub = "IOT/Warehouse/sensor"; // Topic for sensor data
const char* mqtt_topic_time = "IOT/Warehouse/time"; // Topic for time data
const char* mqtt_topic_sub = "IOT/Warehouse/control"; // Topic for control commands
const char* mqtt_topic_alarm = "IOT/Warehouse/alarm"; // Topic for alarm status
const char* mqtt_topic_lwt = "IOT/Warehouse/status"; // Topic for device status (LWT)
const char* mqtt_topic_motion = "IOT/Warehouse/motion"; // Topic for motion events
// Create hardware/communication objects
WiFiClient espClient; // WiFi client instance
PubSubClient client(espClient); // MQTT client instance
DHT dht(DHTPIN, DHTTYPE); // DHT sensor instance
LiquidCrystal_I2C lcd(0x27, 20, 4); // LCD display instance (I2C addr 0x27, 20x4)
// Global variables to store sensor data
float currentHumidity = 0.0;
float currentTempC = 0.0;
float currentTempF = 0.0;
int currentGasLevel = 0;
// State variables
bool led1State = false, led2State = false;
unsigned long lastMsg = 0, lastDisplayChange = 0, // Timers for periodic actions
alarmStartTime = 0, lastBuzzerToggle = 0,
lastSensorRead = 0;
const long sensorReadInterval = 2000; // Read sensors every 2 seconds
const long publishInterval = 2000, displayInterval = 2000; // Publish/display intervals
int displayState = 0; // Tracks current LCD display mode
char msg[100]; // Buffer for MQTT messages
bool welcomeDisplayed = false, isAlarm = false, // System state flags
motionDetected = false, motionStateChanged = false;
unsigned long lastMotionTime = 0; // Last time motion was detected
int buttonState, lastButtonState = HIGH;// Button debounce variables
unsigned long lastDebounceTime = 0, debounceDelay = 50;
bool restartTriggered = false;
uint8_t dot[] = {0x0E, 0x0A, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}; // Custom LCD character (degree symbol)
// Function prototypes - declare functions before they're used
void blinkAndRestart();
void checkSensors();
void setup_wifi();
int readGasLevel();
void handlePIR();
void displayWelcome();
void displayDateTime();
void displaySensorData();
void callback(char* topic, byte* payload, unsigned int length);
void controlBuzzer(bool enable);
void handleButton();
void reconnect();
// WiFi connection setup
void setup_wifi() {
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password);
int wifiRetry = 0;
while (WiFi.status() != WL_CONNECTED && wifiRetry < 10) { // Retry 10 times
Serial.print(".");
delay(500);
wifiRetry++;
}
if (WiFi.status() == WL_CONNECTED) Serial.println("\nWiFi connected!");
else {
Serial.println("\nWiFi failed! Restarting...");
esp_restart(); // Reboot if WiFi fails
}
}
// Read gas sensor value (analog input)
int readGasLevel() { return analogRead(GAS_PIN); }
// Handle PIR motion sensor detection
void handlePIR() {
bool currentMotion = digitalRead(PIR_PIN) == HIGH; // Read current motion state
if (currentMotion) { // If motion detected
lastMotionTime = millis();
if (!motionDetected) { // New motion detected
motionDetected = true;
motionStateChanged = true;
Serial.println("Motion detected!");
client.publish(mqtt_topic_motion, "detected"); // Publish motion event
}
} else if (motionDetected && millis() - lastMotionTime > MOTION_DELAY) { // Motion stopped
motionDetected = false;
motionStateChanged = true;
Serial.println("Motion stopped");
client.publish(mqtt_topic_motion, "stopped"); // Publish motion stopped
}
}
// Display welcome message on LCD
void displayWelcome() {
if (!welcomeDisplayed) {
lcd.clear();
lcd.print("Hi,Welcome!");
lcd.setCursor(0, 1);
lcd.print("Warehouse1,");
lcd.setCursor(0, 2);
lcd.print("By Group3!");
welcomeDisplayed = true;
delay(2000); // Show for 2 seconds
}
}
// Display current date and time on LCD
void displayDateTime() {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) { // If time is synchronized
char dtbuf[21];
strftime(dtbuf, sizeof(dtbuf), "%Y-%m-%d %H:%M:%S", &timeinfo); // Format time
lcd.clear();
lcd.print("DateTime:");
lcd.setCursor(0, 1);
lcd.print(dtbuf);
if (millis() - lastMsg >= publishInterval) { // Periodically publish time
lastMsg = millis();
client.publish(mqtt_topic_time, dtbuf);
}
} else {
lcd.clear();
lcd.print("Time sync lost!"); // Error if time not available
}
}
// Read sensors and check for alarm conditions
void checkSensors() {
unsigned long start = millis();
// Read sensor data
currentHumidity = dht.readHumidity();
currentTempC = dht.readTemperature();
currentTempF = dht.readTemperature(true); // true = Fahrenheit
currentGasLevel = readGasLevel();
if (millis() - start > 1000) { // Timeout if sensor read takes too long
Serial.println("Sensor read timeout");
return;
}
// Check for DHT sensor errors
if (isnan(currentHumidity) || isnan(currentTempC) || isnan(currentTempF)) {
Serial.println(F("DHT read failed!"));
return;
}
// Print sensor data to serial monitor
Serial.printf("Hum: %.1f%% Temp: %.1f°C / %.1f°F Gas: %d Motion: %s\n",
currentHumidity, currentTempC, currentTempF, currentGasLevel, motionDetected ? "Yes" : "No");
// Check if any alarm thresholds are exceeded
bool tempAlarm = currentTempC > TEMP_THRESHOLD_C;
bool humAlarm = currentHumidity > HUMIDITY_THRESHOLD;
bool gasAlarm = currentGasLevel > GAS_THRESHOLD;
bool newAlarm = tempAlarm || humAlarm || gasAlarm;
if (newAlarm) { // If alarm condition detected
if (!isAlarm) { // New alarm
isAlarm = true;
alarmStartTime = millis();
client.publish(mqtt_topic_alarm, "Alarm"); // Publish alarm status
const char* alarmType = "";
float alarmValue = 0;
const char* alarmUnit = "";
// Determine which alarm was triggered
if (gasAlarm) {
alarmType = "Gas";
alarmValue = currentGasLevel;
}
else if (tempAlarm) {
alarmType = "Temp";
alarmValue = currentTempC;
alarmUnit = "°C";
}
else if (humAlarm) {
alarmType = "Humidity";
alarmValue = currentHumidity;
alarmUnit = "%";
}
Serial.printf("Alarm triggered: %s %.1f%s\n", alarmType, alarmValue, alarmUnit);
}
} else { // Alarm condition cleared
if (isAlarm) {
isAlarm = false;
client.publish(mqtt_topic_alarm, "Normal"); // Publish normal status
Serial.println("Alarm cleared");
}
}
}
// Display sensor data on LCD (or alarm message if triggered)
void displaySensorData() {
if (isAlarm) { // Show alarm message
lcd.clear();
lcd.print("ALARM!");
lcd.setCursor(0, 1);
// Identify which alarm is active
bool tempAlarm = currentTempC > TEMP_THRESHOLD_C;
bool humAlarm = currentHumidity > HUMIDITY_THRESHOLD;
bool gasAlarm = currentGasLevel > GAS_THRESHOLD;
if (gasAlarm) {
lcd.print("Gas Over!");
lcd.setCursor(0, 2);
lcd.print(String(currentGasLevel) + " > " + String(GAS_THRESHOLD));
}
else if (tempAlarm) {
lcd.print("Temp Over!");
lcd.setCursor(0, 2);
lcd.print(String(currentTempC,1));lcd.write(0);lcd.print("C>");
lcd.print(String(TEMP_THRESHOLD_C, 1));lcd.write(0);lcd.print("C");
}
else if (humAlarm) {
lcd.print("Humidity Over!");
lcd.setCursor(0, 2);
lcd.print(String(currentHumidity) + "% > " + String(HUMIDITY_THRESHOLD) + "%");
}
lcd.setCursor(0, 3);
lcd.print("Motion: ");
lcd.print(motionDetected ? "Detected" : "None");
} else { // Normal operation - show sensor data
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Humidity: "); lcd.print(currentHumidity, 1); lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("Temp: "); lcd.print(currentTempC, 1); lcd.write(0); lcd.print("C/");
lcd.print(currentTempF, 1); lcd.write(0); lcd.print("F");
lcd.setCursor(0, 2);
lcd.print("Gas Level: "); lcd.print(currentGasLevel);
lcd.setCursor(0, 3);
lcd.print("Motion: "); lcd.print(motionDetected ? "Detected" : "None");
// Periodically publish sensor data via MQTT
if (millis() - lastMsg >= publishInterval) {
lastMsg = millis();
snprintf(msg, sizeof(msg),
"{\"temp_c\": %.1f, \"temp_f\": %.1f, \"hum\": %.1f, \"gas\": %d, \"motion\": %s}",
currentTempC, currentTempF, currentHumidity, currentGasLevel, motionDetected ? "true" : "false");
client.publish(mqtt_topic_pub, msg);
}
}
}
// MQTT callback - handles incoming messages
void callback(char* topic, byte* payload, unsigned int length) {
payload[length] = 0; // Null-terminate payload
String command = String((char*)payload);
command.toUpperCase();
Serial.print("MQTT command: "); Serial.println(command);
if (command == "RESTART") { // Handle restart command
Serial.println("Received restart command");
blinkAndRestart();
}
}
// Control buzzer for alarm sounds
void controlBuzzer(bool enable) {
static int buzzerPattern = 0;
unsigned long now = millis();
if (enable && now - lastBuzzerToggle >= 500) { // Play alarm pattern
lastBuzzerToggle = now;
buzzerPattern = (buzzerPattern + 1) % 4;
switch (buzzerPattern) {
case 0: tone(BUZZER_PIN, 1000); break; // Different tones
case 1: tone(BUZZER_PIN, 1500); break;
case 2: tone(BUZZER_PIN, 2000); break;
case 3: noTone(BUZZER_PIN); break;
}
} else if (!enable) { // Turn off buzzer
noTone(BUZZER_PIN);
}
}
// Blink LED and restart device
void blinkAndRestart() {
lcd.clear();
lcd.print("Restarting...");
// Blink LED before restart
for (int i = 0; i < 3; i++) {
digitalWrite(LED2_PIN, LOW);
delay(300);
digitalWrite(LED2_PIN, HIGH);
delay(300);
}
client.publish(mqtt_topic_lwt, "restarting", true); // Publish status
delay(100);
esp_restart(); // Restart ESP32
}
// Handle button press (with debouncing)
void handleButton() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) { // Button state changed
lastDebounceTime = millis();
}
// Stabilize button reading (debounce)
if (millis() - lastDebounceTime > debounceDelay && reading != buttonState) {
buttonState = reading;
if (buttonState == LOW && !restartTriggered) { // Button pressed
restartTriggered = true;
Serial.println("Button pressed: Restart");
blinkAndRestart(); // Trigger restart
}
}
lastButtonState = reading;
}
// Reconnect to MQTT broker if disconnected
void reconnect() {
if (client.connected()) return; // Already connected
Serial.println("Connecting to MQTT...");
// Connect with LWT (Last Will and Testament) message
if (client.connect("ESP32ClientDevice", mqtt_topic_lwt, 0, true, "offline")) {
Serial.println("MQTT connected!");
client.publish(mqtt_topic_lwt, "online", true); // Publish online status
client.subscribe(mqtt_topic_sub); // Subscribe to control commands
delay(500);
} else {
Serial.print("MQTT failed (");
Serial.print(client.state());
Serial.println("), retry in 5s...");
delay(5000); // Wait before retrying
}
}
// Initial setup - runs once at startup
void setup() {
Serial.begin(115200); // Initialize serial communication
Serial.println("Starting...");
// Set pin modes
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED1_PIN, OUTPUT);
pinMode(LED2_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(GAS_PIN, INPUT);
pinMode(PIR_PIN, INPUT);
// Initialize pin states
digitalWrite(LED1_PIN, LOW);
digitalWrite(LED2_PIN, HIGH);
digitalWrite(BUZZER_PIN, LOW);
// Initialize LCD
lcd.init();
lcd.backlight();
lcd.createChar(0, dot); // Create custom degree symbol
lcd.clear();
lcd.print("Initializing...");
delay(1000);
dht.begin(); // Initialize DHT sensor
setup_wifi(); // Connect to WiFi
// Configure MQTT
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
client.setKeepAlive(120);
client.setSocketTimeout(5000);
// Configure time synchronization (UTC+8)
configTime(8 * 3600, 0, "pool.ntp.org");
struct tm timeinfo;
int timeRetry = 0;
while (!getLocalTime(&timeinfo) && timeRetry < 5) { // Retry time sync
Serial.println("Waiting for time...");
delay(1000);
timeRetry++;
}
if (timeRetry < 5) { // Time sync successful
Serial.println("Time synced");
displayWelcome();
lastDisplayChange = millis();
lastMsg = 0;
} else {
Serial.println("Time sync failed!");
}
Serial.println("Entering loop");
}
// Main loop - runs continuously
void loop() {
client.loop(); // Maintain MQTT connection
if (!client.connected()) reconnect(); // Reconnect MQTT if needed
handleButton(); // Check button press
handlePIR(); // Check motion sensor
unsigned long now = millis();
// Read sensors at specified interval
if (now - lastSensorRead >= sensorReadInterval) {
lastSensorRead = now;
checkSensors();
}
// Handle alarm indicators
if (isAlarm) {
controlBuzzer(true); // Activate buzzer
if (now - lastBuzzerToggle >= 250) { // Blink alarm LED
lastBuzzerToggle = now;
digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));
}
} else {
controlBuzzer(false); // Deactivate buzzer
digitalWrite(LED1_PIN, LOW);
}
// Manage LCD display
if (isAlarm) { // Show alarm screen during alarm
if (now - lastDisplayChange >= 1000) {
lastDisplayChange = now;
displaySensorData();
}
} else { // Cycle through normal displays
if (motionStateChanged && !restartTriggered) { // Update on motion change
motionStateChanged = false;
if (displayState == 1) displayDateTime();
else if (displayState == 2) displaySensorData();
}
// Cycle display modes at interval
if (now - lastDisplayChange >= displayInterval && !restartTriggered) {
lastDisplayChange = now;
displayState = (displayState + 1) % 3;
switch (displayState) {
case 0: displayWelcome(); break;
case 1: displayDateTime(); break;
case 2: displaySensorData(); break;
}
}
}
}