【快速上手ESP32(基于ESP-IDFVSCode)】11-MQTT

news2024/10/7 8:30:57

MQTT

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是一种基于发布/订阅模式的轻量级通讯协议,构建于TCP/IP协议之上。它最初由IBM在1999年发布,主要用于在硬件性能受限和网络状况不佳的情况下,为远程设备提供可靠的消息传输服务。MQTT协议简单易用、可靠性高、延迟低,因此广泛应用于物联网(IoT)、机器人、智能城市管理、农业物联网以及能源监测与管理等领域。

MQTT协议由三个主要部分组成:客户端、服务器和主题。客户端是发送和接收消息的应用程序,可以是发布者或订阅者。服务器(也称为代理)负责处理消息,接收来自发布者的消息并将其传递给已订阅该主题的订阅者。主题是消息的路径,用于区分不同类型的消息。发布者将消息发布到特定主题,而订阅者则订阅感兴趣的主题以接收消息。

MQTT协议的工作原理如下:

  1. 连接建立:客户端(发布者或订阅者)与代理之间建立TCP连接。客户端需要提供客户端ID以及连接到代理的凭据(如用户名和密码)。
  2. 主题订阅:订阅者向代理发送订阅请求,以订阅特定的主题。
  3. 消息发布:发布者将消息发布到特定的主题。代理接收到消息后,会将其传递给已订阅该主题的订阅者。
  4. 消息传递:一旦代理接收到发布者发布的消息,并确认订阅者已订阅该主题,代理就会将消息传递给订阅者。订阅者收到消息后可以进行相应的处理。
  5. 断开连接:在通信结束后,客户端可以选择断开与代理的连接。断开连接时,客户端需要发送断开连接请求给代理。

MQTT协议的优点包括:

  • 轻量级:MQTT协议规范简单,易于实现,对硬件资源要求低,适用于资源受限的设备。
  • 高可靠性:使用TCP协议进行传输,保证了消息的可靠传递。
  • 低延迟:基于发布/订阅模式,减少了消息传递的延迟,提高了实时性。
  • 灵活性:MQTT协议支持多种消息传递方式,如QoS(服务质量)等级设置,以满足不同应用场景的需求。

MQTT协议在物联网领域的应用尤为广泛,可以帮助设备与云平台或中心服务器进行高效的数据交互。设备通过MQTT协议将采集到的数据发布到指定的主题,云平台或中心服务器订阅相应的主题即可实时获取数据。同时,云平台或中心服务器也可以通过MQTT向设备发送控制指令,实现对设备的远程监控与控制。

以上介绍来自文心一言。

我之前的文章中也有提到MQTT,当时用的Arduino和MicroPython写的ESP32的程序,我们需要找第三方库才能实现MQTT,但是这次我们使用的是ESP-IDF,人家官方自带了MQTT啦,我们就不需要去找第三方库。

除了MQTT,还有Modbus,TLS,HTTP之类的我们也都可以直接使用官方提供的API。

ESP-MQTT

#include "mqtt_client.h"

从编程指南的介绍可以看出官方提供的MQTT库支持MQTT v5.0版本的(当前编程指南的ESP-IDF是5.1版本的,不同IDF版本可能支持的MQTT版本不一样)。

并且该有的都有,不过MQTT本身也不复杂。

初始化句柄

esp_mqtt_client_handle_t esp_mqtt_client_init ( const esp_mqtt_client_config_t * config )

首先自然是初始化,然后返回给我们MQTT客户端句柄。

问题在于传入的参数,esp_mqtt_client_config_t这个结构体相当复杂,结构体里嵌套结构体再嵌套结构体,是我目前为止见过最复杂的配置参数了,定义结构体的代码包括注释足足有一百多行。

因此我们只挑几个常用的成员变量说。

    esp_mqtt_client_config_t emcct = {
        .broker.address.uri="mqtt://xxx.xxx.xxx.xxx",
        .broker.address.port=1883,
        .credentials.client_id="xxxxxx",
        .credentials.username="xxx",
        .credentials.authentication.password='xxx',
        .session.keepalive=120,
        .buffer.size=1024,
        .buffer.out_size=1024
    };
    esp_mqtt_client_init(&emcct);

根据我上面的例子,我们按照顺序来介绍。

开头两个分别是mqtt服务器的uri和端口,端口一般默认都是1883,因此除非是自己搭的mqtt服务器,并且端口还改成乱七八糟的,一般都1883,在上面结构体中是不用配置的。

