kawaii-mqtt移植记录

kawaii-mqtt 移植总结:

​ 由于业务需要使用MQTT来传输数据,而对于单片机实现来说,一般就两种方案,第一种是通过网络模块的自带的MQTT通过AT命令或者MQTT透传方式,第二种是模块不具备MQTT协议通过TCP的方式实现MQTT协议。

​ 第一种方式基本基本按照模块AT命令配置好后就可以收发数据,比较容易实现。

​ 第二种方式需要基于TCP的方式移植一个MQTT协议栈。

​ 通过查阅资料MQTT相关的开源软件比较多:

kawaii-mqtt-master // 收录到了RTT软件包中,对MQTT支持比较全面。网上介绍比较多。
MQTT-C-master // 代码比较少,两三个文件。
umqtt-master  //RT-Thread 官方维护
RyanMqtt-main	//和kawaii-mqtt-master差不多都是对pahoMqtt的封装。维护的比较频繁。
wolfMQTT-master //STM32CubeMX 自带,但网上资料比较少。但是由于STM32CubeMX 自带,移植非常简单,勾选就可以了。

​ 我选用的是kawaii-mqtt-master,主要是网上资料稍微多一点,作者也录了相关移植视频,看了感觉用起来问题不大。但是似乎并不是一帆风顺,经过几天的折腾,终于顺利稳定的跑起来了。记录一下自己踩过的坑。

第一问题

​ 由于在移植过程中没有考虑到int platform_net_socket_recv_timeout(int fd, unsigned char *buf, int len, int timeout)这个函数中,timeout等于0的情况,等于0的时候,只返回一个字节,不管长度len 等于多少,就直接超时退出了。导致订阅总是失败。

int platform_net_socket_recv_timeout(int fd, unsigned char *buf, int len, int timeout)

第二个问题

