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有线网络这一块基本就结束了,有帮助的话多多点赞。

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值