#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <ESP32Servo.h>
#include <WiFi.h>
#include <PubSubClient.h>
// LCD 设置
LiquidCrystal_I2C lcd(0x27, 16, 2);
// DHT22 温湿度传感器设置
#define DHTPIN 32
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// 舵机设置
Servo myservo;
int servoPin = 13;
int servoPos = 0;
// PIR 传感器和 LED 设置
#define PIR_PIN 34
#define LED_PIN 12
#define RED_LED_PIN 23
// Wi-Fi 设置
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// MQTT 设置
const char* mqttServer = "broker.hivemq.com";
const int mqttPort = 1883;
const char* mqttClientName = "dhtClient";
WiFiClient espClient;
PubSubClient client(espClient);
// MQTT 主题
const char* temperatureTopic = "home/door/temperature";
const char* humidityTopic = "home/door/humidity";
const char* pirTopic = "home/door/pir";
const char* ledTopic = "home/door/led";
const char* lcdTopic = "home/door/lcd";
const char* pin34ControlTopic = "home/door/pin34/control";
const char* morningTemperatureTopic = "home/door/morning_temperature"; // 上午气温主题
const char* afternoonTemperatureTopic = "home/door/afternoon_temperature"; // 下午气温主题
const char* avgTemperatureTopic = "home/door/avg_temperature"; // 新增的平均气温输出主题
// 系统状态
enum SystemMode { AUTO, MANUAL_ON, MANUAL_OFF };
SystemMode systemMode = AUTO;
bool lastPirState = false;
unsigned long lastActionTime = 0;
unsigned long lastDisplayTime = 0;
const unsigned long coolDownPeriod = 5000; // 5秒冷却时间
const unsigned long displayInterval = 5000; // 5秒更新一次 LCD 显示
bool pin34State = false; // 默认为 OFF
// 上午和下午气温输入
float morningTemp = 0.0; // 上午气温
float afternoonTemp = 0.0; // 下午气温
void setup() {
Serial.begin(115200);
// 初始化硬件
myservo.attach(servoPin);
pinMode(PIR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(RED_LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
digitalWrite(RED_LED_PIN, LOW);
// 初始化 LCD
lcd.init();
lcd.backlight();
showSystemStatus("System Booting...", "");
// 连接 WiFi
connectWiFi();
// 初始化传感器和MQTT
dht.begin();
client.setServer(mqttServer, mqttPort);
client.setCallback(mqttCallback);
}
void connectWiFi() {
WiFi.begin(ssid, password);
int retryCount = 0;
while (WiFi.status() != WL_CONNECTED && retryCount < 10) {
delay(1000);
Serial.print(".");
retryCount++;
}
if (WiFi.status() == WL_CONNECTED) {
showSystemStatus("WiFi Connected", "IP: " + WiFi.localIP().toString());
} else {
showSystemStatus("WiFi Failed", "Check connection");
}
delay(2000);
}
void showSystemStatus(String line1, String line2) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(line1);
lcd.setCursor(0, 1);
lcd.print(line2);
publishLCDContent(line1 + " | " + line2);
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
// 处理收到的上午气温
if (String(topic) == morningTemperatureTopic) {
morningTemp = message.toFloat(); // 将收到的字符串转换为浮动数值
Serial.println("Received Morning Temp: " + String(morningTemp, 1));
}
// 处理收到的下午气温
if (String(topic) == afternoonTemperatureTopic) {
afternoonTemp = message.toFloat(); // 将收到的字符串转换为浮动数值
Serial.println("Received Afternoon Temp: " + String(afternoonTemp, 1));
}
// 处理pin34控制
if (String(topic) == pin34ControlTopic) {
if (message == "on") {
pin34State = true; // 打开 pin34
showSystemStatus("Pin34 ON", "PIR Enabled");
} else if (message == "off") {
pin34State = false; // 关闭 pin34
showSystemStatus("Pin34 OFF", "PIR Disabled");
}
}
// 计算并发送平均气温(如果上午和下午气温都已接收到)
if (morningTemp != 0.0 && afternoonTemp != 0.0) {
calculateAndSendAverageTemperature();
}
}
void reconnectMQTT() {
while (!client.connected()) {
if (client.connect(mqttClientName)) {
client.subscribe(morningTemperatureTopic); // 订阅上午气温主题
client.subscribe(afternoonTemperatureTopic); // 订阅下午气温主题
client.subscribe(pin34ControlTopic); // 订阅pin34控制主题
} else {
delay(5000);
}
}
}
void publishLCDContent(String content) {
client.publish(lcdTopic, content.c_str());
}
void calculateAndSendAverageTemperature() {
// 计算平均气温
float averageTemp = (morningTemp + afternoonTemp) / 2.0;
String avgTempStr = "Avg Temp: " + String(averageTemp, 1) + " C"; // 转换为字符串
// 将平均气温通过MQTT发送到 home/door/avg_temperature 主题
client.publish(avgTemperatureTopic, avgTempStr.c_str());
// 只在串口打印不在LCD显示
Serial.println("Sending Average Temp: " + String(averageTemp, 1) + " C");
}
void operateDoor(bool open) {
if (open) {
showSystemStatus("Door Opening", "Please wait...");
for (servoPos = 0; servoPos <= 180; servoPos++) {
myservo.write(servoPos);
delay(15);
}
delay(2000); // 保持开门状态2秒
showSystemStatus("Door Closing", "Please wait...");
for (servoPos = 180; servoPos >= 0; servoPos--) {
myservo.write(servoPos);
delay(15);
}
showSystemStatus("Door Closed", "System Ready");
digitalWrite(RED_LED_PIN, HIGH);
delay(1000);
digitalWrite(RED_LED_PIN, LOW);
}
}
void updateTemperatureHumidityLCD(float temperature, float humidity) {
String tempStr = "Temp: " + String(temperature, 1) + " C";
String humStr = "Hum: " + String(humidity, 1) + " %";
// 避免乱码:清除行内多余的字符
lcd.setCursor(0, 0);
lcd.print(tempStr);
// 如果字符串短于16个字符,清除剩余的部分
for (int i = tempStr.length(); i < 16; i++) {
lcd.print(" ");
}
lcd.setCursor(0, 1);
lcd.print(humStr);
// 如果字符串短于16个字符,清除剩余的部分
for (int i = humStr.length(); i < 16; i++) {
lcd.print(" ");
}
}
void loop() {
if (!client.connected()) {
reconnectMQTT();
}
client.loop();
unsigned long currentTime = millis();
// 每5秒钟更新一次 LCD 显示温湿度
if (currentTime - lastDisplayTime >= displayInterval) {
lastDisplayTime = currentTime;
// 读取环境数据
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
if (!isnan(temperature) && !isnan(humidity)) {
updateTemperatureHumidityLCD(temperature, humidity); // 更新温湿度显示
client.publish(temperatureTopic, String(temperature, 1).c_str());
client.publish(humidityTopic, String(humidity, 1).c_str());
}
}
// 仅当 pin34 为 "ON" 时,PIR 才能控制舵机
if (pin34State) {
// PIR 检测逻辑
bool currentPirState = digitalRead(PIR_PIN);
// 只有状态变化时才处理
if (currentPirState != lastPirState) {
lastPirState = currentPirState;
client.publish(pirTopic, currentPirState ? "ON" : "OFF");
// 只在开启 pin34 且 PIR 触发并且冷却时间到期时执行
if (currentPirState && (currentTime - lastActionTime > coolDownPeriod)) {
lastActionTime = currentTime; // 更新最后一次操作时间
digitalWrite(LED_PIN, HIGH);
client.publish(ledTopic, "ON");
operateDoor(true); // 控制舵机打开
digitalWrite(LED_PIN, LOW);
client.publish(ledTopic, "OFF");
}
}
}
delay(100); // 添加短延时,避免 CPU 过度占用
}