​ mqtt_publish这个函数中, if (CLIENT_STATE_CONNECTED != mqtt_get_client_state©) 这里判断失败后,直接goto exit 去platform_mutex_unlock(&c->mqtt_write_lock);,而此时并没有上锁,我使用的freeRTOS ,如果没有上锁的话,开锁直接会断言失败。( configASSERT( pxTCB == pxCurrentTCB )😉 在开锁中判断的这个锁的持有者是否等于当前线程。如果没有上锁,那么这锁的持有者不等于当前线程。解决这个问题只需要将platform_mutex_lock(&c->mqtt_write_lock); 放到if (CLIENT_STATE_CONNECTED != mqtt_get_client_state©) 之前,示例如下:

int mqtt_publish(mqtt_client_t* c, const char* topic_filter, mqtt_message_t* msg)
{
    int len = 0;
    int rc = KAWAII_MQTT_FAILED_ERROR;
    platform_timer_t timer;
    MQTTString topic = MQTTString_initializer;
    topic.cstring = (char *)topic_filter;
    
    platform_mutex_lock(&c->mqtt_write_lock);// 将锁的位置修改到此处。
    if (CLIENT_STATE_CONNECTED != mqtt_get_client_state(c)) {
        rc = KAWAII_MQTT_NOT_CONNECT_ERROR;
       
        goto exit;
    }
    if ((NULL != msg->payload) && (0 == msg->payloadlen))
        msg->payloadlen = strlen((char*)msg->payload);

    //platform_mutex_lock(&c->mqtt_write_lock); 删除
    ...
exit:
    msg->payloadlen = 0;        // clear
    platform_mutex_unlock(&c->mqtt_write_lock);
   ...
    RETURN_ERROR(rc);     
}

第三个问题

​ 这个问题如果严格的MQTT协议或者网络稳定情况一般不会出现,我这里两种方式会导致这个函数有问题,一种是我没有关闭网路模块的心跳包,每次隔一段时间会上传一段数据(和MQTT协议无关的数据),另一种是MQTT频繁的收发数据,我突然断开模块,一帧MQTT数据可能收发不完整。两种问题都会导致msg_handler = mqtt_get_msg_handler(c, topic_name);返回为空,即msg_handler=NULL。而下面memset(message->payload, 0, message->payloadlen);这里直接导致软件崩溃。解决只需要增加空判断。代码修改如下:

static int mqtt_deliver_message(mqtt_client_t* c, MQTTString* topic_name, mqtt_message_t* message)
{
    int rc = KAWAII_MQTT_FAILED_ERROR;
    message_handlers_t *msg_handler;
    
    /* get mqtt message handler */
    msg_handler = mqtt_get_msg_handler(c, topic_name);
    logDebug(" %X  %d %s ", msg_handler, topic_name->lenstring.len, topic_name->lenstring.data);
    if (NULL != msg_handler) {
        message_data_t md;
        mqtt_new_message_data(&md, topic_name, message);    /* make a message data */
        msg_handler->handler(c, &md);       /* deliver the message */
        rc = KAWAII_MQTT_SUCCESS_ERROR;
    } else if (NULL != c->mqtt_interceptor_handler) {
        message_data_t md;
        mqtt_new_message_data(&md, topic_name, message);    /* make a message data */
        c->mqtt_interceptor_handler(c, &md);
        rc = KAWAII_MQTT_SUCCESS_ERROR;
    }
    if (NULL == msg_handler || NULL == c->mqtt_interceptor_handler) { RETURN_ERROR(rc); } //增加空判断。
     /*payload may not be string, so use meessage->payloadlen memeset zhaoshimin 20200629*/
     
    if (message->payload)memset(message->payload, 0, message->payloadlen);//增加空判断。
    if (topic_name->lenstring.data)memset(topic_name->lenstring.data, 0, topic_name->lenstring.len);//增加空判断。

    RETURN_ERROR(rc);
}

第四个问题

​ 这个问题的现象是我订阅了2个主题,但是每次第一次定义的主题可以正确的调用回调函数。多次调试后,问题如下:

主题订阅函数调用关系:
mqtt_subscribe 订阅主题函数
	mqtt_ack_list_record 记录一下应答事件
		mqtt_list_add_tail(&ack_handler->list, &c->mqtt_ack_handler_list); 添加到链表 通过链表管理应答事件

	其大致流程是调用订阅函数把订阅的主题放到链表中,等待服务器应答时,在找出对应的应答事件移除链表。放在链表的好处是超时再次重发。
主题订阅应答处理流程:
	mqtt_yield 线程处理函数 调用mqtt_packet_handle
		mqtt_packet_handle 数据包处理函数
				 rc = mqtt_suback_packet_handle(c, timer);  第一次处理服务器订阅应答正常,且rc等于0表示处理成功。
				 if (rc == KAWAII_MQTT_SUCCESS_ERROR) 后续有这个判断,有将rc赋值为报类型,
       					 rc = packet_type;         
            	  RETURN_ERROR(rc); 最终返回 订阅主题应答 包类型 为9
   
   mqtt_yield 线程处理函数 调用mqtt_packet_handle 后有一下判断         
     if (rc >= 0) {
            /* scan ack list, destroy ack handler that have timed out or resend them */
            mqtt_ack_list_scan(c, 1);        }
     此时rc等于9。需要执行 mqtt_ack_list_scan(c, 1); 
     mqtt_ack_list_scan 遍历整个应答的链表,判断是否有超时的节点和根据包类型判断是否需要重发。
         	最后执行了这两个函数
            mqtt_ack_handler_destroy(ack_handler);// 将应答链表移除。此时第二个主题的应答事件被移除了,当再次收到服务器应答时候,应答链表中不存在第二次订阅主题的事件,则反馈应答失败。
            mqtt_subtract_ack_handler_num(c);
   

解决办法:
	  if ((ack_handler->type ==  PUBACK) || (ack_handler->type ==  PUBREC) || (ack_handler->type ==  PUBREL) || (ack_handler->type ==  PUBCOMP) || (ack_handler->type == SUBACK))
	 在mqtt_ack_list_scan函数中增加了:ack_handler->type == SUBACK 判断,逻辑是如果超时了则重发订阅的主题数据包。

	导致这个问题情况应答是超时时间和mqtt_packet_handle处理时间间隔的问题,
	c->mqtt_cmd_timeout 和 订阅主题时 ack_handler->timer 这两个时间设置的问题,通过源码发现 这个时间设置是一样的,而订阅主题函数几乎是同时调用,第一个主题的ack_handler->timer和第二个主题的ack_handler->timer很有可能相等,那么处理完成了第一个主题确认后,执行mqtt_ack_list_scan,发现第二个主体已经超时了,超时了没做任何处理 直接移除应答事件链表。
	当然解决这个问题,还有其他方式,例如重新调整c->mqtt_cmd_timeout 和 订阅主题时 ack_handler->timer 超时时间。
	

​ 目前发现的这些问题,修改后已经正常跑了一天了。以上的问题理解有不合理的地方欢迎大家指出、讨论。

源码的下载链接:

kawaii-mqtt: 基于socket API之上的跨平台MQTT客户端 (gitee.com)

其他参考链接:

这个问题也可以看看,如果遇到了可以快速修改:记一次解决MQTT软件包内存泄露的心路历程_android mqtt 连接泄漏-CSDN博客

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【RT-Thread作品秀】物联网智能门锁 作者:张林 概述 随着人们生活水平的提高、物联网技术与人工智能技术的大力发展,住宅小区的智能化发展已成科学发展的必然趋势,智能化小区是当代计算机科学在住宅产业的综合应用,集成了信息、物联网、传感器、人工智能算法等技术,旨在改善住宅质量、住房舒适度、提高生活水平。继互联网、计算机科学之后,物联网技术成为第三次信息产业发展的浪潮,在不久的将来、物联网技术将会像互联网一样走进人类社会,深入我们的生活,本文根据物联网智能小区的设计理念进行了一些探索,并对智能小区的设计实现提供了思路或解决方案。 开发环境(所采用的软、硬件方案) 硬件:ART-PI、OPENMV、继电器 RT-Thread版本:4.03 开发工具及版本:RT-Thread Studio V1.5 RT-Thread使用情况概述 内核部分:信号量、互斥量、邮箱、线程管理、定时器 组件部分:wlan组件、PIN设备 软件包部分:Cjson、fal、kawaii_mqtt 硬件框架 通过art-pi控制openmv、采集openmv发送的信息、进行开关门处理,并通过mqtt发送至腾讯云服务器。 软件框架说明 启动程序后、进行硬件系统初始化、初始化内核对象、初始化相关软件包,初始化openmv,采集一openmv数据后送入邮箱线程,等待mqtt连接成功后将邮箱数据发送至服务器。 软件模块说明 数据采集线程采集到数据后送入全局邮箱、mqtt线程获取邮件、并将其发送至云服务器。 演示效果 比赛感悟 时间过得很快,rtt全连接大赛就要结束了,很感谢主办方提供给我这样一个机会去锻炼自己,去做自己喜欢的事情,在本次实践中我学会了很多东西,加深了对rtt操作系统的学习印象、掌握了rtt的使用方法、相信在不久的将来,这款操作系统将会在我的更多项目中得到应用、我会尽自己最大努力去提供一些rtt现在没有的传感器软件包、为rtt开源生态贡献自己微小的一份力量。这一路走来、有苦有甜,这些都是可贵的经历、是短暂大学生活浓彩画的一处斑斓。很感谢老师提供了这样一次锻炼自己的机会、在这次实践中、我深刻的认识到了rtt系统,认识到了ART-PI这款开发板的适用性,相信通过本次实践、我会更加喜爱嵌入式开发、将自己所学的知识运用到实际生活中,去解决更多的现实问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值