第三个是连接mqtt服务器所需要用到的ID,在同一个MQTT服务器同时连接的客户端中,不允许ID相同,如果是自己的服务器那无所谓,如果连接的公用的MQTT服务器,那么就需要保证这个ID不会和别人重复。这个不配置也问题不大,因为ESP-MQTT帮我们把这个ID默认设置为“ESP_xxxxxx”,其中xxxxxx是我们的MAC地址的后6位(16进制形式)

可以从上图看到如果我们不配置这个ID,那么会帮我们配置一个默认的ID。 

默认的ID就是"ESP_xxxxxx",xxxxxx就是MAC地址的后三位。

我们再回到例子中第四和第五个结构体成员,看名字也可以知道是连接MQTT服务器用的用户名和密码。如果是使用的公用的MQTT服务器那么大概率是不需要的,但是如果有小伙伴为了得到更高质量的MQTT服务器的服务,那么提供MQTT服务器的服务商都是要求连接MQTT服务器需要用户名和密码的。

倒数第三个是心跳时间,也就是我们最多每隔多久就需要给MQTT服务器发送一个心跳包以证明我们还连接着,默认是120s,我们也可以修改,单位为s。

最后两个分别是我们接收数据的缓冲区大小和发送数据的缓冲区大小。接收的缓冲区大小默认为1024,发送的缓冲区大小默认和接收的一致,也就是说如果都不配置的话,那么默认都是1024。

一般情况下是够用的,但是如果发送或者接收的数据比较大时就需要修改了。

之前有个小项目,使用MQTT来传输图片,然后死活传不出去,整了一个下午才发现是图片的大小超过了缓冲区大小。

我们MQTT理论上最多可以传输256MB的数据,因此只要不是太离谱,都是可以发送的。

剩下还有一个遗嘱我在上面的例子中没有设置,但是还是提一下。

在esp_mqtt_client_config_t下的session_t下的last_will_t(套了三层结构体)可以配置遗嘱信息,看成员名字都可以看懂,我就不多介绍了,这里就提一下。

修改uri

esp_err_t esp_mqtt_client_set_uri(esp_mqtt_client_handle_t client, const char *uri)

除了一开始初始化,我们还可以后面再修改。

启动&停止MQTT客户端

esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)

esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client)

注册MQTT事件

和WiFI一样的是MQTT同样会产生很多事件,例如连接或是断开MQTT服务器,收到了订阅的消息等,因此我们需要注册MQTT的事件处理函数。

不一样的是MQTT拥有一套独立的注册函数。

esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler, void *event_handler_arg)

关于参数怎么填写可以参考我下面的例子。

void mqtt_event_fun(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data){
    //处理逻辑

}

    
esp_mqtt_client_register_event(emcht,ESP_EVENT_ANY_ID,mqtt_event_fun,NULL);

处理函数的格式需要跟上面一样(函数名自己随便起)。

参数一是MQTT客户端的句柄。

参数二是-1的宏定义,表示我们处理所有关于MQTT的所有ID的事件。

发布信息

int esp_mqtt_client_publish ( esp_mqtt_client_handle_t client , const char * topic , const char * data , int len , int qos , int keep ) 

这里简单提一下qos这个参数,是信息的等级0~2,0是最多发送一次消息,1是至少发送一次消息,2是必定能让订阅这个主题的人都收到消息,收不到就一直发。

因此等级越高,对资源的消耗越大,我们常用的就是0和1。很少用到2,除非是非常非常重要的消息。

最后一个参数没啥用,塞个0就行。

订阅主题

int esp_mqtt_client_subscribe_single ( esp_mqtt_client_handle_t client , const char * topic , int qos )

int esp_mqtt_client_subscribe_multiple ( esp_mqtt_client_handle_t client , const esp_mqtt_topic_t * topic_list , int size ) 

两个函数都可以订阅主题,区别在于第一个函数一次订阅一个主题,而第二个函数可以一次性订阅多个主题。

那么当我们订阅主题之后,肯定是希望我们能够第一时间收到信息的对吧,那么我们如何接收处理信息呢

在Arduino和MicroPython中,我们都是订阅了一个回调函数去处理,但是在ESP-IDF中,我们直接在MQTT事件中接收数据。

MQTT事件的ID为MQTT_EVENT_DATA的事件就是接收到订阅信息的事件,我们在触发了这个事件的逻辑中处理收到的数据。

这个数据我们从处理函数的最后一个形参里获取,由于它是void*类型的参数,我们无法直接使用,我们需要将其的类型进行强转,强转成esp_mqtt_event_t*类型的参数。

