#include <Wire.h>
#include "Adafruit_SHT4x.h"
#include <Adafruit_SGP30.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <U8g2lib.h>
#include <PMS.h>
#include <ArduinoJson.h>
// PMS7103 串口定义
HardwareSerial pmsSerial(2); // 使用 ESP32 的 Serial2
PMS pms(pmsSerial);
PMS::DATA pms_data;
#define I2C_SDA 42
#define I2C_SCL 41
#define MQ7_PIN 1
#define MQ2_PIN 2
#define MQ3_PIN 4
#define PMS_TX 17
#define PMS_RX 18
// 设备状态标志(新增)
bool sht40_connected = true;
bool sgp30_connected = true;
bool oled_connected = true;
bool pms_connected = false;
unsigned long lastScreenSwitch = 0;
bool showFirstScreen = true; // 标记当前显示哪一屏
// WiFi 设置
const char* ssid = "66668888";
const char* password = "666888111";
// MQTT 设置
const char* mqtt_server = "120.27.203.178";
const int MQTT_PORT = 1883;
const char* mqtt_user = "admin";
const char* mqtt_pass = "Zhu131420";
const char* mqtt_client_id = "空气质量监测";
// 创建对象
WiFiClient espClient;
PubSubClient client(espClient);
Adafruit_SHT4x sht4 = Adafruit_SHT4x();
Adafruit_SGP30 sgp30;
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, I2C_SCL, I2C_SDA);
// 使用软串口读取 PMS7103 数据
//SoftwareSerial pmsSerial(PMS_RX, PMS_TX);
//PMS pms(pmsSerial);
//PMS::DATA pms_data;
// 记录上次重启时间
unsigned long lastRestartTime = 0;
unsigned long lastMQTTAttempt = 0;
unsigned long lastWiFiAttempt = 0;
bool isOLEDConnected() {
Wire.beginTransmission(0x3C); // 尝试OLED默认地址0x3C
byte error = Wire.endTransmission();
return (error == 0);
}
void connectWiFi() {
if (millis() - lastWiFiAttempt >= 20000) {
Serial.println("尝试连接WiFi...");
WiFi.disconnect();
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi已连接");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi连接失败,20秒后重试");
}
lastWiFiAttempt = millis();
}
}
void checkWiFi() {
if (WiFi.status() != WL_CONNECTED) {
connectWiFi(); // 复用连接逻辑
}
}
void connectMQTT() {
if (millis() - lastMQTTAttempt >= 20000) {
Serial.println("尝试连接MQTT...");
if (client.connect(mqtt_client_id, mqtt_user, mqtt_pass)) {
Serial.println("MQTT已连接");
client.publish("homeassistant/sensor/esp32_air_quality/status", "online");
} else {
Serial.printf("MQTT连接失败, rc=%d,20秒后重试\n", client.state());
}
lastMQTTAttempt = millis();
}
}
void checkMQTT() {
if (!client.connected()) {
connectMQTT(); // 复用连接逻辑
} else {
client.loop();
}
}
void sendMQTTData(float temperature, float humidity, uint16_t eCO2, uint16_t TVOC,
float mq7_value, float mq2_value, float mq3_value, int pm25_value) {
// 创建动态JSON文档
DynamicJsonDocument doc(256);
// 只添加有效的数据
if (!isnan(temperature)) {
doc["temperature"] = temperature;
} else {
doc["temperature"] = nullptr; // 或者可以省略这一行
}
if (!isnan(humidity)) {
doc["humidity"] = humidity;
} else {
doc["humidity"] = nullptr;
}
if (eCO2 > 0) { // SGP30返回0表示错误
doc["eCO2"] = eCO2;
} else {
doc["eCO2"] = nullptr;
}
if (TVOC >= 0) {
doc["TVOC"] = TVOC;
} else {
doc["TVOC"] = nullptr;
}
// MQ传感器总是有值(模拟输入)
doc["MQ7"] = mq7_value;
doc["MQ2"] = mq2_value;
doc["MQ3"] = mq3_value;
// PM2.5
if (pm25_value >= 0) { // PMS7103返回-1表示错误
doc["PM2_5"] = pm25_value;
} else {
doc["PM2_5"] = nullptr;
}
// 添加设备状态
doc["sht40_status"] = sht40_connected ? "online" : "offline";
doc["sgp30_status"] = sgp30_connected ? "online" : "offline";
// 序列化并发送JSON
char payload[256];
serializeJson(doc, payload);
client.publish("homeassistant/sensor/esp32_air_quality/data", payload);
}
void mqttCallback(char* topic, byte* message, unsigned int length) {
// 处理MQTT消息
}
void displayData(float temperature, float humidity, uint16_t eCO2, uint16_t TVOC,
float mq7_value, float mq2_value, float mq3_value, int pm25_value) {
// 每2秒切换一次屏幕
if (millis() - lastScreenSwitch > 4000) {
showFirstScreen = !showFirstScreen;
lastScreenSwitch = millis();
}
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_wqy12_t_gb2312);
if (showFirstScreen) {
u8g2.setCursor(0, 15);
u8g2.printf("温度: %.1f°C", temperature);
u8g2.setCursor(0, 30);
u8g2.printf("湿度: %.1f%%", humidity);
u8g2.setCursor(0, 45);
u8g2.printf("eCO2: %uppm", eCO2);
u8g2.setCursor(0, 60);
u8g2.printf("TVOC: %uppb", TVOC);
u8g2.setCursor(0, 75);
u8g2.print("1/2");
} else {
u8g2.setCursor(0, 15);
u8g2.printf("CO: %.2f ppm", mq7_value);
u8g2.setCursor(0, 30);
u8g2.printf("酒精: %.2f ppm", mq3_value);
u8g2.setCursor(0, 45);
u8g2.printf("可燃气体: %.2f ppm", mq2_value);
u8g2.setCursor(0, 60);
u8g2.printf("PM2.5: %d ug/m3", pm25_value);
u8g2.setCursor(0, 75);
u8g2.print("2/2");
}
u8g2.sendBuffer();
}
void setup() {
Serial.begin(115200);
// 初始化I2C总线
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(400000);
Wire.setTimeout(1000);
// 初始化 PMS7103
pmsSerial.begin(9600, SERIAL_8N1, PMS_RX, PMS_TX);
pms.activeMode(); // 如果你希望手动读取
delay(100);
if (!isOLEDConnected()) {
oled_connected = false;
Serial.println("OLED未连接!");
} else {
u8g2.begin();
if(u8g2.getDisplayWidth() == 0) {
oled_connected = false;
Serial.println("OLED初始化失败");
} else {
u8g2.enableUTF8Print();
u8g2.setFont(u8g2_font_wqy12_t_gb2312);
u8g2.clearBuffer();
u8g2.drawStr(0, 20, "Be initializing...");
u8g2.sendBuffer();
oled_connected = true;
}
}
pinMode(MQ2_PIN, INPUT);
pinMode(MQ3_PIN, INPUT);
pinMode(MQ7_PIN, INPUT);
// 连接 WiFi
connectWiFi();
// 连接 MQTT
client.setServer(mqtt_server, MQTT_PORT);
client.setKeepAlive(60);
client.setCallback(mqttCallback);
connectMQTT();
// 初始化I2C设备
sht40_connected = sht4.begin();
if(sht40_connected) {
sht4.setPrecision(SHT4X_HIGH_PRECISION);
sht4.setHeater(SHT4X_NO_HEATER);
Serial.println("SHT40初始化成功");}
else{
Serial.println("SHT40初始化失败");
}
sgp30_connected = sgp30.begin();
if(sgp30_connected) {
sgp30.IAQinit();
} else {
Serial.println("SGP30初始化失败");
}
// 发送设备上线消息
client.publish("homeassistant/sensor/esp32_air_quality/status", "online");
lastRestartTime = millis();
}
void loop() {
checkWiFi();
checkMQTT();
// 读取传感器数据
float temperature = NAN;
float humidity = NAN;
uint16_t eCO2 = 0;
uint16_t TVOC = 0;
// 读取 PMS7103 数据
int pm25_value = -1;
pms.wakeUp();
delay(100);
if (sht40_connected) {
sensors_event_t temp_event, humidity_event;
if (sht4.getEvent(&humidity_event, &temp_event)) {
temperature = round(temp_event.temperature * 100) / 100.0;
humidity = round(humidity_event.relative_humidity * 100) / 100.0;
} else {
sht40_connected = false;
Serial.println("SHT40读取失败");
}
}
if(sgp30_connected) {
if(!sgp30.IAQmeasure()) {
sgp30_connected = false;
Serial.println("SGP30读取失败");
} else {
eCO2 = sgp30.eCO2;
TVOC = sgp30.TVOC;
}
}
int raw_mq7 = analogRead(MQ7_PIN);
float mq7_value = round((raw_mq7 / 4095.0f) * 330.0f * 100) / 100.0 ;
int raw_mq2 = analogRead(MQ2_PIN);
float mq2_value = round((raw_mq2 / 4095.0f) * 330.0f * 100) / 100.0 ;
int raw_mq3 = analogRead(MQ3_PIN);
float mq3_value = round((raw_mq3 / 4095.0f) * 330.0f * 100) / 100.0 ;
if (pms.readUntil(pms_data)) {
pm25_value = pms_data.PM_AE_UG_2_5;
} else {
Serial.println("PMS7103 数据读取失败");
}
pms.sleep();
// 串口输出(显示设备状态)
Serial.printf("设备状态: SHT31-%s SGP30-%s OLED-%s ",
sht40_connected?"正常":"故障",
sgp30_connected?"正常":"故障",
oled_connected?"正常":"故障");
// 在串口输出数据
Serial.printf("温度: %.2f °C, 湿度: %.2f %%\n", temperature, humidity);
Serial.printf("eCO2: %u ppm, TVOC: %u ppb\n", eCO2, TVOC);
Serial.printf("MQ-2 可燃气体: %.2f ppm\n", mq2_value);
Serial.printf("MQ-3 酒精: %.2f ppm\n", mq3_value);
Serial.printf("MQ-7 CO 浓度: %.2f ppm\n", mq7_value);
Serial.printf("PM2.5 浓度: %d ug/m³\n", pm25_value);
Serial.printf("Free Heap: %d\n", ESP.getFreeHeap());
Serial.println("----------------------------------");
sendMQTTData(temperature, humidity, eCO2, TVOC, mq7_value, mq2_value, mq3_value, pm25_value);
displayData(temperature, humidity, eCO2, TVOC, mq7_value, mq2_value, mq3_value, pm25_value);
//delay(1000);
}