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

ESP32能否同时作为ESP-NOW网关与MQTT客户端?求实现方案

问题:ESP32同时实现ESP-NOW网关与MQTT连接的冲突解决

项目背景

  • 多个ESP8266从机作为开关,向ESP32网关发送设备ID数据
  • 从机与网关通过ESP-NOW库通信
  • 网关需连接树莓派MQTT服务器,服务器根据设备ID下发自动化指令

当前问题

ESP8266与ESP32的ESP-NOW通信正常,树莓派MQTT Broker配置完成,但开启WiFi连接与MQTT连接代码后,ESP-NOW的OnDataRecv回调功能失效。

解决方案

完全可以实现ESP32同时作为ESP-NOW网关和MQTT客户端,问题出在初始化顺序、阻塞式循环以及中断上下文的不当操作,以下是修复方案:

关键修改点

  1. 调整初始化顺序:先设置WiFi STA模式,再初始化ESP-NOW,确保两者兼容
  2. 移除阻塞式循环:将WiFi连接的while(WiFi.status() != WL_CONNECTED)改为loop中的定时重连,避免阻塞ESP-NOW回调
  3. 中断上下文安全:ESP-NOW回调属于中断函数,禁止使用delay,改用非阻塞逻辑处理LED闪烁
  4. 维持MQTT连接:loop中必须调用MQTTclient.loop(),否则MQTT连接会断开

修改后的完整代码

#include <esp_now.h>
#include <WiFi.h>
#include <PubSubClient.h> 
#define LED 2

int myData = 0;
bool needBlink = false; // 标记是否需要闪烁LED
unsigned long blinkStartTime = 0;
int blinkCount = 0;

char ssid[] = "my ssid";     
char password[] = "mypass";  
char mqtt_server[] = "192.168.1.55";  
WiFiClient espClient;
PubSubClient MQTTclient(espClient);

// ESP-NOW接收回调,中断上下文内禁止阻塞操作
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&myData, incomingData, sizeof(myData));
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.print("data: ");
  Serial.println(myData);

  // 标记需要闪烁LED,避免在回调中使用delay
  needBlink = true;
  blinkCount = 0;
  blinkStartTime = millis();

  // 仅当MQTT连接正常时发布消息
  if(MQTTclient.connected()){
    String response = "device" + String(myData);
    MQTTclient.publish("homecommand", response.c_str());
    Serial.println("MQTT message published: " + response);
  } else {
    Serial.println("MQTT not connected, skip publishing");
  }
}

void MQTTconnect() {
  if (MQTTclient.connected()) return;

  Serial.print("Attempting MQTT connection...");
  String clientId = "ESP32-Gateway-";
  clientId += String(random(0xffff), HEX);

  // 连接无密码的MQTT Broker
  if (MQTTclient.connect(clientId.c_str())) {
    Serial.println("connected");
    // 可选:订阅服务器指令主题
    // MQTTclient.subscribe("home/commands");
  } else {
    Serial.print("failed, rc=");
    Serial.print(MQTTclient.state());
    Serial.println(" try again in 5 seconds");
    delay(5000);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);

  // 先设置WiFi STA模式
  WiFi.mode(WIFI_STA);
  
  // 初始化ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  esp_now_register_recv_cb(OnDataRecv);

  // 启动WiFi连接(非阻塞,在loop中完成)
  WiFi.begin(ssid, password);
  MQTTclient.setServer(mqtt_server, 1883);
}

void loop() {
  // 处理WiFi重连
  if (WiFi.status() != WL_CONNECTED) {
    static unsigned long lastReconnectAttempt = 0;
    if (millis() - lastReconnectAttempt > 5000) {
      lastReconnectAttempt = millis();
      Serial.print("Connecting to WiFi...");
      if (WiFi.reconnect()) {
        Serial.println("connected");
      } else {
        Serial.println("failed");
      }
    }
  }

  // 处理MQTT连接与消息
  if (WiFi.status() == WL_CONNECTED) {
    MQTTconnect();
    MQTTclient.loop(); // 必须调用,维持连接并处理订阅消息
  }

  // 非阻塞式LED闪烁
  if (needBlink) {
    if (millis() - blinkStartTime >= 100) { // 50ms亮+50ms灭,周期100ms
      blinkStartTime = millis();
      digitalWrite(LED, (blinkCount % 2 == 0) ? HIGH : LOW);
      blinkCount++;
      if (blinkCount >= 6) { // 3次闪烁=6次状态切换
        needBlink = false;
        digitalWrite(LED, LOW);
      }
    }
  }
}

代码说明

  • WiFi连接:采用定时重连机制,避免阻塞ESP-NOW的回调处理
  • ESP-NOW回调:仅做数据接收和状态标记,把耗时操作(LED闪烁、MQTT发布)移到loop中处理,保证中断上下文的高效性
  • MQTT维护:MQTTclient.loop()会处理心跳和消息接收,确保连接稳定
  • LED闪烁:使用非阻塞的时间戳方式,替代原有的delay,避免影响其他功能

内容的提问来源于stack exchange,提问作者floprm

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 22:20:26