#pragma once
#include <WiFi.h> // For WiFi connectivity
#include <PubSubClient.h> // For MQTT communication
#include <Wire.h> // For I2C communication (LCD)
#include <LiquidCrystal_I2C.h> // For controlling I2C LCD
#include <DHT.h> // For DHT temperature/humidity sensor
#include <time.h> // For time synchronization
#include <esp_system.h> // For ESP32 system functions (e.g., restart)
// Pin definitions
#define BUTTON_PIN 15 // Button pin (for restart)
#define LED1_PIN 19 // LED1 pin (alarm indicator)
#define LED2_PIN 18 // LED2 pin (status indicator)
#define DHTPIN 26 // DHT sensor pin
#define DHTTYPE DHT22 // DHT sensor type (DHT22)
#define BUZZER_PIN 4 // Buzzer pin (alarm sound)
#define GAS_PIN 34 // Gas sensor pin (analog input)
#define PIR_PIN 27 // PIR motion sensor pin
// Thresholds for alarms
const float TEMP_THRESHOLD_C = 20.0; // Temperature threshold (°C)
const float TEMP_THRESHOLD_F = 68.0; // Temperature threshold (°F)
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 (ms)
// WiFi & MQTT configuration
const char* ssid = "Wokwi-GUEST"; // WiFi SSID
const char* password = ""; // WiFi password
const char* mqtt_server = "broker.hivemq.com"; // MQTT broker address
const char* mqtt_topic_pub = "IOT/Warehouse/sensor"; // Sensor data topic
const char* mqtt_topic_time = "IOT/Warehouse/time"; // Time data topic
const char* mqtt_topic_sub = "IOT/Warehouse/control";// Control commands topic
const char* mqtt_topic_alarm = "IOT/Warehouse/alarm";// Alarm status topic
const char* mqtt_topic_lwt = "IOT/Warehouse/status"; // Last Will & Testament topic
const char* mqtt_topic_motion = "IOT/Warehouse/motion";// Motion status topic
// Object instances
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 instance (I2C addr 0x27, 20x4 display)
// Global state variables
bool led1State = false, led2State = false; // LED states
unsigned long lastMsg = 0, lastDisplayChange = 0, // Timestamps for events
alarmStartTime = 0, lastBuzzerToggle = 0,
lastSensorRead = 0;
const long sensorReadInterval = 2000; // Sensor read interval (ms)
const long publishInterval = 2000, displayInterval = 2000; // Publish/display intervals
int displayState = 0; // LCD display state (0:Welcome,1:Time,2:Sensors)
char msg[100]; // MQTT message buffer
bool welcomeDisplayed = false, isAlarm = false, // Flags for welcome, alarm
motionDetected = false, motionStateChanged = false; // Motion detection flags
unsigned long lastMotionTime = 0; // Last motion detection timestamp
int buttonState, lastButtonState = HIGH; // Button debounce states
unsigned long lastDebounceTime = 0, debounceDelay = 50; // Button debounce timing
bool restartTriggered = false; // Restart trigger flag
uint8_t dot[] = {0x0E, 0x0A, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}; // Custom LCD character (°)
// Function prototypes
void blinkAndRestart(); // Blink LED then restart
void checkSensors(); // Read sensors & check alarms
void setup_wifi(); // Initialize WiFi
int readGasLevel(); // Read gas sensor value
void handlePIR(); // Handle PIR motion sensor
void displayWelcome(); // Show welcome message on LCD
void displayDateTime(); // Show date/time on LCD
void displaySensorData(); // Show sensor data on LCD
void callback(char* topic, byte* payload, unsigned int length); // MQTT callback
void controlBuzzer(bool enable); // Control buzzer (alarm sound)
void handleButton(); // Handle button press (restart)
void reconnect(); // Reconnect to MQTT broker
// Initialize WiFi connection
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 { // Restart if WiFi fails
Serial.println("\nWiFi failed! Restarting...");
esp_restart();
}
}
// Read gas sensor analog value
int readGasLevel() { return analogRead(GAS_PIN); }
// Handle PIR motion sensor (detect motion start/stop)
void handlePIR() {
bool currentMotion = digitalRead(PIR_PIN) == HIGH;
if (currentMotion) { // Motion detected
lastMotionTime = millis();
if (!motionDetected) { // New motion
motionDetected = true;
motionStateChanged = true;
Serial.println("Motion detected!");
client.publish(mqtt_topic_motion, "detected"); // Publish to MQTT
}
} else if (motionDetected && millis() - lastMotionTime > MOTION_DELAY) { // Motion stopped
motionDetected = false;
motionStateChanged = true;
Serial.println("Motion stopped");
client.publish(mqtt_topic_motion, "stopped"); // Publish to MQTT
}
}
// Show welcome message on LCD (once)
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);
}
}
// Display date/time on LCD and publish to MQTT
void displayDateTime() {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) { // If time is synced
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) { // Publish periodically
lastMsg = millis();
client.publish(mqtt_topic_time, dtbuf);
}
} else { // Time sync failed
lcd.clear();
lcd.print("Time sync lost!");
}
}
// Read sensors and check for alarm conditions
void checkSensors() {
unsigned long start = millis();
float h = dht.readHumidity(); // Read humidity
float c = dht.readTemperature(); // Read temp (°C)
float f = dht.readTemperature(true);// Read temp (°F)
int gas = readGasLevel(); // Read gas level
if (millis() - start > 1000) { // Timeout if sensor read takes too long
Serial.println("Sensor read timeout");
return;
}
if (isnan(h) || isnan(c) || isnan(f)) { // Check for DHT read errors
Serial.println(F("DHT read failed!"));
return;
}
// Print sensor data to serial
Serial.printf("Hum: %.1f%% Temp: %.1f°C / %.1f°F Gas: %d Motion: %s\n",
h, c, f, gas, motionDetected ? "Yes" : "No");
// Check if any threshold is exceeded
bool tempAlarm = c > TEMP_THRESHOLD_C;
bool humAlarm = h > HUMIDITY_THRESHOLD;
bool gasAlarm = gas > GAS_THRESHOLD;
bool newAlarm = tempAlarm || humAlarm || gasAlarm;
if (newAlarm) { // Trigger alarm if new condition
if (!isAlarm) {
isAlarm = true;
alarmStartTime = millis();
client.publish(mqtt_topic_alarm, "Alarm"); // Publish alarm
const char* alarmType = "";
float alarmValue = 0;
const char* alarmUnit = "";
// Determine alarm type
if (gasAlarm) {
alarmType = "Gas";
alarmValue = gas;
alarmUnit = "";
}
else if (tempAlarm) {
alarmType = "Temp";
alarmValue = c;
alarmUnit = "°C";
}
else if (humAlarm) {
alarmType = "Humidity";
alarmValue = h;
alarmUnit = "%";
}
Serial.printf("Alarm triggered: %s %.1f%s\n", alarmType, alarmValue, alarmUnit);
}
} else { // Clear alarm if conditions normal
if (isAlarm) {
isAlarm = false;
client.publish(mqtt_topic_alarm, "Normal"); // Publish normal status
Serial.println("Alarm cleared");
}
}
}
// Display sensor data on LCD (show alarm info if triggered)
void displaySensorData() {
float h = dht.readHumidity();
float c = dht.readTemperature();
float f = dht.readTemperature(true);
int gas = readGasLevel();
if (isAlarm) { // Show alarm info
lcd.clear();
lcd.print("ALARM!");
lcd.setCursor(0, 1);
bool tempAlarm = c > TEMP_THRESHOLD_C;
bool humAlarm = h > HUMIDITY_THRESHOLD;
bool gasAlarm = gas > GAS_THRESHOLD;
if (gasAlarm) {
lcd.print("Gas Over!");
lcd.setCursor(0, 2);
lcd.print(String(gas) + " > " + String(GAS_THRESHOLD));
}
else if (tempAlarm) {
lcd.print("Temp Over!");
lcd.setCursor(0, 2);
lcd.print(String(c,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(h) + "% > " + String(HUMIDITY_THRESHOLD) + "%");
}
lcd.setCursor(0, 3);
lcd.print("Motion: ");
lcd.print(motionDetected ? "Detected" : "None");
} else { // Show normal sensor data
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Humidity: "); lcd.print(h, 1); lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("Temp: "); lcd.print(c, 1); lcd.write(0); lcd.print("C/");
lcd.print(f, 1); lcd.write(0); lcd.print("F");
lcd.setCursor(0, 2);
lcd.print("Gas Level: "); lcd.print(gas);
lcd.setCursor(0, 3);
lcd.print("Motion: "); lcd.print(motionDetected ? "Detected" : "None");
// Publish sensor data periodically
if (millis() - lastMsg >= publishInterval) {
lastMsg = millis();
snprintf(msg, sizeof(msg),
"{\"temp_c\": %.1f, \"temp_f\": %.1f, \"hum\": %.1f, \"gas\": %d, \"motion\": %s}",
c, f, h, gas, motionDetected ? "true" : "false");
client.publish(mqtt_topic_pub, msg);
}
}
}
// MQTT callback (handle received commands)
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 (variable tone for alarm)
void controlBuzzer(bool enable) {
static int buzzerPattern = 0;
unsigned long now = millis();
if (enable && now - lastBuzzerToggle >= 500) { // Change tone every 500ms
lastBuzzerToggle = now;
buzzerPattern = (buzzerPattern + 1) % 4;
switch (buzzerPattern) {
case 0: tone(BUZZER_PIN, 1000); break; // 1kHz tone
case 1: tone(BUZZER_PIN, 1500); break; // 1.5kHz tone
case 2: tone(BUZZER_PIN, 2000); break; // 2kHz tone
case 3: tone(BUZZER_PIN, 3000); break; // 3kHz tone
case 4: noTone(BUZZER_PIN); break; // Stop
}
} else if (!enable) { // Stop buzzer
noTone(BUZZER_PIN);
}
}
// Blink LED then restart device
void blinkAndRestart() {
lcd.clear();
lcd.print("Restarting...");
for (int i = 0; i < 3; i++) { // Blink 3 times
digitalWrite(LED2_PIN, LOW);
delay(300);
digitalWrite(LED2_PIN, HIGH);
delay(300);
}
client.publish(mqtt_topic_lwt, "restarting", true); // Publish restart status
delay(100);
esp_restart(); // Restart ESP32
}
// Handle button press (with debounce) to trigger restart
void handleButton() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) { // Reset debounce timer on state change
lastDebounceTime = millis();
}
// Check if button state is stable (debounced)
if (millis() - lastDebounceTime > debounceDelay && reading != buttonState) {
buttonState = reading;
if (buttonState == LOW && !restartTriggered) { // Button pressed
restartTriggered = true;
Serial.println("Button pressed: Restart");
blinkAndRestart();
}
}
lastButtonState = reading;
}
// Reconnect to MQTT broker if disconnected
void reconnect() {
if (client.connected()) return; // Do nothing if already connected
Serial.println("Connecting to MQTT...");
// Connect with LWT (Last Will & Testament)
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 topic
delay(500);
} else { // Retry if connection fails
Serial.print("MQTT failed (");
Serial.print(client.state());
Serial.println("), retry in 5s...");
delay(5000);
}
}
// Initial setup (run once)
void setup() {
Serial.begin(115200);
Serial.println("Starting...");
// Initialize pins
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);
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(); // Start DHT sensor
setup_wifi(); // Connect to WiFi
// Configure MQTT
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
client.setKeepAlive(120);
client.setSocketTimeout(5000);
// Sync time with NTP server (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 { // Time sync failed
Serial.println("Time sync failed!");
}
Serial.println("Entering loop");
}
// Main loop (run continuously)
void loop() {
client.loop(); // Process MQTT messages
if (!client.connected()) reconnect(); // Reconnect MQTT if needed
handleButton(); // Check button press
handlePIR(); // Check motion sensor
unsigned long now = millis();
// Read sensors at interval
if (now - lastSensorRead >= sensorReadInterval) {
lastSensorRead = now;
checkSensors();
}
// Handle alarm state (buzzer and LED)
if (isAlarm) {
controlBuzzer(true);
if (now - lastBuzzerToggle >= 250) { // Blink LED1 every 250ms
lastBuzzerToggle = now;
digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));
}
} else { // No alarm: turn off buzzer and LED1
controlBuzzer(false);
digitalWrite(LED1_PIN, LOW);
}
// Update LCD display
if (isAlarm) { // Show alarm info continuously
if (now - lastDisplayChange >= 1000) {
lastDisplayChange = now;
displaySensorData();
}
} else { // Cycle through displays normally
if (motionStateChanged && !restartTriggered) { // Update on motion change
motionStateChanged = false;
if (displayState == 1) displayDateTime();
else if (displayState == 2) displaySensorData();
}
// Cycle display states 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;
}
}
}
}