#pragma once
#include <WiFi.h> // ESP32 WiFi连接库,用于连接无线网络
#include <PubSubClient.h> // MQTT客户端库,用于发送/接收MQTT消息
#include <Wire.h> // I2C通信库,用于驱动I2C接口的LCD屏幕
#include <LiquidCrystal_I2C.h> // I2C LCD屏幕驱动库
#include <DHT.h> // 温湿度传感器(DHT系列)驱动库
#include <time.h> // 时间处理库,用于NTP时间同步
#include <esp_system.h> // ESP32系统函数库,用于重启等操作
#define BUTTON_PIN 15 // 按键引脚(用于触发重启)
#define LED1_PIN 19 // 指示灯1引脚(警报时闪烁)
#define LED2_PIN 18 // 指示灯2引脚(系统运行指示)
#define DHTPIN 26 // DHT温湿度传感器引脚
#define DHTTYPE DHT22 // 温湿度传感器型号(DHT22)
#define BUZZER_PIN 4 // 蜂鸣器引脚(警报发声)
#define GAS_PIN 34 // 气体传感器引脚(模拟输入)
#define PIR_PIN 27 // PIR人体红外传感器引脚(检测运动)
// 阈值参数(超过则触发警报)
const float TEMP_THRESHOLD_C = 20.0; // 温度阈值(摄氏度)
const float TEMP_THRESHOLD_F = 68.0; // 温度阈值(华氏度)
const float HUMIDITY_THRESHOLD = 70.0;// 湿度阈值(百分比)
const int GAS_THRESHOLD = 4000; // 气体浓度阈值
const unsigned long MOTION_DELAY = 5000; // 运动检测持续时间(5秒,超过则视为无运动)
// 网络与MQTT参数
const char* ssid = "Wokwi-GUEST"; // WiFi名称
const char* password = ""; // WiFi密码(此处为空)
const char* mqtt_server = "broker.hivemq.com"; // MQTT服务器地址(公共服务器)
// MQTT主题(用于消息发布/订阅)
const char* mqtt_topic_pub = "IoT/Warehouse room1/sensor"; // 传感器数据发布主题
const char* mqtt_topic_time = "IoT/Warehouse room1/time"; // 时间信息发布主题
const char* mqtt_topic_sub = "IoT/Warehouse room1/command"; // 命令订阅主题(如重启)
const char* mqtt_topic_alarm = "IoT/Warehouse room1/alarm"; // 警报信息发布主题
const char* mqtt_topic_lwt = "IoT/Warehouse room1/status"; // LWT遗嘱主题(设备状态)
const char* mqtt_topic_motion = "IoT/Warehouse room1/motion"; // 运动检测发布主题
//全局对象与状态变量
WiFiClient espClient; // WiFi客户端对象(用于网络连接)
PubSubClient client(espClient); // MQTT客户端对象(依赖WiFi客户端)
DHT dht(DHTPIN, DHTTYPE); // DHT传感器对象(绑定引脚和型号)
LiquidCrystal_I2C lcd(0x27, 20, 4); // LCD屏幕对象(I2C地址0x27,20列4行)
// 状态变量
bool led1State = false, led2State = false; // LED1/LED2状态(亮/灭)
unsigned long lastMsg = 0, lastDisplayChange = 0, // 时间戳变量(用于定时操作)
alarmStartTime = 0, lastBuzzerToggle = 0;
const long publishInterval = 3000, displayInterval = 3000; // 定时周期(3秒)
int displayState = 0; // LCD显示状态(0:欢迎页 1:时间 2:传感器数据)
char msg[100]; // MQTT消息缓冲区
bool welcomeDisplayed = false, isAlarm = false, // 标志位(欢迎页是否显示、是否报警等)
motionDetected = false, motionStateChanged = false;
unsigned long lastMotionTime = 0; // 最后一次检测到运动的时间戳
int buttonState, lastButtonState = HIGH; // 按键状态(用于防抖)
unsigned long lastDebounceTime = 0, debounceDelay = 50; // 按键防抖参数(50ms)
bool restartTriggered = false; // 重启触发标志
uint8_t dot[] = {0x0E, 0x0A, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}; // LCD自定义字符(温度符号℃)
//WiFi 连接
void setup_wifi() {
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password); // 启动WiFi连接
int wifiRetry = 0;
while (WiFi.status() != WL_CONNECTED && wifiRetry < 10) { // 重试10次
Serial.print(".");
delay(500); // 每0.5秒重试一次
wifiRetry++;
}
if (WiFi.status() == WL_CONNECTED) Serial.println("\nWiFi connected!");
else { // 连接失败则重启设备
Serial.println("\nWiFi failed! Restarting...");
esp_restart();
}
}
//气体传感器读取函数
int readGasLevel() { return analogRead(GAS_PIN); } // 读取气体传感器模拟值
//PIR 运动检测处理函数
void handlePIR() {
bool currentMotion = digitalRead(PIR_PIN) == HIGH; // 读取PIR传感器状态(高电平表示有运动)
if (currentMotion) { // 检测到运动
lastMotionTime = millis(); // 更新最后运动时间戳
if (!motionDetected) { // 状态从"无运动"变为"有运动"
motionDetected = true;
motionStateChanged = true; // 标记状态变化
Serial.println("Motion detected!");
client.publish(mqtt_topic_motion, "detected"); // 发布"有运动"消息
}
} else if (motionDetected && millis() - lastMotionTime > MOTION_DELAY) { // 无运动且超过持续时间
motionDetected = false;
motionStateChanged = true; // 标记状态变化
Serial.println("Motion stopped");
client.publish(mqtt_topic_motion, "stopped"); // 发布"无运动"消息
}
}
//LCD 欢迎页显示函数
void displayWelcome() {
if (!welcomeDisplayed) { // 只显示一次
lcd.clear();
lcd.print("Hi,Welcome!");
lcd.setCursor(0, 1); // 第2行(索引1)
lcd.print("Warehouse1,");
lcd.setCursor(0, 2); // 第3行
lcd.print("By Group3!");
welcomeDisplayed = true; // 标记已显示
delay(2000); // 显示2秒
}
}
//日期时间显示函数
void displayDateTime() {
struct tm timeinfo; // 时间结构体
if (getLocalTime(&timeinfo)) { // 获取本地时间(已通过NTP同步)
char dtbuf[21];
strftime(dtbuf, sizeof(dtbuf), "%Y-%m-%d %H:%M:%S", &timeinfo); // 格式化时间字符串
lcd.clear();
lcd.print("DateTime:");
lcd.setCursor(0, 1);
lcd.print(dtbuf); // 显示格式化的时间
if (millis() - lastMsg >= publishInterval) { // 每3秒发布一次时间
lastMsg = millis();
client.publish(mqtt_topic_time, dtbuf);
}
} else { // 时间同步失败
lcd.clear();
lcd.print("Time sync lost!");
}
}
//传感器数据显示与发布函数
void displaySensorData() {
unsigned long start = millis();
float h = dht.readHumidity(); // 读取湿度
float c = dht.readTemperature(); // 读取摄氏度
float f = dht.readTemperature(true); // 读取华氏度
int gas = readGasLevel(); // 读取气体浓度
if (millis() - start > 1000) { // 传感器读取超时(超过1秒)
lcd.clear();
lcd.print("Sensor Timeout!");
return;
}
if (isnan(h) || isnan(c) || isnan(f)) { // 温湿度读取失败(返回NaN)
Serial.println(F("DHT read failed!"));
lcd.clear();
lcd.print("Sensor Error!");
return;
}
// 打印传感器数据到串口
Serial.printf("Hum: %.1f%% Temp: %.1f°C / %.1f°F Gas: %d Motion: %s\n",
h, c, f, gas, motionDetected ? "Yes" : "No");
// 判断是否触发警报
bool tempAlarm = c > TEMP_THRESHOLD_C; // 温度超限
bool humAlarm = h > HUMIDITY_THRESHOLD; // 湿度超限
bool gasAlarm = gas > GAS_THRESHOLD; // 气体超限
if (tempAlarm || humAlarm || gasAlarm) { // 触发警报
isAlarm = true;
alarmStartTime = millis(); // 记录警报开始时间
lcd.clear();
lcd.print("ALARM!");
lcd.setCursor(0, 1);
if (tempAlarm) lcd.print("Temp Over!");
else if (humAlarm) lcd.print("Humidity Over!");
else lcd.print("Gas Over!"); // 显示具体警报类型
lcd.setCursor(0, 2); // 显示超限值与阈值
lcd.print(tempAlarm ? String(c) + "C > " + String(TEMP_THRESHOLD_C) + "C"
: humAlarm ? String(h) + "% > " + String(HUMIDITY_THRESHOLD) + "%"
: String(gas) + " > " + String(GAS_THRESHOLD));
lcd.setCursor(0, 3); // 显示运动状态
lcd.print("Motion: ");
lcd.print(motionDetected ? "Detected" : "None");
// 发布警报信息到MQTT
char alarmMsg[100];
snprintf(alarmMsg, sizeof(alarmMsg), "Alarm: %s %.1f%s",
tempAlarm ? "Temp" : humAlarm ? "Humidity" : "Gas",
tempAlarm ? c : humAlarm ? h : (float)gas,
tempAlarm ? "C" : humAlarm ? "%" : "");
client.publish(mqtt_topic_alarm, alarmMsg);
} else { // 无警报,显示正常数据
isAlarm = false;
digitalWrite(BUZZER_PIN, LOW); // 关闭蜂鸣器
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 / "); // 0是自定义℃符号
lcd.print(f, 1); 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");
// 每3秒发布一次传感器数据到MQTT(JSON格式)
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 消息回调函数
void callback(char* topic, byte* payload, unsigned int length) {
payload[length] = 0;
String command = String((char*)payload);
command.toUpperCase();
Serial.print("MQTT: "); Serial.println(command);
if (command == "RESTART") {
Serial.println("Restart command");
blinkAndRestart();
}
}
//蜂鸣器控制函数
void controlBuzzer(bool enable) {
static int buzzerPattern = 0; // 静态变量(记录当前模式)
unsigned long now = millis();
if (enable && now - lastBuzzerToggle >= 500) { // 启用警报且间隔0.5秒
lastBuzzerToggle = now;
buzzerPattern = (buzzerPattern + 1) % 4; // 循环切换模式(0-3)
switch (buzzerPattern) { // 不同模式对应不同频率
case 0: tone(BUZZER_PIN, 1000); break; // 1000Hz
case 1: tone(BUZZER_PIN, 1500); break; // 1500Hz
case 2: tone(BUZZER_PIN, 2000); break; // 2000Hz
case 3: noTone(BUZZER_PIN); break; // 静音
}
} else if (!enable) noTone(BUZZER_PIN); // 关闭蜂鸣器
}
//重启前处理函数
void blinkAndRestart() {
lcd.clear();
lcd.print("Restarting..."); // LCD显示重启提示
for (int i = 0; i < 3; i++) { // LED2闪烁3次(0.3秒亮/灭)
digitalWrite(LED2_PIN, LOW);
delay(300);
digitalWrite(LED2_PIN, HIGH);
delay(300);
}
client.publish(mqtt_topic_lwt, "restarting", true); // 发布重启状态(保留消息)
delay(250); // 等待消息发布完成
esp_restart(); // 重启设备
}
//按键处理函数
void handleButton() {
int reading = digitalRead(BUTTON_PIN); // 读取按键状态
if (reading != lastButtonState) lastDebounceTime = millis(); // 状态变化时更新防抖时间
// 防抖处理:状态稳定50ms后确认状态
if (millis() - lastDebounceTime > debounceDelay && reading != buttonState) {
buttonState = reading;
if (buttonState == LOW && !restartTriggered) { // 按键按下(LOW,因使用INPUT_PULLUP)
restartTriggered = true;
Serial.println("Button: Restart");
blinkAndRestart(); // 触发重启
}
}
lastButtonState = reading; // 更新上一次状态
}
//MQTT 重连函数
void reconnect() {
if (client.connected()) return; // 已连接则直接返回
Serial.println("Connecting to MQTT...");
// 连接MQTT服务器(设置遗嘱消息:断开时发布"offline")
if (client.connect("ESP32ClientDevice", mqtt_topic_lwt, 0, true, "offline")) {
Serial.println("MQTT connected!");
client.publish(mqtt_topic_lwt, "online", true); // 连接成功后发布"online"
client.subscribe(mqtt_topic_sub); // 订阅命令主题
delay(500);
} else { // 连接失败,5秒后重试
Serial.print("MQTT failed (");
Serial.print(client.state()); // 打印错误状态码
Serial.println("), retry in 5s...");
delay(5000);
}
}
// 初始化函数
void setup() {
Serial.begin(115200); // 初始化串口(波特率115200)
Serial.println("Starting...");
// 初始化引脚模式
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); // 初始关闭LED1
digitalWrite(LED2_PIN, HIGH); // 初始打开LED2(运行指示)
digitalWrite(BUZZER_PIN, LOW); // 初始关闭蜂鸣器
// 初始化LCD
lcd.init();
lcd.backlight();
lcd.createChar(0, dot); // 注册自定义字符(℃符号)
lcd.clear();
lcd.print("Initializing...");
delay(1000);
dht.begin(); // 初始化DHT传感器
setup_wifi(); // 连接WiFi
// 初始化MQTT客户端
client.setServer(mqtt_server, 1883); // 设置MQTT服务器地址和端口(1883)
client.setCallback(callback); // 设置消息回调函数
client.setKeepAlive(120); // 心跳间隔120秒
client.setSocketTimeout(5000); // 连接超时5秒
// 配置NTP时间同步(UTC+8时区)
configTime(8 * 3600, 0, "pool.ntp.org"); // 8*3600=东八区时差
struct tm timeinfo;
int timeRetry = 0;
while (!getLocalTime(&timeinfo) && timeRetry < 5) { // 最多重试5次
Serial.println("Waiting for time...");
delay(1000);
timeRetry++;
}
if (timeRetry < 5) { // 时间同步成功
Serial.println("Time synced");
displayWelcome(); // 显示欢迎页
lastDisplayChange = millis();
lastMsg = 0;
} else Serial.println("Time sync failed!"); // 同步失败
Serial.println("Entering loop");
}
//主循环函数
void loop() {
client.loop(); // 处理MQTT消息(必须周期性调用)
if (!client.connected()) reconnect(); // MQTT断开则重连
handleButton(); // 处理按键输入
handlePIR(); // 处理PIR运动检测
unsigned long now = millis();
if (isAlarm) { // 处于警报状态
controlBuzzer(true); // 启动蜂鸣器
if (now - lastBuzzerToggle >= 250) { // 每0.25秒切换LED1状态(闪烁)
lastBuzzerToggle = now;
digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));
}
if (now - alarmStartTime > 30000) { // 警报持续30秒后自动解除
isAlarm = false;
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED1_PIN, led1State);
}
}
// 运动状态变化且无警报/重启时,刷新LCD显示
if (motionStateChanged && !isAlarm && !restartTriggered) {
motionStateChanged = false;
if (displayState == 1) displayDateTime();
else if (displayState == 2) displaySensorData();
}
// 定时切换LCD显示内容(每3秒一次)
if (now - lastDisplayChange >= displayInterval && !isAlarm && !restartTriggered) {
lastDisplayChange = now;
displayState = (displayState + 1) % 3; // 循环切换状态(0→1→2→0)
switch (displayState) {
case 0: displayWelcome(); break;
case 1: displayDateTime(); break;
case 2: displaySensorData(); break;
}
}
}