我们需要的信息就在上图我红框里,分别是数据和数据长度,以及主题名和主题名的长度。

重新连接&断开连接

esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client)

 小伙伴可能会感到疑惑,为什么是重新连接,没有连接的函数吗。

这是因为在我们启动MQTT客户端的时候就已经帮我们进行一次连接了。所以这个重新连接函数是用于我们已经连接过服务器后面又断开了,然后还需要再连接的时候使用的。

esp_err_t esp_mqtt_client_disconnect(esp_mqtt_client_handle_t client)

心跳信息

不用发送心跳信息!!!ESP-MQTT帮我们自动发送。

完整实操代码

了解了上面的API之后,我们就可以开始写代码进行MQTT的通信了。

在进行MQTT服务器的连接之前,需要先连上网,可以参考我上一篇文章。

下面的代码我都写上了注释,大家应该都能看得懂。直接拿走只需要把WiFI的名称和密码以及MQTT服务器的uri改掉就可以用了。

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mqtt_client.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "nvs_flash.h"

void Z_WiFi_Init(void);
void Z_Mqtt_Init(void);

bool Z_mqtt_connect_flag=false;             //记录是否连接上MQTT服务器的一个标志,如果连接上了才可以发布信息
esp_mqtt_client_handle_t emcht;             //MQTT客户端句柄

//WiFI事件处理函数
void  wifi_event_fun(void* handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data){
    printf("%s,%ld\r\n",event_base,event_id);

    if(event_id==WIFI_EVENT_STA_START){                 //如果是STA开启了,那么尝试连接
        esp_wifi_connect();                         
    }else if(event_id==WIFI_EVENT_STA_CONNECTED){       //连接上WiFI之后
        Z_Mqtt_Init();                                  //开始连接MQTT服务器
    }else if(event_id==WIFI_EVENT_STA_DISCONNECTED){    //断开WiFi之后
        esp_wifi_connect();                             //尝试重连WiFi
    }
}

//MQTT事件处理函数
void mqtt_event_fun(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data){
    printf("%s,%ld\r\n",event_base,event_id);

    if(event_id==MQTT_EVENT_CONNECTED){                 //连接上MQTT服务器
        Z_mqtt_connect_flag=true;
        esp_mqtt_client_subscribe_single(emcht,"Z_topic",1);    //订阅一个测试主题
        printf("success connect mqtt\r\n");
    }else if(event_id==MQTT_EVENT_DISCONNECTED){        //断开MQTT服务器连接
        Z_mqtt_connect_flag=false;
        printf("lose connect mqtt\r\n");
    }else if(event_id==MQTT_EVENT_DATA){                //收到订阅信息
        esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t )event_data;   //强转获取存放订阅信息的参数
        printf("receive data : %.*s from %.*s\r\n",event->data_len,event->data,event->topic_len,event->topic);
    }
}

void Z_WiFi_Init(void){
    nvs_flash_init();                           //初始化nvs
    esp_netif_init();                           //初始化TCP/IP堆栈
    esp_event_loop_create_default();            //创建默认事件循环
    esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,wifi_event_fun,NULL);       //绑定事件处理函数
    esp_netif_create_default_wifi_sta();        //创建STA
    wifi_init_config_t wict = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&wict);                       //初始化WiFI
    esp_wifi_set_mode(WIFI_MODE_STA);           //设为STA模式
    wifi_config_t wct = {
        .sta = {
            .ssid="xxx",
            .password="xxx"
        }
    };
    esp_wifi_set_config(WIFI_IF_STA,&wct);      //设置WiFi
    esp_wifi_start();                           //启动WiFi
}

void Z_Mqtt_Init(void){
    esp_mqtt_client_config_t emcct = {
        .broker.address.uri="mqtt://xxx.xxx.xxx.xxx",  //MQTT服务器的uri
        .broker.address.port=1883                   //MQTT服务器的端口
    };
    emcht = esp_mqtt_client_init(&emcct);           //初始化MQTT客户端获取句柄
    if(!emcht)  printf("mqtt init error!\r\n");
    
    //注册MQTT事件处理函数
    if(esp_mqtt_client_register_event(emcht,ESP_EVENT_ANY_ID,mqtt_event_fun,NULL)!=ESP_OK)  printf("mqtt register error!\r\n");

    //开启MQTT客户端
    if(esp_mqtt_client_start(emcht) != ESP_OK)  printf("mqtt start errpr!\r\n");
}

