当前位置: 首页 > news >正文

基于正点原子阿波罗F429开发板的LWIP应用(7)——MQTT

说在开头

        正点原子F429开发板主芯片采用的是STM32F429IGT6,网络PHY芯片采用的是LAN8720A(V1)和YT8512C(V2),采用的是RMII连接,PHY_ADDR为0;在代码中将会对不同的芯片做出适配。

        CubeMX版本:6.6.1;

        F4芯片组Pack包版本:STM32Cube FW_F4 V1.27.0;

        本实验代码以《基于正点原子阿波罗F429开发板的LWIP应用(3)——Netbiosns功能》一章的代码作为基础;

        做过物联网开发的对MQTT协议都不陌生,MQTT是一个基于“客户端——服务器”的消息“发布/订阅”传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。

        在STM32中运行的MQTT服务一般是MQTT客户端。论MQTT实现的难易度那肯定是在有操作系统的情况下更容易实现,在这里我做出一个裸机状态下长时间保持连接的MQTT应用实现。

        实现过程如下:

1、“lwipopts.h”文件修改:

1、使能DHCP:#define LWIP_DHCP 1
2、使能DNS: #define LWIP_DNS  1
3、修改MEMP_NUM_SYS_TIMEOUT的值为16:#define MEMP_NUM_SYS_TIMEOUT 16

2、新建"mqtt_client.c"、"mqtt_client.h"文件并添加进MDK,添加以下代码(注意:"mqtt_client.h"文件中的连接参数需要修改)

