You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>&deg;C</sup>\
      </p>\
      <p>\
        <span>Humidity</span>\
        <span>%.2f</span>\
        <sup>&percnt;</sup>\
      </p>\
      <p>\
        <span>Dew Point</span>\
        <span>%.2f</span>\
        <sup>&deg;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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 11:45:58