void app_main(void){
    Z_WiFi_Init();
    char* data="Hello World";
    while(1){
        //每隔5S发布一次测试消息
        if(Z_mqtt_connect_flag) esp_mqtt_client_publish(emcht,"test",data,strlen(data),1,0);
        vTaskDelay(3000/portTICK_PERIOD_MS);
    }
}

从结果上看,我们是可以正常地接收和发送数据的。

上图中我用的MQTT软件是MQTTfx1.7.1版本的(更高版本要收费。。。)

大家可以关注我的公众号“折途想要敲代码”回复关键词“ESP32”即可免费下载啦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1625997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

M-LAG的基本概念

如图所示&#xff0c;用户侧设备Switch&#xff08;可以是交换机或主机&#xff09;通过M-LAG机制与另外两台设备&#xff08;SwitchA和SwitchB&#xff09;进行跨设备链路聚合&#xff0c;共同组成一个双活系统。这样可以实现SwitchA和SwitchB共同进行流量转发的功能&#xff…

泰迪智能科技助力中山三院放射科搭建生成式大模型应用

泰迪智能科技作为一家专业从事物联网、大数据及人工智能技术研发、咨询与培训的高科技企业&#xff0c;具有强大的技术研发实力和应用经验。中山大学附属第三医院放射科是集医疗、教学、科研工作于一体的广东省临床重点专科&#xff0c;具有深厚的医疗资源和科研基础。两者合作…

安卓和ios设置自己的短链

ios 的info.plist文件 设置 CFBundleURLSchemes 其中konnect 就是设置app的短链名称 <array><dict><key>CFBundleTypeRole</key><string>Editor</string><key>CFBundleURLName</key><string>org.konnect.app</str…

4.keepalive 与 Idle 监测

为什么需要 keepalive ? 假设你开了一个饭店,别人电话来订餐,电话通了后,订餐的说了一堆订餐要求,说着说 着,对方就不讲话了(可能忘记挂机/出去办事/线路故障等)。 这个时候你会一直握着电话等么? 不会 如果不会,那你一般怎么去做?会确认一句“你还在么?”,如果对…

常见的掼蛋误区

1、过于依赖大牌 很多新手玩家会觉得手中的大牌是必胜的保证&#xff0c;然而这种想法都是片面的。在掼蛋游戏中&#xff0c;一个合理的牌型组合往往比单一的大牌更有胜算。因此玩家要综合考虑的是手中的牌型和牌面大小。 2、盲目跟风 不少玩家在掼蛋中喜欢跟着对手的出牌思路走…

当NebulaGraph遇上智能体:图数据库智能助手

在数字化转型的浪潮中&#xff0c;图数据库技术凭借其出色的数据关联性能和灵活的查询功能&#xff0c;逐渐成为企业重要的技术选项。我们的团队之前曾经在两个项目中进行了图数据库的重构&#xff1a;一次是从OrientDB迁移到NebulaGraph&#xff0c;另一次是将ES系统迁移到Neb…

边写论文边发疯,多大仇啊?导师批注学生论文:建议把致谢部分烧掉

盼望着&#xff0c;盼望着&#xff0c;夏天来了&#xff0c;毕业的脚步近了。 于是乎&#xff0c;莘莘学子又开始幻想自己被导师带飞一路畅通顺利毕业了。 而且&#xff0c;如果可行的话&#xff0c;大家还希望能够这样&#xff1a; 然而&#xff0c;我阴暗的心中不由得泛起一…

只需几步,即可享有笔记小程序

本示例是一个简单的外卖查看店铺点菜的外卖微信小程序&#xff0c;小程序后端服务使用了MemFire Cloud&#xff0c;其中使用到的MemFire Cloud功能包括&#xff1a; 其中使用到的MemFire Cloud功能包括&#xff1a; 云数据库&#xff1a;存储外卖微信小程序所有数据表的信息。…

[Diffusion Model 笔记]DDIM 笔记 数学推导 Denoising Diffusion Implicit Models

目录 核心总结符号定义第一套&#xff0c;快速简单讲清采样方法继续分析&#xff0c;待定系数法求解图示理解关于参数sigma 本文是观看以下视频的笔记&#xff0c;强烈推荐观看最后的图示理解&#xff1a; https://www.bilibili.com/video/BV13P411J7dm/?spm_id_from333.788 论…

http基础了解

