ESP32MQTTClient 开发回顾

2025-03-26 · 返回首页

项目背景

2022 年的时候,我在做一个智能家居相关的项目,需要在 ESP32 上使用 MQTT 协议与后端通信。当时找了一圈现有的 Arduino 库,发现存在几个问题:

于是我决定基于 ESP-IDF 原生的 MQTT 组件,封装一个简单易用的 Arduino 库。

技术选型

ESP-IDF 从 4.x 升级到 5.x 后,MQTT 客户端 API 有了较大变化。我对比了几种方案:

方案 优点 缺点
PubSubClient 轻量、广泛使用 未适配 ESP32 Core 3.x,功能较简单
AsyncMqttClient 异步非阻塞 依赖旧版 AsyncTCP,维护停滞
esp-mqtt (IDF) 官方维护,功能完整 API 较底层,需要封装

最终选择了基于 esp-mqtt 进行封装,这样既能保证功能完整,又能提供简洁的 Arduino 风格 API。

线程安全的设计

ESP32 是双核处理器,经常在 FreeRTOS 的多任务环境下使用。原生的 esp-mqtt 虽然是线程安全的,但在 Arduino 层面的封装需要考虑更多问题。

互斥锁的使用

对于可能被多个任务同时调用的方法,我添加了 FreeRTOS 互斥锁保护:

bool ESP32MQTTClient::publish(const char* topic, const char* payload, uint8_t qos, bool retain) {
    if (_mutex == NULL) return false;
    
    // 获取互斥锁,最多等待 1 秒
    if (xSemaphoreTake(_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
        log_w("Failed to take mutex");
        return false;
    }
    
    esp_mqtt_client_publish(_client, topic, payload, 0, qos, retain);
    
    xSemaphoreGive(_mutex);
    return true;
}

回调函数的上下文

MQTT 事件回调在中断上下文中执行,不能直接调用阻塞操作。我将事件放入队列,在 loop() 中统一处理:

void ESP32MQTTClient::loop() {
    MQTTEvent event;
    while (xQueueReceive(_eventQueue, &event, 0) == pdTRUE) {
        switch (event.type) {
            case MQTT_EVENT_CONNECTED:
                if (_onConnect) _onConnect();
                break;
            case MQTT_EVENT_DATA:
                if (_onMessage) {
                    _onMessage(event.topic, event.payload);
                }
                break;
            // ...
        }
    }
}

适配 ESP-IDF 5.x

ESP-IDF 5.x 相对于 4.x 有一些破坏性变更,主要集中在:

  1. 头文件路径变更mqtt_client.h 的位置从 esp-mqtt/mqtt_client.h 改为 mqtt_client.h
  2. 配置结构体变化:部分字段被移除或重命名
  3. 证书处理:TLS 相关 API 有调整

我通过条件编译来兼容两个版本:

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
    #include "mqtt_client.h"
#else
    #include "esp-mqtt/mqtt_client.h"
#endif

使用示例

最终封装后的使用方式非常简单:

#include 

ESP32MQTTClient mqtt;

void setup() {
    mqtt.setURI("mqtt://broker.hivemq.com:1883");
    mqtt.setOnMessageCallback([](String topic, String payload) {
        Serial.println("Received: " + topic + " = " + payload);
    });
    mqtt.begin();
}

void loop() {
    mqtt.loop();
    mqtt.publish("test/topic", "hello");
    delay(1000);
}

社区反馈

开源后陆续收到了一些 Issue 和 PR,主要关注点:

总结

这个项目让我学到了很多关于 ESP32 和 MQTT 的知识,特别是多线程编程和跨版本兼容性处理。目前该库已经获得 40+ Star,被用于多个实际项目中。

项目地址:https://github.com/cyijun/ESP32MQTTClient

← 返回首页