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

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.mfbz.cn/a/576330.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用 Fly.io 和 Tigris 部署 Next.js 应用

在本教程中&#xff0c;您将学习到应用部署平台 Fly.io 和全球分布式的 S3 兼容对象存储服务 Tigris。 这两个平台密切相关&#xff0c;使它们成为您项目的绝佳选择。您可以从 Fly.io 获得应用部署体验&#xff0c;并从 Tigris 获得对象存储功能。 应用部署相当简单易懂&…

短视频素材去哪里找,而且不带水印的那种?

为了确保视频创作者能够接触到全球范围内的优质资源&#xff0c;下面列出的视频素材网站各具特色&#xff0c;提供从标准视频到高动态范围&#xff08;HDR&#xff09;的素材&#xff0c;满足你在不同项目中的需求。 1. 蛙学府 (中国) 提供专业级的视频素材&#xff0c;特别适…

【C++】STL-vector的使用

目录 1、什么是vector&#xff1f; 2、vector的使用 2.1 vector的定义 ​编辑 2.2 遍历修改数据 2.3 迭代器 2.4 vector空间增长问题 2.5 vector的增删查改 3、迭代器失效 3.1 会引起其底层空间改变的操作&#xff0c;都有可能是迭代器失效 3.2 指定位置元素的删除操…

【触摸案例-多点触摸的案例 Objective-C语言】

一、我们来做这个多点触摸的案例 1.首先呢,按着这个option键啊,可以模拟多点触摸, 然后呢,再去怎么着去画圈儿, 它这个里边就会产生一个imageView,跟着你去变,会有这么一个效果, 那么,首先啊,我们新建一个项目, Name:03-多点触摸的案例 1)首先,我们把控制器的v…

dwc3控制器是怎么处理otg

概念 在OTG中&#xff0c;初始主机设备称为A设备&#xff0c;外设称为B设备。可用电缆的连接方式来决定初始角色。两用设备使用新型Mini-AB插座&#xff0c;从而使Mini-A插头、Mini-B插头和Mini-AB插座增添了第5个引脚&#xff08;ID&#xff09;&#xff0c;以用于识别不同的…

网御星云防火墙策略配置

网御星云防火墙配置 1. 初始设定2. 网络配置3. 安全规则和策略4. 监控和维护零基础入门学习路线视频配套资料&国内外网安书籍、文档网络安全面试题 1. 初始设定 接入网络&#xff1a; 在开始配置之前&#xff0c;确保你的网御星云防火墙正确连接到网络。这通常涉及将WAN接…

基于Python实现的推箱子小游戏

Python贪吃蛇小游戏实现: 推箱子曾经在我们的童年给我们带来了很多乐趣。推箱子这款游戏现在基本上没人玩了&#xff0c;甚至在新一代人的印象中都已毫无记忆了。。。但是&#xff0c;这款游戏可以在一定程度上锻炼自己的编程能力。 运行效果如图所示&#xff1a; 游戏关卡有点…

Ubuntu系统强制用户设置复杂密码

1、安装cracklib模块 安装PAM的cracklib模块&#xff0c;cracklib能提供额外的密码检查能力 sudo apt-get install libpam-cracklib2、可用vim打开配置文件&#xff08;或其它方式&#xff09; sudo vim /etc/pam.d/common-password3、设置密码复杂度 在# here are the per…

滚珠丝杆有哪些应用场景?

在传动领域中滚珠丝杆是自动化设备和智能制造设备相结合的关键装置&#xff0c;在精密制造工艺、精密装配作业及现代物流系统等多元领域中&#xff0c;发挥着不可或缺的核心作用。其优点在于快速、高效、准确可靠和稳定。它能够在较小的转矩下产生很大的推力&#xff0c;所以被…

win11 安装qt5.14.2 、qtcreator、vs编译器 。用最小安装进行 c++开发qt界面

系统 &#xff1a;win11 一、安装vs生成工具 &#xff0c;安装编译器 下载visualstudio tools 生成工具&#xff1a; 安装编译器 和 windows sdk&#xff1a; 安装debug 调试器&#xff1a; 二、Qt5.14.2下载 下载链接: Index of /archive/qt/5.14/5.14.2 安装qt 三、配置QT/…

