实现MQTT协议接入onenet云平台----ESP32

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)

  • 1999 年由 IBM 开发,轻量级、发布 / 订阅模式的物联网专用传输协议;
  • 基于 TCP/IP,也支持 WebSocket(网页端 MQTT),专为带宽小、算力弱、网络不稳定的嵌入式设备设计(ESP32/STM32、传感器、单片机)。

区别于 HTTP 一问一答的点对点,MQTT 引入Broker(消息服务器 / 中间件) 做中转,解耦发送方与接收方:

  1. 发布者 Publisher:设备产生数据(传感器上报温湿度、硬件上报状态),向主题发送消息;
  2. Broker 服务端:接收、缓存、转发消息,管理所有客户端连接与订阅关系;
  3. 订阅者 Subscriber:客户端订阅指定主题,Broker 收到对应消息后自动推送给它;
  4. 主题 Topic :消息的分类标识,分层字符串(用/分隔,类似路径),例: device/esp32_001/tempplatform/one_net/control

MQTT通信流程

  1. 所有客户端先和 Broker 建立 TCP 长连接;
  2. 订阅者提前订阅感兴趣的 Topic;
  3. 发布者往 Topic 发消息;
  4. Broker 匹配订阅关系,把消息推送给所有订阅该 Topic 的客户端;
  5. 订阅者收到消息后本地处理。

MQTT规定了14种报文包类型

  1. 连接请求(CONNECT):设备向云发起连接登录。
  2. 连接应答(CONNACK):云端响应CONNECT,告知登录成功/失败。
  3. 发布消息(PUBLISH):双向通用,设备上报数据,云端下发指令。主要任务:a.提取topic,载荷payload。b.区分云端业务数据,解析JSON存业务变量。c.根据QOS等级回复握手包。(QOS0无需应答,QOS1回复PUBACK,QOS2回复PUBREC)
  4. 订阅主题(SUBSCRIBE):订阅主题,设备主动订阅下发主题。该条报文仅设备发送,云端回复 SUBACK;设备不会接收该包。
  5. 取消订阅(UBSBUCRIBE):设备主动取消订阅主题。
  6. 订阅应答(SUBACK):云端确认订阅结果。
  7. 取消订阅应当(UNSUBACK):云端确认取消订阅结果。
  8. PUBACK(发布确认QOS1):QOSI消息级别应答。
  9. PUBREC(发布收到,QOS2第一步):QOS2握手第一段。
  10. PUBERL(发布释放,QOS2第二步):QOS2握手第二段。
  11. PUBCOMP(发布完成,QOS2第三步):QOS2握手收尾。
  12. PINGREQ(心跳请求):设备定时发送保活心跳。
  13. PINGRESP(心跳响应):云端回复心跳,链路正常。
  14. DISCONNECED(断开连接):设备主动下发离线通知。

接下来具体看ESP32官方如何实现mqtt协议。

协议接入流程如下

1.配置mqtt结构体,并将该配置初始化。

在配置结构体中,有以上参数可配置。

最小配置参数如下,最必要的参数就是你要接入的网址端口。(uri配置为你要接入的网址端口)其余参数比如verification和credentials(校验层相关参数)可按需自行配置。接下来需要创建一个mqtt客户端,并将该参数初始化到该客户端中,注册事件处理函数实时监控mqtt事件的发生,最后开启mqtt客户端即可。

cs 复制代码
const esp_mqtt_client_config_t mqtt_cfg = {
    .broker.address.uri = "mqtt://mqtt.eclipseprojects.io",
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
esp_mqtt_client_start(client);

这里再详细介绍一下结构体esp_mqtt_client_config_t中各参数。

broker.address:用代理相关的配置(比如服务器网址端口)

broker.verification:用于ESP32验证mqtt服务器身份,防止连接到假服务器。(单向TLS认证)

credentials:客户端凭证层,用于服务器校验ESP32身份,服务器根据此配置来判断是否允许设备接入topic集群。

cs 复制代码
//普通账号密码认证
credentials.username = "设备用户名";
credentials.authentication.password = "设备密码/签名密钥";
credentials.client_id = "自定义设备ID";

对应 MQTT CONNECT 报文中的 ClientID、Username、Password 字段,明文或 TLS 加密后发给 Broker 鉴权。

cs 复制代码
//双向TLS客户端根证书认证
credentials.authentication.certificate = 设备客户端证书;
credentials.authentication.key = 设备私钥;

对应双向 TLS(Mutual TLS):TLS 握手时 ESP32 把自己的设备证书发给 Broker,服务器校验设备合法性,属于 TLS 层客户端鉴权。

可以看到,在ESP32中,规定了八个mqtt事件,因此,在mqtt_event_handler事件处理函数中,就可以处理这些事件。

cs 复制代码
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
        ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;

    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d, return code=0x%02x ", event->msg_id, (uint8_t)*event->data);
        msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);
        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno",  event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));

        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}