#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <time.h>
// 定义传感器引脚和类型
#define DHTPIN 26
#define DHTTYPE DHT22
#define BUTTON_PIN 14 // 按钮输入引脚
#define LED_PIN 19 // LED输出引脚
// WiFi和MQTT配置
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.hivemq.com";
// MQTT主题定义
const char* mqtt_topic_pub = "IoT/WXP/sensor"; // 发布温湿度数据
const char* mqtt_topic_time = "IoT/WXP/time"; // 发布时间数据
const char* mqtt_topic_sub = "IoT/WXP/led"; // 订阅LED控制命令
const char* mqtt_topic_alarm = "IoT/WXP/alarm"; // 发布告警信息
const char* mqtt_topic_lwt = "IoT/WXP/status"; // 遗嘱消息主题(设备状态)
// 创建客户端和传感器对象
WiFiClient espClient;
PubSubClient client(espClient);
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 20, 4); // 使用20x4 LCD
// 全局变量
bool ledState = false; // LED当前状态
unsigned long lastMsg = 0; // 上次发布消息时间
unsigned long lastButtonTime = 0; // 上次按钮操作时间
unsigned long lastDisplayChange = 0; // 上次显示更新时间
const long publishInterval = 5000; // 5秒定时发布间隔
const long idleTimeToSleep = 60000; // 1分钟无操作进入休眠
const long displayInterval = 5000; // 5秒切换显示内容
int displayState = 0; // 0:欢迎, 1:时间, 2:温湿度
char msg[100]; // MQTT消息缓冲区
// 自定义温度点字符(用于LCD显示℃符号)
uint8_t dot[] = {0x0E, 0x0A, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00};
// 连接WiFi网络
void setup_wifi() {
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password);
// 等待WiFi连接
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println("\nWiFi connected successfully!");
}
// MQTT消息回调函数,处理订阅的消息
void callback(char* topic, byte* payload, unsigned int length) {
payload[length] = 0; // 确保字符串以空字符结尾
String command = String((char*)payload);
command.toUpperCase(); // 转换为大写方便比较
Serial.print("Received command: ");
Serial.println(command);
// 处理LED控制命令
if (command == "ON") {
digitalWrite(LED_PIN, HIGH);
ledState = true;
Serial.println("LED turned ON via MQTT command");
} else if (command == "OFF") {
digitalWrite(LED_PIN, LOW);
ledState = false;
Serial.println("LED turned OFF via MQTT command");
}
}
// 重新连接MQTT服务器(带重试间隔控制)
void reconnect() {
// 增加重连间隔控制,避免频繁重试
static unsigned long lastReconnectAttempt = 0;
const long reconnectInterval = 5000; // 5秒重试间隔
if (millis() - lastReconnectAttempt < reconnectInterval) {
return; // 未到重试时间
}
lastReconnectAttempt = millis();
Serial.println("Connecting to MQTT...");
// 连接MQTT服务器,设置遗嘱消息
if (client.connect("ESP32ClientDevice", mqtt_topic_lwt, 0, true, "offline")) {
Serial.println("MQTT connected successfully");
client.publish(mqtt_topic_lwt, "online", true); // 发布在线状态
client.subscribe(mqtt_topic_sub); // 订阅LED控制主题
// 设置更长的KeepAlive时间和socket超时,提高稳定性
client.setKeepAlive(60);
client.setSocketTimeout(30);
} else {
Serial.print("MQTT connection failed, status=");
Serial.print(client.state());
Serial.println(", will retry later");
}
}
// 在LCD上显示欢迎信息
void displayWelcome() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ESP32!");
lcd.setCursor(0, 1);
lcd.print("Hello, World!");
}
// 在LCD上显示当前日期和时间,并发布到MQTT
void displayDateTime() {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char dtbuf[21];
// 格式化时间为"YYYY-MM-DD HH:MM:SS"
strftime(dtbuf, sizeof(dtbuf), "%Y-%m-%d %H:%M:%S", &timeinfo);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Current DateTime:");
lcd.setCursor(0, 1);
lcd.print(dtbuf);
// 发布时间到MQTT
client.publish(mqtt_topic_time, dtbuf);
}
}
// 在LCD上显示温湿度数据,并发布到MQTT
void displayTempHumidity() {
// 在读取传感器前检查是否超时,避免长时间操作导致休眠
unsigned long start = millis();
// 读取温湿度数据
float h = dht.readHumidity();
float c = dht.readTemperature();
float f = dht.readTemperature(true);
// 如果读取时间过长,直接返回不更新显示
if (millis() - start > 1000) {
return;
}
// 检查读取是否成功
if (isnan(h) || isnan(c) || isnan(f)) {
Serial.println(F("Failed to read from DHT sensor!"));
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sensor Error!");
return;
}
// 打印温湿度数据到串口
Serial.printf(
"Humidity: %.1f Temperature: %.1f deg C %.1f deg F\n",
h, c, f
);
// 在LCD上显示温湿度数据
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Humidity: "); lcd.print(h, 1); lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("Temperature: "); lcd.print(c, 2); lcd.write(0); lcd.print("C");
lcd.setCursor(0, 2);
lcd.print("Temperature: "); lcd.print(f, 1); lcd.write(0); lcd.print("F");
// 发布温湿度数据到MQTT(JSON格式)
snprintf(msg, sizeof(msg), "{\"temp\": %.1f, \"hum\": %.1f}", c, h);
client.publish(mqtt_topic_pub, msg);
}
// 初始化函数,系统启动时执行一次
void setup() {
Serial.begin(115200);
Serial.println("Starting setup()...");
// 配置IO引脚
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW); // 初始关闭LED
// 初始化LCD
lcd.init();
lcd.backlight();
lcd.clear();
// 创建自定义字符(℃符号)
lcd.createChar(0, dot);
// 初始化传感器和网络
dht.begin();
setup_wifi();
// 配置MQTT客户端
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
// 配置NTP时间同步 (UTC+8时区)
configTime(8 * 3600, 0, "pool.ntp.org");
struct tm timeinfo;
// 等待时间同步完成
while (!getLocalTime(&timeinfo)) {
Serial.println("Waiting for time sync...");
delay(1000);
}
Serial.println("Time synchronized successfully");
// 初始化计时器和显示
lastButtonTime = millis();
lastDisplayChange = millis();
displayWelcome();
Serial.println("setup() complete, entering loop()");
}
// 主循环,系统启动后不断循环执行
void loop() {
// 检查MQTT连接,必要时重连
if (!client.connected()) {
reconnect();
}
// 处理MQTT网络事件
client.loop();
unsigned long now = millis();
// 按钮检测(使用非阻塞方式)
if (digitalRead(BUTTON_PIN) == LOW) {
delay(50); // 简单消抖
if (digitalRead(BUTTON_PIN) == LOW) { // 确认按钮按下
// 切换LED状态
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
Serial.print("Button pressed, LED toggled to: ");
Serial.println(ledState ? "ON" : "OFF");
// 发布按钮按下告警到MQTT
client.publish(mqtt_topic_alarm, "Button pressed!");
// 重置计时器,避免休眠
lastButtonTime = now;
lastDisplayChange = now;
// 等待按钮释放
while (digitalRead(BUTTON_PIN) == LOW) {
delay(10);
// 在等待按钮释放期间持续更新计时器,防止休眠
now = millis();
lastButtonTime = now;
}
}
}
// 检查是否超时无操作,进入休眠状态
if (now - lastButtonTime > idleTimeToSleep) {
Serial.println("Idle timeout reached, entering light sleep...");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Sleeping 10s...");
// 设置30秒后唤醒
esp_sleep_enable_timer_wakeup(30 * 1000000ULL);
Serial.println("Entering light sleep now.");
esp_light_sleep_start(); // 进入浅睡眠模式
// 唤醒后执行的代码
Serial.println("Woke up from light sleep!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Woke up!");
delay(500);
// 重置状态和计时器
lastButtonTime = millis();
lastDisplayChange = millis();
displayState = 0;
displayWelcome();
return; // 从loop函数返回,重新开始循环
}
// 显示内容轮换控制
if (now - lastDisplayChange >= displayInterval) {
lastDisplayChange = now;
displayState = (displayState + 1) % 3; // 循环切换显示状态
// 根据当前显示状态更新LCD内容
switch (displayState) {
case 0: displayWelcome(); break;
case 1: displayDateTime(); break;
case 2: displayTempHumidity(); break;
}
}
}