超文本传输协议&#xff08;HTTP&#xff09;是一个用于传输超媒体文档&#xff08;例如 HTML&#xff09;的应用层协议。它是为 Web 浏览器与 Web 服务器之间的通信而设计的&#xff0c;但也可以用于其他目的。HTTP 遵循经典的客户端—服务端模型&#xff0c;客户端打开一个连…

01、创建型-单例模式--只有一个实例

文章目录 前言一、基本介绍1.1 什么是单例模式1.2 为什么要用单例模式1.3 应用场景1.4 单例优缺点 二、单例模式的实现方式2.1 饿汉式单例2.1.1 静态变量方式2.1.2 静态代码块 2.2 懒汉式单例2.2.1 懒汉式单例2.2.2 懒汉式优化①-线程安全2.2.2 懒汉式优化②-双重检查锁2.2.3 懒…

力扣-1832.判断句子是否全为字母句

思路: 首先&#xff0c;我们初始化了一个长度为 26 的布尔值列表 exist&#xff0c;所有值都为 False&#xff0c;表示所有字母初始都未出现过。然后&#xff0c;我们遍历输入的字符串 sentence 中的每个字符。对于每个字符&#xff0c;我们通过计算其 ASCII 码值减去字母 a 的…

ArcGIS Pro专题地图系列教程

专题地图系列是ArcGIS Pro3.2的新功能。之前&#xff0c;如果要做8张相同区域的专题图&#xff0c;可能需要新建8个布局&#xff0c;分别进行排版&#xff0c;再导出。现在&#xff0c;一幅地图&#xff0c;一个布局&#xff0c;就可以完成这个流程。 原理是&#xff0c;根据单…

ROS python实现乌龟跟随

产生两只乌龟&#xff0c;中间的乌龟(A) 和 左下乌龟(B), B 会自动运行至A的位置&#xff0c;并且键盘控制时&#xff0c;只是控制 A 的运动&#xff0c;但是 B 可以跟随 A 运行 乌龟跟随实现的核心&#xff0c;是乌龟A和B都要发布相对世界坐标系的坐标信息&#xff0c;然后&am…

用例整体执行及pytest.ini文件

在我们写代码的过程中&#xff0c;一般都是右键或者命令行去执行一个用例 但是当我们写完后&#xff0c;需要整体执行一遍。那应该怎么搞呢&#xff1f; 我们可以在根目录下新建一个main.py或者run.py之类的文件&#xff0c;文件内容如下&#xff1a; if __name__ "__ma…

报错:图片验证码接口对接vue+springboot(下一个笔记会记录整个验证码的代码)

问题&#xff1a;空指针异常ai: 根据错误堆栈信息中提供的方法调用位置&#xff0c;看起来空指针异常是在 AuthCodeServiceImpl 类的 authUserCoded 方法的第 41 行发生的。 为了解决这个问题&#xff0c;你可以检查 AuthCodeServiceImpl 类中 authUserCoded 方法的第 41 行&am…

发那科FANUC机器人R-2000iB平衡缸维修攻略

在发那科机器人中&#xff0c;平衡缸扮演着稳定机械臂运动的关键角色。它通过内部的压力调节来平衡负载&#xff0c;保证机器人的精准定位和平稳操作。一旦出现法兰克机械手平衡缸故障或损坏&#xff0c;机器人的性能可能会大打折扣&#xff0c;因此及时且正确的FANUC机械手平衡…

2024通信会|迈向智慧配电网建设新时代,锐捷网络发布双平面配电通信解决方案

近期,以“加快推进通信数智化,助力构建新型能源体系”为主题的2024年能源网络通信创新应用大会在四川成都圆满结束,会议围绕构建新型能源体系和新型电力系统建设需求,探讨能源网络通信创新应用的最新趋势与成果、“主、配、微”通信网一体化融合、配电通信网、通信数智化转型等…

Methoxy-PEG-PCL,Methoxy-PEG-Poly(ε-caprolactone)可以作为制备纳米颗粒的重要原料

【试剂详情】 英文名称 mPEG-PCL&#xff0c;MPEG-Poly(ε-caprolactone)&#xff0c;Methoxy-PEG-PCL&#xff0c;Methoxy-PEG-Poly(ε-caprolactone) 中文名称 聚乙二醇单甲醚聚己内酯两嵌段共聚物&#xff0c; 聚乙二醇单甲醚聚己内酯 外观性状 由分子量决定&#xff0…

C++实战演练---负载均衡在线oj项目预热

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 前言 学习准备了快一年时间&#xff0c;心心念念的实战演练终于可以开始了&#xff0c;话不多说&#xff0c;直接进入主题…