第二十一章 ESP32开发MQTT Client ESP-IDF

学习目的及目标

  1. 掌握MQTT原理和工作过程
  2. 掌握ESP32的MQTT程序设计

MQTT原理和工作过程讲解

MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个 (服务器)。

通过MQTT协议,目前已经扩展出了数十个MQTT服务器端程序,可以通过PHP,JAVA,Python,C,C#等系统语言来向MQTT发送相关消息。

此外,国内很多企业都广泛使用MQTT作为Android手机客户端与服务器端推送消息的协议。MQTT由于开放源代码,耗电量小等特点。在物联网领域,传感器与服务器的通信,信息的收集,MQTT都可以作为考虑的方案之一。在未来MQTT会进入到我们生活的各各方面。

所以,如果物联网设备想要联网,MQTT是不二选择。

MQTT特点

MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:

  1. 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
  2. 对负载内容屏蔽的消息传输;
  3. 使用 TCP/IP 提供网络连接;
  4. 有三种消息发布服务质量:
    “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。 “至少一次”,确保消息到达,但消息重复可能会发生。 “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。

小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;

这就是为什么在介绍里说它非常适合物联网领域,要知道嵌入式设备的运算能力和带宽都相对薄弱,使用这种协议来传递消息再适合不过了。

实现方式  

实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:

Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);

payload,可以理解为消息的内容,是指订阅者具体要使用的内容。

    MQTT服务器的主要工作是数据分发,没有数据保存功能。 可以订阅自己发布的主题,服务器就是回发测试。 MQTT让逻辑变得更清晰,需要什么订阅什么。 走标准化流程,解放了私有协议制定、实现、调试、测试一整套复杂的流程。

Win7搭建本地MQTT服务器()

目前主流的Broker有以下3个:

  1. Mosquitto:
  2. VerneMQ:
  3. EMQTT: 此处,我们使用搭建EMQTT。

下载MQTT服务器压缩包

解压到电脑

我这里是解压到E盘。

打开终端

进入目录运行MQTT服务

执行成功会弹出下面窗口,不成功就关掉cmd重新试下。

测试MQTT服务器

打开浏览器-> 输入 -> 用户名:admin-> 密码:public-> 进入如下界面

连接MQTT服务器。

这个软件不怎么好用,连不上,关掉再连。使用127.0.0.1也可以。

连上了两个

订阅->发布,就可以数据发送了。

ESP32的MQTT软件设计

ESP32的MQTT详细过程逻辑

MQTT接口介绍

MQTT是基于TCP的,关于是如何封装的,乐鑫已经整理好了,留给我们应用层使用的接口相对较少,内部如何封装,乐鑫怎么封装,标准怎么封装,有时间可以仔细阅读乐鑫源码和标准文档,后面有需要我们会解析下乐鑫封装的那一层,此处不做介绍,只讲解应用层。所谓流程+接口,打遍无敌手。

MQTT配置信息esp_mqtt_client_config_t;

参数原型 esp_mqtt_client_config_t 参数功能 MQTT配置信息 参数 typedef struct { mqtt_event_callback_t event_handle; /*回调*/ const char *host; /*!< MQTT 服务器域名(ipv4 as string)*/ const char *uri; /*!< MQTT 服务器域名 */ uint32_t port; /*!< MQTT服务器端口*/ const char *client_id; /*MQTT Client的名字默认是ESP32_加上MAC后3hex*/ const char *username; /*MQTT用户名*/ const char *password; /*MQTT密码*/ const char *lwt_topic; /*!< LWT主题,默认为空*/ const char *lwt_msg; /*!< LWT信息,默认为空*/ int lwt_qos; /*!< LWT消息质量*/ int lwt_retain; /*!< LWT保留消息标志*/ int lwt_msg_len; /*!< LWT消息长度*/ int disable_clean_session; /*!< mqtt clean session,默认为真*/ int keepalive; /*MQTT心跳,默认120秒 */ bool disable_auto_reconnect; /*错误,断开后重连,true不连*/ void *user_context; /*用户信息 */ int task_prio; /*!< MQTT任务优先级,默认为5,可以在make menuconfig中修改*/ int task_stack; /*!< MQTT 任务堆栈大小,默认6144 bytes,可以在make menuconfig中修改*/ int buffer_size; /*!< MQTT收发缓存,默认1024 */ const char *cert_pem; /*指向用于服务器验证(使用SSL)的PEM格式的证书数据的指针,默认值为空,不需要验证服务器 */ const char *client_cert_pem; /*指向用于SSL相互身份验证的PEM格式的证书数据的指针,默认值为空,如果不需要相互身份验证,则不需要。如果不为空,还必须提供“客户机密钥”。*/ const char *client_key_pem; /*指向用于SSL相互身份验证的PEM格式的私钥数据的指针,默认值为空,如果不需要相互身份验证,则不需要。如果不为空,还必须提供“client-cert-pem”。*/ esp_mqtt_transport_t transport; /*覆盖URI传输*/ } esp_mqtt_client_config_t;

MQTT Client初始化函数:esp_mqtt_client_init();

