STM32+LWIP协议栈实现MQTT协议并挂载到EMQ_X_CLOUD平台
引言
在前几篇文章中,我主要用LWIP协议栈实现了httpd服务器,及其一些应用。本篇我们来实现另外一种在物联网中应用非常广泛的MQTT协议。MQTT协议的定义我就不讲了,主要记住MQTT协议是一种订阅/发布式的协议。
云平台选择
要想将我们的设备使用MQTT协议挂载到云平台上,第一步我们需要选择合适的云平台,阿里云,腾讯云都可以,在这里我选择EMQ X Cloud云平台,这个云平台是我在知乎上看到的,现在新人免费试用14天。
进入控制面板后我们可以看到具体操作界面,进入监控选项,我们可以在这里看到我们的客户端连接。
这个平台的具体使用方法,我现在发现的主要有2个方式可以进行客户端连接。
1、在客户端连接指引中,下载MQTT X工具
在这个软件中,可以连接到我们的MQTT服务器。
2、在EMQ X Cloud云平台中,进行在线调试
这个是网页版的调试。
这两种方式都可以当做客户端连接。可以先用来测试云平台设置的对不对,能不能进行正常的订阅/发布。
代码编写
LWIP协议栈中带的有MQTT协议栈。我们直接用就可以,具体代码在mqtt.c文件中,这是一套较为完整的MQTT库,我们只需要编写mqtt客户端的C文件。
LWIP官方给了一个简单的样例,我们按照官方教程写一个mqtt客户端文件,命名为mqtt_client.c。
我写的带freeRTOS操作系统,不带操作系统的可以看这位大佬的文章STM32H743ZI+LWIP+MQTT(无OS)
#include <stdio.h>
#include <string.h>
#include "mqtt_client.h"
#include "mqtt.h"
#include "cmsis_os.h"
#define ipaddr_mqtt "120.79.215.239"
#define mqtt_usrname "897923957"
#define mqtt_password "XXXXX" //因为EMQ_CLOUD平台必备设置账号密码,所以要在里输入与云平台对于的密码
osThreadId_t mqttpub_TaskHandle;
//mqtt_client_t *client;
const osThreadAttr_t defaultTask_attribute = {
.name = "defaultTask",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 128 * 4
};
void example_do_connect(mqtt_client_t *client);
/* Called when publish is complete either with sucess or failure */
static void mqtt_pub_request_cb(void *arg, err_t result)
{
if(result != ERR_OK) {
printf("Publish result: %d\n", result);
}
}
void example_publish(mqtt_client_t *client, void *arg)
{
const char *pub_payload= "PubSubHubLubJub";
err_t err;
u8_t qos = 2; /* 0 1 or 2, see MQTT specification */
u8_t retain = 0; /* No don't retain such crappy payload... */
err = mqtt_publish(client, "pub_topic", pub_payload, strlen(pub_payload), qos, retain, mqtt_pub_request_cb, arg);
if(err != ERR_OK) {
printf("Publish err: %d\n", err);
}
}
static int inpub_id;
static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len)
{
printf("Incoming publish at topic %s with total length %u\n", topic, (unsigned int)tot_len);
/* Decode topic string into a user defined reference */
if(strcmp(topic, "subtopic") == 0) {
inpub_id = 0;
} else if(topic[0] == 'A') {
/* All topics starting with 'A' might be handled at the same way */
inpub_id = 1;
} else {
/* For all other topics */
inpub_id = 2;
}
}
static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags)
{
printf("Incoming publish payload with length %d, flags %u\n", len, (unsigned int)flags);
if(flags & MQTT_DATA_FLAG_LAST) {
/* Last fragment of payload received (or whole part if payload fits receive buffer
See MQTT_VAR_HEADER_BUFFER_LEN) */
/* Call function or do action depending on reference, in this case inpub_id */
if(inpub_id == 0) {
/* Don't trust the publisher, check zero termination */
// if(data[len-1] == 0) {
printf("mqtt_incoming_data_cb: %s\n", (const char *)data);
// }
} else if(inpub_id == 1) {
/* Call an 'A' function... */
} else {
//printf("mqtt_incoming_data_cb: %s\n", (const char *)data);
printf("mqtt_incoming_data_cb: Ignoring payload...\n");
}
} else {
/* Handle fragmented payload, store in buffer, write to file or whatever */
}
}
static void mqtt_sub_request_cb(void *arg, err_t result)
{
/* Just print the result code here for simplicity,
normal behaviour would be to take some action if subscribe fails like
notifying user, retry subscribe or disconnect from server */
printf("Subscribe result: %d\n", result);
}
static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
{
err_t err;
if(status == MQTT_CONNECT_ACCEPTED) {
printf("mqtt_connection_cb: Successfully connected\n");
/* Setup callback for incoming publish requests */
mqtt_set_inpub_callback(client, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, arg);
/* Subscribe to a topic named "subtopic" with QoS level 1, call mqtt_sub_request_cb with result */
err = mqtt_subscribe(client, "subtopic", 1, mqtt_sub_request_cb, arg);
if(err != ERR_OK) {
printf("mqtt_subscribe return: %d\n", err);
}
} else {
printf("mqtt_connection_cb: Disconnected, reason: %d\n", status);
/* Its more nice to be connected, so try to reconnect */
example_do_connect(client);
}
}
void example_do_connect(mqtt_client_t *client)
{
struct mqtt_connect_client_info_t ci;
ip_addr_t ip_mqtt;
err_t err;
/* Setup an empty client info structure */
memset(&ci, 0, sizeof(ci));
/* Minimal amount of information required is client identifier, so set it here */
ci.client_id = "lwip_test";
ci.client_user = mqtt_usrname;
ci.client_pass = mqtt_password;
/* Initiate client and connect to server, if this fails immediately an error code is returned
otherwise mqtt_connection_cb will be called with connection result after attempting
to establish a connection with the server.
For now MQTT version 3.1.1 is always used */
/*
err = ipaddr_aton(ipaddr_mqtt,&ip_mqtt);
if(err == 0)
printf("cp could not be converted to addr!!!!!!\n");
*/
IP4_ADDR(&ip_mqtt, 120, 79, 215, 239); //这里填自己云平台的ip地址
err = mqtt_client_connect(client, &ip_mqtt, MQTT_PORT, mqtt_connection_cb, 0, &ci);
printf("mqtt_connect return %d\n", err);
/* For now just print the result code if something goes wrong*/
if(err != ERR_OK) {
printf("mqtt_connect return %d\n", err);
}
}
/*
void mqtt_pub_thread(void *argument)
{
while (1)
{
example_publish(client, (void *)mqtt_pub_request_cb);
osDelay(1000);
}
}
*/
//example_do_connect(&static_client);
void mqtt_init(void)
{
mqtt_client_t *client = mqtt_client_new();
if(client != NULL) {
example_do_connect(client);
}
//mqttpub_TaskHandle = osThreadNew(mqtt_pub_thread, NULL, &defaultTask_attribute);
//example_publish(client, (void *)mqtt_pub_request_cb); //这个arg参数为什么赋值这个
//mqtt_disconnect(client);
}
这只是一个简单的例子,用来测试连接服务器。在初始化的时候调用mqtt_init()函数就可以了。
实验结果
调用初始化函数后,我们可以看到连接已经建立,能够在云平台上看到,lwip_test就是我们的STM32单片机。
也能够正常接收到其他客户端发布的消息。
注意一下客户端发布需要在同样的主题下发布,在这里我用的都是subtopic这个主题。
到此为止,这个实验已经做完。
踩坑记录
在这个实验中踩坑主要有2个。
1、Assertion “sys_timeout: timeout != NULL, pool MEMP_SYS_TIMEOUT is empty” failed at line 216 in src/core/timers.c错误
参考这位大佬的文章
解决方法
把CUBEMX这个选项改大一点,或者直接在代码中搜索这个宏的定义,自己改。默认好像是3,我改到了13.
2、程序没理由的死机
发现还是在上一篇文章中的问题一样,lwip栈设置的太小,在lwipopts.h中改大。
总结
这几天把MQTT协议做了一遍,做的比较简单,日后用的的话再深入研究,这最多算个demo,用来测试。下一个任务把OTA升级做一下,STM32有线网络这一块基本就结束了,有帮助的话多多点赞。