【多态】有关多继承和菱形继承的多态

博主首页&#xff1a; 有趣的中国人 专栏首页&#xff1a; C进阶 其它专栏&#xff1a; C初阶 | 初阶数据结构 | Linux 博主会持续更新 本篇文章主要讲解 多继承和菱形继承的多态 的相关内容 文章目录 1. 回顾多态底层2. 抽象类2.1 概念2.2 接口继承和实现继承 3. 虚表所在…

文件上传漏洞(upload-labs)

目录 一、文件上传漏洞 1.什么是文件上传漏洞 常见的WebShell 2.文件上传产生漏洞的原因 二、文件上传绕过 &#xff08;一&#xff09;客服端绕过-JS验证 1.前端验证 upload-labs第一关 &#xff08;二&#xff09;绕过黑名单验证 黑名单验证 1.特殊解析后缀 upl…

Pandas 2.2 中文官方教程和指南(十一·一)

原文&#xff1a;pandas.pydata.org/docs/ PyArrow 功能 原文&#xff1a;pandas.pydata.org/docs/user_guide/pyarrow.html pandas 可以利用PyArrow来扩展功能并改善各种 API 的性能。这包括&#xff1a; 与 NumPy 相比&#xff0c;拥有更广泛的数据类型 对所有数据类型支持缺…

C# 结合JavaScript实现手写板签名并上传到服务器

应用场景 我们最近开发了一款笔迹测试功能的程序&#xff08;测试版&#xff09;&#xff0c;用户在手写板上手写签名&#xff0c;提交后即可测试出被测试者的心理素质评价分析。类似功能的场景还比如&#xff0c;在银行柜台办理业务&#xff0c;期间可能需要您使用手写设备进…

linux 编译binutil 遇到问题

在centos6.10上编译binutil2.27时遇到问题&#xff1a; as.c&#x1f4af;31: error: ‘DEFAULT_GENERATE_ELF_STT_COMMON’ undeclared here (not in a function) 搜到解决方法是这个&#xff1a; 1、https://github.com/riscv-software-src/riscv-tools/issues/66 &#xf…

十七、Java网络编程(一)

1、Java网络编程的基本概念 1)网络编程的概念 Java作为一种与平台无关的语言,从一出现就与网络有关及其密切的关系,因为Java写的程序可以在网络上直接运行,使用Java,只需编写简单的代码就能实现强大的网络功能。下面将介绍几个与Java网络编程有关的概念。 2)TCP/IP协议概…

内置对象部分

一&#xff0c;内置对象 二&#xff0c;math对象 不是构造函数&#xff0c;不需要new来调用&#xff0c;而是直接使用里面的属性和方法即可 1.随机方法random 返回一个随机的小数 [0,1&#xff09; 2.日起格式化 返回的月份会小一&#xff0c;记得加一 周一返回1&#xff…

iObit Uninstaller 安装、激活、使用教程

「软件简介」 IObit Uninstaller 一款专业的卸载工具&#xff0c;旨在彻底移除不需要的软件、插件以及 Windows 应用&#xff0c;同时提供安全、快速和轻量化的 PC 使用体验。 〖下载安装软件〗 版本&#xff1a;V 13.4.0 | 26.9 MB 支持系统&#xff1a;Windows 11/10/8.1/8/…

传媒论坛编辑部传媒论坛杂志社传媒论坛杂志2024年第7期目录

专题│场景传播研究 场景传播&#xff1a;一场遮盖自我与寻找自我的博弈 胡沈明; 3 基于CiteSpace的中国场景传播研究热点分析 管倩;粟银慧; 4-610《传媒论坛》投稿&#xff1a;cnqikantg126.com 数字世界的美与危&#xff1a;场景传播的失范与应对之举 王依晗;章洁…

Centos/linux根目录扩容、分区、挂载。LVM、物理卷、逻辑卷

前言    &#xff08;空格&#xff09; &#xff1a;分区挂载和扩容是两码事 每个Linux使用者在安装Linux时都会遇到这样的困境&#xff1a;在为系统分区时&#xff0c;如何精确评估和分配各个硬盘分区的容量&#xff0c;因为系统管理员不但要考虑到当前某个分区需要的容量&a…
最新文章