如何在ESP32上解耦Web服务器与MQTT发布任务
问题与解决方案:ESP32-POE Web服务器与MQTT任务解耦
问题背景
使用ESP32-POE开发板同时运行Web服务器和MQTT传感器数据发布,需求是每60秒发布一次MQTT数据。但在loop中加入delay(60000)会阻塞server.handleClient()执行,导致浏览器请求偶尔超时。尝试ESPAsyncWebServer时遇到与ETH.h、ESP32 Arduino core 3.x的兼容性问题,需用millis()替代delay()实现任务解耦。
实现思路
利用millis()记录上次MQTT发布的时间戳,在loop循环中持续检查当前时间与上次时间的差值,当差值达到设定的60秒间隔时才执行MQTT发布操作。这种方式不会阻塞主循环,server.handleClient()可以持续处理HTTP请求,避免超时。
修改后的完整代码
char title[] = "<location> sensor"; #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <DHT.h> #include "AsyncUDP.h" #include <PubSubClient.h> // ETH.h precursors #ifndef ETH_PHY_TYPE #define ETH_PHY_TYPE ETH_PHY_LAN8720 // Type of the Ethernet PHY (LAN8720 or TLK110) #define ETH_PHY_ADDR 0 // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110) #define ETH_PHY_MDC 23 // Pin# of the I²C clock signal for the Ethernet PHY #define ETH_PHY_MDIO 18 // Pin# of the I²C IO signal for the Ethernet PHY #define ETH_PHY_POWER -1 #define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source) #endif #include <ETH.h> static bool eth_connected = false; //Set up DHT11 DHT dht(4, DHT11); //Start Webserver WebServer server(80); // MQTT定时发布相关变量 const unsigned long MQTT_PUBLISH_INTERVAL = 60000; // 60秒间隔 unsigned long lastPublishTime = 0; // 上次发布的时间戳 //HTML void handleRoot() { char msg[1500]; snprintf(msg, 1500, "<html>\ <head>\ <meta http-equiv='refresh' content='60'/>\ <meta name='viewport' content='width=device-width, initial-scale=1'/>\ </head>\ <body>\ <h3> %s </h3>\ <p>\ <span>Temperature</span>\ <span>%.2f</span>\ <sup>°C</sup>\ </p>\ <p>\ <span>Humidity</span>\ <span>%.2f</span>\ <sup>%</sup>\ </p>\ <p>\ <span>Dew Point</span>\ <span>%.2f</span>\ <sup>°C</sup>\ </p>\ </body>\ </html>", title, readDHTTemperature(), readDHTHumidity(), readDewPoint() ); server.send(200, "text/html", msg); } // Set up ethernet event handler (optional) void onEvent(arduino_event_id_t event) { switch (event) { case ARDUINO_EVENT_ETH_START: ETH.setHostname("esp32-ethernet"); break; case ARDUINO_EVENT_ETH_CONNECTED: Serial.println("ETH Connected"); break; case ARDUINO_EVENT_ETH_GOT_IP: eth_connected = true; break; case ARDUINO_EVENT_ETH_LOST_IP: eth_connected = false; break; case ARDUINO_EVENT_ETH_DISCONNECTED: eth_connected = false; break; case ARDUINO_EVENT_ETH_STOP: eth_connected = false; break; default: break; } } // MQTT info const char *mqtt_broker = "broker.emqx.io"; char topic_temp[64]; char topic_hum[64]; const char *mqtt_username = "emqx"; const char *mqtt_password = "public"; const int mqtt_port = 1883; WiFiClient ethClient; PubSubClient client(ethClient); void setup() { //initialise DHT sensor dht.begin(); // Serial.begin(115200); //Initialise serial for debugging //Initialise Ethernet ETH.onEvent(onEvent); ETH.begin(); // //Initialise WiFi for debugging // const char *ssid = ""; // const char *password = ""; // WiFi.mode(WIFI_STA); // WiFi.begin(ssid, password); //Create MQTT topic strings String MacAsString = ETH.macAddress(); MacAsString.replace(":", ""); //remove colons from default output snprintf(topic_temp, sizeof(topic_temp), "sensor/%s/1/temperature", MacAsString.c_str()); snprintf(topic_hum, sizeof(topic_hum), "sensor/%s/1/humidity", MacAsString.c_str()); //Initialise web server server.on("/", handleRoot); server.begin(); //Connect to the MQTT broker client.setServer(mqtt_broker, mqtt_port); reconnectMQTT(); } char tpayload[10]; //define buffers for client.publish char hpayload[10]; void loop(void){ // 持续处理HTTP请求,无阻塞 server.handleClient(); // 检查MQTT连接状态,断开则重连 if (!client.connected()) { reconnectMQTT(); } client.loop(); // 处理MQTT客户端内部事务 // 检查是否到了MQTT发布时间 unsigned long currentTime = millis(); if (currentTime - lastPublishTime >= MQTT_PUBLISH_INTERVAL) { lastPublishTime = currentTime; // 更新上次发布时间 // 读取传感器数据并发布 dtostrf(readDHTTemperature(), 6, 2, tpayload); dtostrf(readDHTHumidity(), 6, 2, hpayload); client.publish(topic_temp, tpayload); client.publish(topic_hum, hpayload); } } void reconnectMQTT() { while (!client.connected()) { if (client.connect("ESP32_ETH_Client", mqtt_username, mqtt_password)) { //Serial.println("connected"); } else { //Serial.print("failed, rc="); //Serial.print(client.state()); //Serial.println(" trying again in 2 seconds"); delay(2000); } } } // 补充原代码中缺失的传感器读取函数 float readDHTTemperature() { return dht.readTemperature(); } float readDHTHumidity() { return dht.readHumidity(); } float readDewPoint() { return dht.computeDewPoint(dht.readTemperature(), dht.readHumidity()); }
关键修改点
- 添加
MQTT_PUBLISH_INTERVAL和lastPublishTime全局变量,用于控制发布间隔和记录上次发布时间 - 移除
loop中的delay(60000),替换为基于millis()的时间差判断,避免阻塞主循环 - 在
loop中增加client.loop()调用,确保MQTT客户端能处理订阅消息、心跳等内部事务 - 补充原代码中缺失的
readDHTTemperature()、readDHTHumidity()、readDewPoint()函数,保证代码可编译运行
内容的提问来源于stack exchange,提问作者Spags
相关产品推荐
相关产品推荐