函数原型 esp_mqtt_client_handle_t esp_mqtt_client_init ( const esp_mqtt_client_config_t *config ) 函数功能 MQTT Client初始化函数 参数 [in] config:MQTT配置参数,上面介绍了 返回值 MQTT Client 句柄

MQTT Client启动函数:esp_mqtt_client_start();

函数原型 esp_err_t esp_mqtt_client_start (esp_mqtt_client_handle_t client); 函数功能 MQTT Client启动函数 参数 [in] client:MQTT Client 句柄 返回值 ESP_OK:成功 other : 失败

MQTT Client停止函数:esp_mqtt_client_stop();

函数原型 esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client); 函数功能 MQTT Client停止函数 参数 [in] client:MQTT Client 句柄 返回值 ESP_OK:成功 other : 失败

MQTT Client订阅主题函数:esp_mqtt_client_subscribe();

函数原型 esp_err_t esp_mqtt_client_subscribe ( esp_mqtt_client_handle_t client, const char *topic, int qos ) 函数功能 MQTT Client订阅函数 参数 [in] client:MQTT Client 句柄 [in] topic:MQTT 主题 [in] qos:服务质量 返回值 ESP_OK:成功 other : 失败

MQTT Client取消订阅主题函数:esp_mqtt_client_unsubscribe();

函数原型 esp_err_t esp_mqtt_client_unsubscribe ( esp_mqtt_client_handle_t client, const char *topic ) 函数功能 MQTT Client取消订阅主题函数 参数 [in] client:MQTT Client 句柄 [in] topic:MQTT 主题 返回值 ESP_OK:成功 other : 失败

MQTT Client发布主题函数:esp_mqtt_client_publish();

函数原型 int esp_mqtt_client_publish ( esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain ) 函数功能 MQTT Client发布主题函数 参数 [in] client:MQTT Client 句柄 [in] topic:MQTT 主题 [in] data:数据 [in] len:长度,=0表示data长度 返回值 消息ID

MQTT Client注销函数:esp_mqtt_client_publish();

函数原型 esp_err_t esp_mqtt_client_destroy(esp_mqtt_client_handle_t client); 函数功能 MQTT Client取消订阅主题函数 参数 [in] client:MQTT Client 句柄 返回值 ESP_OK:成功 other : 失败

基于TCP的MQTT源码编写

MQTT初始化

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static void mqtt_app_start(void) { esp_mqtt_client_config_t mqtt_cfg = { .host = "192.168.2.104", //MQTT服务器IP .event_handle = mqtt_event_handler, //MQTT事件 .port=1883, //端口 .username = "admin", //用户名 .password = "public", //密码 // .user_context = (void *)your_context }; client = esp_mqtt_client_init(&mqtt_cfg); esp_mqtt_client_start(client); //等mqtt连上 xEventGroupWaitBits(mqtt_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); }

MQTT回调

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event) { esp_mqtt_client_handle_t client = event->client; int msg_id; // your_context_t *context = event->context; switch (event->event_id) { case MQTT_EVENT_CONNECTED://MQTT连上事件 ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); xEventGroupSetBits(mqtt_event_group, CONNECTED_BIT); //发送订阅 msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1); ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); break; case MQTT_EVENT_DISCONNECTED://MQTT断开连接事件 ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); //mqtt连上事件 xEventGroupClearBits(mqtt_event_group, CONNECTED_BIT); break; case MQTT_EVENT_SUBSCRIBED://MQTT发送订阅事件 ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "订阅成功", 0, 0, 0); ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); break; case MQTT_EVENT_UNSUBSCRIBED://MQTT取消订阅事件 ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); break; case MQTT_EVENT_PUBLISHED://MQTT发布事件 ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); break; case MQTT_EVENT_DATA://MQTT接受数据事件 ESP_LOGI(TAG, "MQTT_EVENT_DATA"); printf("TOPIC=%.*s ", event->topic_len, event->topic); //主题 printf("DATA=%.*s ", event->data_len, event->data); //内容 break; case MQTT_EVENT_ERROR://MQTT错误事件 ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); xEventGroupClearBits(mqtt_event_group, CONNECTED_BIT); break; } return ESP_OK; }

定时发布主题

1 2 3 4 5 6 7 while (1) { //发布主题 //PC订阅了,会收到这条信息 esp_mqtt_client_publish(client, "/topic/qos0", "Hello MQTT ,I am HongXu", 0, 0, 0); vTaskDelay(1000 / portTICK_PERIOD_MS); }

测试效果

测试流程

    启动MQTT服务器,上文步骤。 修改WiFi账号和密码。 修改MQTT配置信息
    make menuconfig -> make all -> make flash ->make mmonitor PC MQTT助手连上服务器。

效果展示

服务器有两个MQTT客户端,分别是ESP32开发板和PC的助手。

订阅和发布,可以互通

MQTT总结

    此处的MQTT源码是基于TCP的MQTT方案。 乐鑫SDK,esp-idf-V3.1.2自带MQTT库,所以我更新了SDK和编译链。 MQTT的源码结构和WiFi状态机一样,都是初始化+回调的方式。 MQTT总结就是:伸手即得

我发布的主题,无论是谁订阅都能收到,包括我自己。

别人发布的主题,只要我订阅就能收到。

经验分享 程序员 微信小程序 职场和发展