//"mqtt_client.c"代码:#include "mqtt_client.h"#include "lwip.h"
#include "lwip/dns.h"
#include "lwip/apps/mqtt.h"static mqtt_client_t* g_mqtt_client = 0;//MQTT控制块
static ip_addr_t g_mqtt_ip = { NULL };//储存解析出的IP地址
static uint8_t mqtt_rec_flag = 0;//标记接收的信息是否来自订阅的topic
extern uint8_t connect_flag;//mqtt连接状态/*** @brief       mqtt进入数据回调函数* @param       arg:传入的参数* @param       data:数据* @param       len:数据大小* @param       flags:标志* @retval      无*/
static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags)
{uint8_t *tert_date = 0;if(mqtt_rec_flag == 1){tert_date = malloc(len+1);if(tert_date){memcpy(tert_date, data, len);printf("%s\n",tert_date);//长度=lenmemset(tert_date, 0, len+1);free(tert_date);}	if (flags & MQTT_DATA_FLAG_LAST)//判断这一帧数据是否是最后一帧{mqtt_rec_flag = 0;		}}
}/*** @brief       mqtt进入发布回调函数* @param       arg:传入的参数* @param       topic:主题* @param       tot_len:主题大小* @retval      无*/
static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len)
{if(strcmp(topic, DEVICE_SUBSCRIBE) == 0){mqtt_rec_flag = 1;printf("date from subtopic\n");}else{mqtt_rec_flag = 0;printf("date not from subtopic\n");				}
}/*** @brief       mqtt发布回调函数* @param       arg:传入的参数* @param       err:错误值* @retval      无*/
static void mqtt_publish_request_cb(void *arg, err_t err)
{if((int)err != ERR_OK) printf("publish timeout!\r\n");//发布不成功就打印信息
}/*** @brief       mqtt连接状态更改回调函数* @param       client:客户端控制块* @param       arg:传入的参数* @param       status:连接状态* @retval      无*/
static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
{err_t err;/* 判断是否连接 */ if (status == MQTT_CONNECT_ACCEPTED){/* 判断是否连接 */ if (mqtt_client_is_connected(client)){/* 设置传入发布请求的回调 */mqtt_set_inpub_callback(g_mqtt_client,mqtt_incoming_publish_cb,//在发布开始时调用,包含主题和有效负载的总长度mqtt_incoming_data_cb,//接收到有效数据产生的回调NULL);/* 订阅操作,并设置订阅响应会回调函数mqtt_sub_request_cb */ err = mqtt_subscribe(client, DEVICE_SUBSCRIBE, 1, NULL, arg);if(err == ERR_OK) {printf("\nmqtt_subscribe return: %d,mqtt_subscribe succeed\n", err);connect_flag = 4;}else printf("\nmqtt_subscribe err,result=%d\n", err);}}
}/*** @brief       DNS回调函数* @param       无* @retval      无*/
static void dns_found_callback_test(const char* name, const ip_addr_t* addr, void* arg) 
{if (addr){connect_flag = 2;printf("DNS resolved: %s -> %s\r\n", name, ipaddr_ntoa(addr));memcpy(&g_mqtt_ip, addr, sizeof(ip_addr_t));printf("\nhost ip:%d.%d.%d.%d\n",(uint8_t)(g_mqtt_ip.addr),(uint8_t)(g_mqtt_ip.addr>>8),(uint8_t)(g_mqtt_ip.addr>>16),(uint8_t)(g_mqtt_ip.addr>>24));}else  printf("DNS resolution failed for: %s\r\n", name); 
}/*** @brief       DNS函数* @param       无* @retval      无*/
void 	mqtt_DNS(void)
{char host_name[]  = "bemfa.com";err_t err;uint16_t i = 5000;err = dns_gethostbyname(host_name, &g_mqtt_ip, dns_found_callback_test, NULL);while( !( (err==ERR_INPROGRESS)||(err == ERR_OK)) ){if(i) i--;else{i = 5000;err = dns_gethostbyname(host_name, &g_mqtt_ip, dns_found_callback_test, NULL);}}connect_flag = 1;
}/*** @brief       mqtt开始连接函数* @param       无* @retval      无*/
void mqtt_connect(void)
{struct mqtt_connect_client_info_t mqtt_client_info = { NULL };//MQTT连接信息结构体err_t err = 0;if(g_mqtt_client == 0){/* 设置一个空的客户端信息结构 */memset(&mqtt_client_info, 0, sizeof(mqtt_client_info));/* 设置客户端的信息量 */ mqtt_client_info.client_id = CLIENT_ID;     mqtt_client_info.client_user = USER_NAME;   mqtt_client_info.client_pass = USER_PASD;    mqtt_client_info.keep_alive = 60;/* 保活时间 */mqtt_client_info.will_msg = NULL;     /* 遗嘱消息 */          mqtt_client_info.will_qos = 0;        /* 遗嘱QOS等级 */mqtt_client_info.will_retain = 0;     /* 遗嘱消息保留标志 */mqtt_client_info.will_topic = NULL;   /* 遗嘱消息topic *//* 创建MQTT客户端控制块 */g_mqtt_client = mqtt_client_new();}err = mqtt_client_connect(g_mqtt_client,        /* 服务器控制块 */&g_mqtt_ip, HOST_PORT,			/* 服务器IP与端口号 */mqtt_connection_cb,		 /*mqtt连接状态更改回调函数*/LWIP_CONST_CAST(void*, &mqtt_client_info),/* 设置服务器连接回调函数 */&mqtt_client_info);//MQTT连接信息结构体if(  err != ERR_OK ) printf("\nMQTTconnect faild!\n");else {connect_flag = 3;printf("\nMQTTconnect OK!\n");}
}/*** @brief       mqtt开始连接函数* @param       *				date:要发送的数据*				len:发送数据的长度* @retval      无*/
void mqtt_send_date(uint8_t *date, uint16_t len)
{		int err = 0;	//发送消息err = mqtt_publish( g_mqtt_client,//MQTT客户端DEVICE_PUBLISH,//publish主题date,//发送的数据 USART_RX_BUFlen,//数据长度0, 0,// QOS等级 MQTT保留标志mqtt_publish_request_cb, NULL);//发送完成或超时回调函数 传入回调的参数			if(err == ERR_CONN)//未连接{mqtt_disconnect(g_mqtt_client);//断开连接connect_flag = 2;//重新连接}	
}//"mqtt_client.h"代码:
#ifndef _MQTT_CLINET_H
#define _MQTT_CLINET_H
#include "main.h"/* 以下参数的宏定义固定,不需要修改,只修改上方的参数即可 */
/*服务器地址可以是域名,也可以是IP地址,如果是域名则需要使能DNS服务解析对应的IP,如果是IP则可以直接进行连接*/
#define HOST_NAME           "XXXXXXXXXXXXXXXXXXXXXXX"                                 	 /* 服务器地址 */
#define HOST_PORT            1883                                          /* 端口*/
#define CLIENT_ID           "XXXXXXXXXXXXXXXXXXXXXXX"             /* 客户端ID*/
#define USER_NAME           "NANE"                                         /* 客户端用户名 */
#define USER_PASD           "PASD"                                         /* 客户端密码 */
#define DEVICE_PUBLISH      "public_topic"                         				 /* 发布 */
#define DEVICE_SUBSCRIBE    "sub_topic"                       						   /* 订阅 */void 	mqtt_DNS(void);
void mqtt_connect(void);
void mqtt_send_date(uint8_t *date, uint16_t len);
#endif /* _CLIENT_H */

3、main.c做一下修改:

1、开头增加头文件:#include "mqtt_client.h"
2、开头新建全局变量:uint8_t connect_flag = 0;//mqtt连接状态
3、mian函数开头新增变量:uint32_t i = 0;
4、mian函数while(1)里增加以下内容:i++;if(i>1000000){i = 0;switch(connect_flag){case 0://刚开始,还未进行DNS{mqtt_DNS();break;}case 1://已经进行DNS,等待DNS完成{break;}case 2://DNS已经完成,开始连接{mqtt_connect();break;}case 3://连接完成,等待主题订阅完成{break;}case 4://连接完成,开始发消息{mqtt_send_date((uint8_t *)"zztx",4);break;}default:break;		}}

        至此修改就完成了,编译0警告0错误。烧录进开发板中可以看到DS0一直在闪烁。同时串口也会打印日志信息。

        接下来我使用巴法云物联网平台(bemfa.com)进行演示,这个平台的MQTT协议接入手册在这里:mqtt.fx 下载使用教程-巴法科技。

        由于MQTT需要使用外网,所以代码中使能了DHCP服务,所以串口会先打印DHCP获取的地址;

之后由于设置的MQTT服务器为域名,所以在连接前需要进行DNS解析,解析完成后会打印解析出来的IP地址;

解析完成就开始进行连接,连接成功会打印OK;可以通过云平台进行查看设备是否在线:

之后进行topic订阅,这里只订阅了一个topic,订阅成功同样会打印OK;

最后间隔2S左右向服务器发送内容“zztx”;这里为了方便查看日志就通过MQTTX这个软件来查看:

在发送的同时也可以接收服务端的消息,例如通过服务端发送“1234”则串口会打印出来;通过MQTTX这个软件也可以同样看到网页端推送的内容;

本次实验源代码如下:

http://www.lryc.cn/news/573504.html

相关文章:

  • 华为OD机试-云短信平台优惠活动-完全背包(JAVA 2024E卷)
  • TodoList 案例(Vue3): 使用Composition API
  • 嵌入式开发之嵌入式系统硬件架构设计时,如何选择合适的微处理器/微控制器?
  • 腾讯云IM即时通讯:开启实时通信新时代
  • 一文详解归并分治算法
  • Python:.py文件如何变成双击可执行的windows程序?(版本1)
  • 深入Java面试:从Spring Boot到微服务
  • Django数据库迁移
  • P1220 关路灯
  • Spring Boot + MyBatis + Vue:全栈开发的深度剖析与实践指南
  • IEEE5节点系统潮流仿真模型(simulink+matlab全功能模型)
  • maxcomputer 和 hologres中的EXTERNAL TABLE 和 FOREIGN TABLE
  • Qt/C++应用:防御性编程完全指南
  • C 语言结构体:从基础到内存对齐深度解析
  • 数据结构——函数填空题
  • Rust调用 DeepSeek API
  • Redis 的穿透、雪崩、击穿
  • SuGAR代码精简解读
  • C++ 中 QVector 的判断与操作
  • 探索阿里云容器:解锁云原生应用的无限可能
  • [TPAMI 2022]HGNN: General Hypergraph Neural Networks+
  • Qt + C++ 入门2(界面的知识点)
  • [muduo] ThreadPool | TcpClient | 异步任务 | 通信测试
  • 【单调栈】-----【Largest Rectangle in a Histogram】
  • NuttX Socket 源码学习
  • C++ 第一阶段项目一:实现简易计算器
  • MCPServer编程与CLINE配置调用MCP
  • Taro 状态管理全面指南:从本地状态到全局方案
  • 人工智能学习57-TF训练
  • 逆向入门(16)程序逆向篇-Cabeca