#include <Keypad.h>
#include <Wire.h> // 引入IIC库
#include <Adafruit_GFX.h> // 引入Adafruit_GFX库
#include <Adafruit_SSD1306.h> // 引入SSD1306库
#include <Fonts/FreeSerif9pt7b.h> // 引入字体库
#include <DHT.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Servo.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
int status = 0; // 状态
int mode = 0; // 模式,默认本地控制
float t, tem_max = 30, tem_min = 25; // 温度
float h, hum_max = 70, hum_min = 50; // 湿度
int l, light_max = 3000, light_min = 800; // 光照强度
char message[70]; // 温湿光度JSON字符串
char ui_status[15]; // 用于更新Node-Red开关状态
const int R = 19; // 红色
const int G = 18; // 绿色
const int B = 5; // 蓝色
const int PIN_FAN = 0; // 风扇
const int buzzer = 2; // 蜂鸣器
unsigned long previousMillis = 0; // 记录上次采集温湿度的时间
const long interval = 3000; // 采集间隔时间为3000毫秒(3秒)
const char *ledStatus = "OFF";
const char *fanStatus = "OFF";
const char *curtainStatus = "OFF";
// 心知天气
const char *Key = "SVeOxnV2vWav1HKci";
const char *location = "福州";
const char *url = "https://api.seniverse.com/v3/weather/now.json";
HTTPClient http;
DynamicJsonDocument doc(256);
// WiFi
const char *ssid = "HONOR"; // 定义WiFi网络的SSID
const char *password = "12345677"; // 定义WiFi网络的密码
// MQTT
const char *mqtt_server = "broker.emqx.io";
const int mqtt_port = 1883;
WiFiClient espClient;
PubSubClient client(espClient);
const char *topic1 = "fjjxu/13/message";
const char *topic2 = "fjjxu/13/mode";
const char *topic3 = "fjjxu/13/status"; // 同步更新状态
const char *topic_tmax = "fjjxu/13/tem_max"; // 最高温度
const char *topic_tmin = "fjjxu/13/tem_min"; // 最高温度
const char *topic_hmax = "fjjxu/13/hum_max"; // 最高温度
const char *topic_hmin = "fjjxu/13/hum_min"; // 最高温度
const char *topic_lmax = "fjjxu/13/light_max"; // 最高温度
const char *topic_lmin = "fjjxu/13/light_min"; // 最高温度
// 温湿度传感器
#define DHTPIN 23
#define DHTTYPE DHT11 // DHT 11
DHT dht(DHTPIN, DHTTYPE);
// 光敏电阻
#define LIGHT_SENSOR_PIN 36
// 伺服电机
static const int servoPin = 4;
Servo servo1;
unsigned long servoLastUpdate = 0;
const int servoInterval = 20;
int servoPos = 0;
int servoTargetPos = -1;
bool servoIncreasing = true;
// 键盘引脚
#define row1 13
#define row2 12
#define row3 14
#define row4 27
#define col1 26
#define col2 25
#define col3 33
#define col4 32
// OLED
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
// OLED初始展示"欢迎进入智能家庭管理系统"
static const unsigned char PROGMEM huan[] = { 0x00, 0x80, 0x00, 0x80, 0xFC, 0x80, 0x04, 0xFC, 0x05, 0x04, 0x49, 0x08, 0x2A, 0x40, 0x14, 0x40,
0x10, 0x40, 0x28, 0xA0, 0x24, 0xA0, 0x45, 0x10, 0x81, 0x10, 0x02, 0x08, 0x04, 0x04, 0x08, 0x02 };
static const unsigned char PROGMEM ying[] = { 0x00, 0x00, 0x20, 0x80, 0x13, 0x3C, 0x12, 0x24, 0x02, 0x24, 0x02, 0x24, 0xF2, 0x24, 0x12, 0x24,
0x12, 0x24, 0x12, 0xB4, 0x13, 0x28, 0x12, 0x20, 0x10, 0x20, 0x28, 0x20, 0x47, 0xFE, 0x00, 0x00 };
static const unsigned char PROGMEM jin[] = { 0x00, 0x90, 0x20, 0x90, 0x10, 0x90, 0x13, 0xFC, 0x00, 0x90, 0x00, 0x90, 0xF0, 0x90, 0x17, 0xFE,
0x10, 0x90, 0x10, 0x90, 0x11, 0x10, 0x11, 0x10, 0x12, 0x10, 0x28, 0x00, 0x47, 0xFE, 0x00, 0x00 };
static const unsigned char PROGMEM ru[] = { 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x80, 0x02, 0x80, 0x02, 0x80,
0x04, 0x40, 0x04, 0x40, 0x08, 0x20, 0x08, 0x20, 0x10, 0x10, 0x20, 0x10, 0x40, 0x08, 0x80, 0x06 };
static const unsigned char PROGMEM zhi[] = { 0x20, 0x00, 0x3E, 0x7C, 0x48, 0x44, 0x08, 0x44, 0xFF, 0x44, 0x14, 0x44, 0x22, 0x7C, 0x40, 0x00,
0x1F, 0xF0, 0x10, 0x10, 0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10, 0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10 };
static const unsigned char PROGMEM neng[] = { 0x10, 0x40, 0x24, 0x44, 0x42, 0x48, 0xFF, 0x70, 0x01, 0x40, 0x00, 0x42, 0x7E, 0x42, 0x42, 0x3E,
0x42, 0x00, 0x7E, 0x44, 0x42, 0x48, 0x42, 0x70, 0x7E, 0x40, 0x42, 0x42, 0x4A, 0x42, 0x44, 0x3E };
static const unsigned char PROGMEM jia[] = { 0x02, 0x00, 0x01, 0x00, 0x7F, 0xFE, 0x40, 0x02, 0x80, 0x04, 0x7F, 0xFC, 0x02, 0x00, 0x0D, 0x08,
0x71, 0x90, 0x02, 0xA0, 0x0C, 0xC0, 0x71, 0xA0, 0x06, 0x98, 0x18, 0x86, 0xE2, 0x80, 0x01, 0x00 };
static const unsigned char PROGMEM ting[] = { 0x01, 0x00, 0x00, 0x80, 0x3F, 0xFE, 0x20, 0x00, 0x2E, 0x0E, 0x22, 0xF0, 0x24, 0x10, 0x24, 0x10,
0x2E, 0xFE, 0x22, 0x10, 0x22, 0x10, 0x2A, 0xFE, 0x44, 0x00, 0x46, 0x00, 0x89, 0xFE, 0x10, 0x00 };
static const unsigned char PROGMEM guan[] = { 0x20, 0x40, 0x3F, 0x7E, 0x48, 0x90, 0x85, 0x08, 0x01, 0x00, 0x7F, 0xFE, 0x40, 0x02, 0x9F, 0xE4,
0x10, 0x20, 0x1F, 0xE0, 0x10, 0x00, 0x1F, 0xF0, 0x10, 0x10, 0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10 };
static const unsigned char PROGMEM li[] = { 0x00, 0x00, 0x01, 0xFC, 0xFD, 0x24, 0x11, 0x24, 0x11, 0xFC, 0x11, 0x24, 0x11, 0x24, 0x7D, 0xFC,
0x10, 0x20, 0x10, 0x20, 0x11, 0xFC, 0x10, 0x20, 0x1C, 0x20, 0xE0, 0x20, 0x43, 0xFE, 0x00, 0x00 };
static const unsigned char PROGMEM xi[] = { 0x00, 0xF8, 0x3F, 0x00, 0x04, 0x00, 0x08, 0x20, 0x10, 0x40, 0x3F, 0x80, 0x01, 0x00, 0x06, 0x10,
0x18, 0x08, 0x7F, 0xFC, 0x01, 0x04, 0x09, 0x20, 0x11, 0x10, 0x21, 0x08, 0x45, 0x04, 0x02, 0x00 };
static const unsigned char PROGMEM tong[] = { 0x10, 0x40, 0x10, 0x20, 0x20, 0x20, 0x23, 0xFE, 0x48, 0x40, 0xF8, 0x88, 0x11, 0x04, 0x23, 0xFE,
0x40, 0x92, 0xF8, 0x90, 0x40, 0x90, 0x00, 0x90, 0x19, 0x12, 0xE1, 0x12, 0x42, 0x0E, 0x04, 0x00 };
// 定义矩阵键盘行列数
const int row = 4;
const int col = 4;
// 定义矩阵键盘按键map
char hexaKeys[row][col] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' }
};
byte rowPins[row] = { row1, row2, row3, row4 }; // 矩阵键盘行数引脚导入
byte colPins[col] = { col1, col2, col3, col4 }; // 矩阵键盘列数引脚导入
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, row, col); // 矩阵键盘map与引脚对应设置
// WiFi连接
void setup_wifi() {
WiFi.mode(WIFI_STA); // 设置ESP32为station模式
Serial.print("Connecting to "); // 打印提示信息
Serial.print(ssid); // 打印WiFi网络的SSID
WiFi.begin(ssid, password); // 连接到WiFi网络,需要提供SSID和密码
while (WiFi.status() != WL_CONNECTED) { // 等待连接成功
delay(500); // 延时500毫秒
Serial.print("."); // 打印点号
if (WiFi.status() == WL_NO_SSID_AVAIL) { // 如果没有找到
Serial.println("\n无可用WiFi,即将进入本地控制模式...");
delay(1000);
return;
}
}
Serial.println(""); // 打印空行
Serial.println("WiFi Connected."); // 打印提示信息
// OLED显示"连接成功"提示消息
display.clearDisplay();
display.setFont(&FreeSerif9pt7b);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 15);
display.println("WiFi Connected.");
display.display();
Serial.print("IP address: "); // 打印提示信息
Serial.println(WiFi.localIP()); // 打印WiFi网络分配给ESP32的IP地址
delay(1000); // 1秒后进入系统
}
void callback(char *topic, byte *payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// 远程控制
if (strcmp(topic, topic2) == 0) {
int message = atoi((char *)payload);
if (message == 1) {
mode = 1; // 自动模式
Serial.println("Auto Control");
} else {
mode = 0; // 手动模式
if (message == 4) {
Serial.println("LED ON");
} else if (message == 7) {
Serial.println("LED OFF");
} else if (message == 5) {
Serial.println("FAN ON");
} else if (message == 8) {
Serial.println("FAN OFF");
}
}
controlSignal((char)(message + '0')); // 交由controlSignal函数控制设备
}
// 最高温度
if (strcmp(topic, topic_tmax) == 0) {
tem_max = atoi((char *)payload);
Serial.print("已设置最高温度: ");
Serial.print(tem_max);
Serial.println(" ℃");
}
// 最低温度
if (strcmp(topic, topic_tmin) == 0) {
tem_min = atoi((char *)payload);
Serial.print("已设置最低温度: ");
Serial.print(tem_min);
Serial.println(" ℃");
}
// 最高温度
if (strcmp(topic, topic_hmax) == 0) {
hum_max = atoi((char *)payload);
Serial.print("已设置最高湿度: ");
Serial.print(hum_max);
Serial.println(" %");
}
// 最低温度
if (strcmp(topic, topic_hmin) == 0) {
hum_min = atoi((char *)payload);
Serial.print("已设置最低湿度: ");
Serial.print(hum_min);
Serial.println(" %");
}
// 最高光强
if (strcmp(topic, topic_lmax) == 0) {
light_max = atoi((char *)payload);
Serial.print("已设置最高光强: ");
Serial.print(light_max);
Serial.println(" %");
}
// 最低光强
if (strcmp(topic, topic_lmin) == 0) {
light_min = atoi((char *)payload);
Serial.print("已设置最低光强: ");
Serial.print(light_min);
Serial.println(" %");
}
}
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect("group13")) {
Serial.println("connected");
// 订阅主题
client.subscribe(topic1);
client.subscribe(topic2);
client.subscribe(topic3);
client.subscribe(topic_tmax);
client.subscribe(topic_tmin);
client.subscribe(topic_hmax);
client.subscribe(topic_hmin);
client.subscribe(topic_lmax);
client.subscribe(topic_lmin);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
// 初始化RGB-LED
pinMode(R, OUTPUT);
pinMode(G, OUTPUT);
pinMode(B, OUTPUT);
pinMode(PIN_FAN, OUTPUT); // 风扇
pinMode(buzzer, OUTPUT); // 蜂鸣器
digitalWrite(PIN_FAN, HIGH); // 默认关闭风扇
servo1.attach(servoPin); // 伺服电机
// 初始化矩阵键盘引脚
for (int i = 0; i < row; i++) {
pinMode(rowPins[i], OUTPUT);
pinMode(rowPins[i], OUTPUT);
}
// 初始化OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
;
}
display.clearDisplay();
display.setTextSize(1); // 字体大小
display.setTextColor(WHITE); // 字体颜色
display.setCursor(0, 30); // 显示位置
display.drawBitmap(32, 12, huan, 16, 16, 1);
display.drawBitmap(48, 12, ying, 16, 16, 1);
display.drawBitmap(64, 12, jin, 16, 16, 1);
display.drawBitmap(80, 12, ru, 16, 16, 1);
display.drawBitmap(0, 30, zhi, 16, 16, 1);
display.drawBitmap(16, 30, neng, 16, 16, 1);
display.drawBitmap(32, 30, jia, 16, 16, 1);
display.drawBitmap(48, 30, ting, 16, 16, 1);
display.drawBitmap(64, 30, guan, 16, 16, 1);
display.drawBitmap(80, 30, li, 16, 16, 1);
display.drawBitmap(96, 30, xi, 16, 16, 1);
display.drawBitmap(112, 30, tong, 16, 16, 1);
display.display();
// 连接WiFi
setup_wifi();
// 温湿度
dht.begin();
// MQTT
client.setServer(mqtt_server, mqtt_port);
client.setKeepAlive(60);
client.setCallback(callback);
}
void loop() {
// 连接MQTT
if (WiFi.status() == WL_CONNECTED) {
if (!client.connected()) {
reconnect();
}
}
unsigned long currentMillis = millis();
// 收集温湿光度的时间
if (currentMillis - previousMillis >= 3000) {
previousMillis = currentMillis;
// 获取温湿光度
t = dht.readTemperature();
h = dht.readHumidity();
l = analogRead(LIGHT_SENSOR_PIN);
if (isnan(t) || isnan(h) || isnan(l)) {
Serial.println(F("Failed to read from sensor!"));
return;
}
Serial.print(F("Temperature: "));
Serial.print(t);
Serial.print(F("℃ Humidity: "));
Serial.print(h);
Serial.print(F("% Light: "));
Serial.println(l);
showDetails(t, h, l); // 更新OLED屏幕
uploadMsg(t, h, l, status); // 上传数据
}
// 自动模式
if (mode == 1) {
if (t >= tem_max) {
status = 5;
digitalWrite(PIN_FAN, LOW); // 超过最高温度打开风扇
fanStatus = "ON";
setBuzzer(); // 蜂鸣器报警
} else if (t < tem_max) {
status = 8;
digitalWrite(PIN_FAN, HIGH); // 关闭风扇
fanStatus = "OFF";
}
if (l <= light_min) {
status = 4;
digitalWrite(R, HIGH); // 低于最低亮度点亮白灯
digitalWrite(G, HIGH);
digitalWrite(B, HIGH);
ledStatus = "ON";
servoTargetPos = 180; // 打开窗帘
servoIncreasing = true;
curtainStatus = "ON";
setBuzzer(); // 蜂鸣器报警
} else if (l > light_min) {
status = 7;
digitalWrite(R, LOW); // 熄灭白灯
digitalWrite(G, LOW);
digitalWrite(B, LOW);
ledStatus = "OFF";
}
if (h >= hum_max || h <= hum_min) {
status = 4;
digitalWrite(R, HIGH); // 超过最高湿度或低于最低湿度点亮红灯
digitalWrite(G, LOW);
digitalWrite(B, LOW);
ledStatus = "ON";
setBuzzer(); // 蜂鸣器报警
} else {
status = 7;
digitalWrite(R, LOW); // 熄灭红灯
digitalWrite(G, LOW);
digitalWrite(B, LOW);
ledStatus = "OFF";
}
showDetails(t, h, l);
// uploadStatus(status);
}
// 矩阵键盘按键获取
char customKey = customKeypad.getKey();
if (customKey == 'A') { // 按键"A"重新连接WiFi
Serial.println("正在重新连接WiFi...");
WiFi.disconnect();
setup_wifi();
}
if (customKey == 'B') { // 按键"B"查询天气
Serial.println("正在查询天气...");
getWeather();
}
controlSignal(customKey);
updateServo(currentMillis); // 开关窗帘
client.loop();
}
// 蜂鸣器
void setBuzzer() {
for (int i = 0; i < 100; i++) {
digitalWrite(buzzer, HIGH);
delay(1);
digitalWrite(buzzer, LOW);
delay(1);
}
}
// 查询天气
void getWeather() {
String request_url = url;
request_url += "?key=" + String(Key);
request_url += "&location=" + String(location);
request_url += "&language=en";
request_url += "&unit=c";
if (http.begin(request_url)) {
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println("Response: " + payload);
deserializeJson(doc, payload);
JsonObject obj = doc["results"][0]["location"];
String name = obj["name"];
String weather = doc["results"][0]["now"]["text"];
String temperature = doc["results"][0]["now"]["temperature"];
String lastUpdate = doc["results"][0]["last_update"];
// 提取年、月、日
String year = lastUpdate.substring(0, 4);
String month = lastUpdate.substring(5, 7);
String day = lastUpdate.substring(8, 10);
Serial.print("位置: ");
Serial.println(name);
Serial.print("天气: ");
Serial.println(weather);
Serial.print("温度: ");
Serial.println(temperature + "°C");
// OLED显示天气信息
display.clearDisplay();
display.setFont(&FreeSerif9pt7b);
display.setTextSize(1); // 字体大小
display.setTextColor(WHITE); // 字体颜色
display.setCursor(0, 15); // 显示位置
display.println(year + "-" + month + "-" + day);
display.println(name);
display.println(weather + " - " + temperature + " C");
display.display();
delay(2000);
} else {
Serial.println("Request failed, error code:" + String(httpCode));
}
http.end();
} else {
Serial.println("Unable to connect");
}
}
// 控制信号
void controlSignal(char customKey) {
if (customKey) { // 矩阵键盘判断
Serial.println(customKey);
if (customKey == '1') {
status = 1;
mode = 1;
Serial.println("当前为自动模式!");
} else {
switch (customKey) {
case '0':
status = 0;
mode = 0;
break;
case '4':
status = 4;
mode = 0;
digitalWrite(R, HIGH); // 点亮白灯
digitalWrite(G, HIGH);
digitalWrite(B, HIGH);
ledStatus = "ON";
break;
case '7':
status = 7;
mode = 0;
digitalWrite(R, LOW); // 熄灭白灯
digitalWrite(G, LOW);
digitalWrite(B, LOW);
ledStatus = "OFF";
break;
case '5':
status = 5;
mode = 0;
digitalWrite(PIN_FAN, LOW); // 打开风扇
fanStatus = "ON";
break;
case '8':
status = 8;
mode = 0;
digitalWrite(PIN_FAN, HIGH); // 关闭风扇
fanStatus = "OFF";
break;
case '6':
status = 6;
mode = 0;
servoTargetPos = 180; // 打开窗帘
servoIncreasing = true;
curtainStatus = "ON";
break;
case '9':
status = 9;
mode = 0;
servoTargetPos = 0; // 关闭窗帘
servoIncreasing = false;
curtainStatus = "OFF";
break;
default:
break;
}
}
showDetails(t, h, l); // 按键事件处理完后立即更新显示
uploadStatus(status);
}
}
// OLED屏幕显示信息
void showDetails(float t, float h, int l) {
display.clearDisplay();
display.setFont();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
// 显示温度
display.print("Temperature: ");
display.print(t);
display.println(" C");
// 显示湿度
display.print("Humidity: ");
display.print(h);
display.println(" %");
// 显示光强
display.print("Light: ");
display.print(l);
display.println(" lx");
// 显示当前模式
display.print("Mode: ");
if (mode == 1) {
display.println("Auto");
} else {
display.println("Manual");
}
// 显示状态
display.println("---------------------");
display.print("Led: ");
display.println(ledStatus);
display.print("Fan: ");
display.println(fanStatus);
display.print("Curtain: ");
display.println(curtainStatus);
display.display();
}
// 发送温湿光度JSON数据
void uploadMsg(float t, float h, int l, int s) {
snprintf(message, 70, "{\"temperature\":%.2f,\"humidity\":%.2f,\"light\":%d,\"status\":%d}", t, h, l, s); // 数据JSON格式化
client.publish(topic1, message); // 发送数据
}
// 发送状态JSON数据,用于同步更新网页开关UI
void uploadStatus(int s) {
snprintf(ui_status, 15, "{\"status\":%d}", s); // 数据JSON格式化
client.publish(topic3, ui_status); // 发送数据
}
// 伺服电机模拟窗帘
void updateServo(unsigned long currentMillis) {
if (servoTargetPos != -1) { // 检查是否设置了目标位置
if (currentMillis - servoLastUpdate >= servoInterval) {
servoLastUpdate = currentMillis;
if (servoIncreasing) {
if (servoPos < servoTargetPos) {
servoPos++;
servo1.write(servoPos);
} else {
servoTargetPos = -1; // 目标位置已达到,停止更新
}
} else {
if (servoPos > servoTargetPos) {
servoPos--;
servo1.write(servoPos);
} else {
servoTargetPos = -1; // 目标位置已达到,停止更新
}
}
}
}
}