0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

RyanMqtt移植指南

冬至子 来源:Ryan_CW 作者:Ryan_CW 2023-10-09 10:24 次阅读

测试环境:stm32F401RCT6、RT-Thread版本: v4.1.0、RT-Thread Studio版本: 2.2.6、网络硬件使用ec800m移植at_socket使用sal框架。

1、移植介绍
RyanMqtt 库希望应用程序为以下接口提供实现:

system 接口
RyanMqtt 需要 RTOS 支持,必须实现如下接口才可以保证 mqtt 客户端的正常运行

1.jpg

network 接口
RyanMqtt 依赖于底层传输接口 API,必须实现该接口 API 才能在网络上发送和接收数据包

MQTT 协议要求基础传输层能够提供有序的、可靠的、双向传输(从客户端到服务端 和从服务端到客户端)的字节流

1.jpg

time 接口
RyanMqtt 依靠函数生成毫秒时间戳,用于计算持续时间和超时,内部已经做了数值溢出处理

1.jpg

2、开始移植
得益于RT-Thread驱动应用层分离的思想和SAL框架,platform/rtthread的适配层可以适应任何RT-Thread代码,所以我们就不拿RT-Thread来移植了。

使用FreeRTOS内核来移植,使用CMSIS-RTOS V2兼容层。

system 接口
系统接口,需要移植RTOS的接口。为方便管理类型使用平台结构体,修改platformSystem.h里面的结构体
就是线程和互斥锁

typedef struct
{
    osThreadId_t thread;
} platformThread_t;
typedef struct
{
    osMutexId_t mutex;
} platformMutex_t;

再来实现platformSystem.c里面的函数定义
注意里面的 platformPrint 函数,由于FreeRTOS没有官方的打印接口。记得修改为你的打印接口

#include "platformSystem.h"
/**

  • @brief 申请内存
  • @param size
  • @return void*
    /
    void platformMemoryMalloc(size_t size)
    {
    return pvPortMalloc(size);
    }
    /
  • @brief 释放内存
  • @param ptr
    /
    void platformMemoryFree(void ptr)
    {
    vPortFree(ptr);
    }
    /
  • @brief ms延时
  • @param ms
    /
    void platformDelay(uint32_t ms)
    {
    osDelay(ms);
    }
    /
    *
  • @brief 打印字符串函数,可通过串口打印出去
  • @param str
  • @param strLen
    /
    void platformPrint(char str, uint16_t strLen)
    {
    }
    /
  • @brief 初始化并运行线程
  • @param userData
  • @param platformThread
  • @param name
  • @param entry
  • @param param
  • @param stackSize
  • @param priority
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformThreadInit(void *userData,
    platformThread_t *platformThread,
    const char *name,
    void (*entry)(void ),
    void const param,
    uint32_t stackSize,
    uint32_t priority)
    {
    const osThreadAttr_t myTask02_attributes = {
    .name = name,
    .stack_size = stackSize,
    .priority = (osPriority_t)priority,
    };
    platformThread->thread = osThreadNew(entry, param, &myTask02_attributes);
    if (NULL == platformThread->thread)
    return RyanMqttNoRescourceError;
    return RyanMqttSuccessError;
    }
    /
  • @brief 销毁自身线程
  • @param userData
  • @param platformThread
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformThreadDestroy(void userData, platformThread_t platformThread)
    {
    osThreadTerminate(platformThread->thread);
    return RyanMqttSuccessError;
    }
    /
  • @brief 开启线程
  • @param userData
  • @param platformThread
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformThreadStart(void userData, platformThread_t platformThread)
    {
    osThreadResume(platformThread->thread);
    return RyanMqttSuccessError;
    }
    /
  • @brief 挂起线程
  • @param userData
  • @param platformThread
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformThreadStop(void userData, platformThread_t platformThread)
    {
    osThreadSuspend(platformThread->thread);
    return RyanMqttSuccessError;
    }
    /
  • @brief 互斥锁初始化
  • @param userData
  • @param platformMutex
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformMutexInit(void userData, platformMutex_t platformMutex)
    {
    const osMutexAttr_t myMutex01_attributes = {
    .name = "mqttMutex"};
    platformMutex->mutex = osMutexNew(&myMutex01_attributes);
    return RyanMqttSuccessError;
    }
    /
  • @brief 销毁互斥锁
  • @param userData
  • @param platformMutex
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformMutexDestroy(void userData, platformMutex_t platformMutex)
    {
    osMutexDelete(platformMutex->mutex);
    return RyanMqttSuccessError;
    }
    /
  • @brief 阻塞获取互斥锁
  • @param userData
  • @param platformMutex
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformMutexLock(void userData, platformMutex_t platformMutex)
    {
    osMutexAcquire(platformMutex->mutex, osWaitForever);
    return RyanMqttSuccessError;
    }
    /
  • @brief 释放互斥锁
  • @param userData
  • @param platformMutex
  • @return RyanMqttError_e
    */
    RyanMqttError_e platformMutexUnLock(void userData, platformMutex_t platformMutex)
    {
    osMutexRelease(platformMutex->mutex);
    return RyanMqttSuccessError;
    }
    /
  • @brief 进入临界区 / 关中断

/
void platformCriticalEnter(void)
{
osKernelLock();
}
/
*

  • @brief 退出临界区 / 开中断

*/
void platformCriticalExit(void)
{
osKernelUnlock();
}
time 接口
time接口,只需要提供一个ms时间戳就行,直接修改函数,这里使用FreeRTOS的心跳。

uint32_t platformUptimeMs(void)
{
if (1000 == osKernelGetTickFreq())
return (uint32_t)osKernelGetTickCount();
else
{
uint32_t tick = 0;
tick = osKernelGetTickCount() * 1000;
return (uint32_t)((tick + osKernelGetTickCount() - 1) / osKernelGetTickCount());
}
}
network 接口
MQTT 协议要求基础传输层能够提供有序的、可靠的、双向传输(从客户端到服务端 和从服务端到客户端)的字节流
由于FreeRTOS没有规定标准的网络层,你可以选择 FreeRTOS-Plus-TCP / FreeRTOS-Cellular-Interface/ lwip / W5500等网络方法,几乎RT-Thread支持的你也可以在FreeRTOS仓库找到。

这里以lwip为例,使用socket接口来实现,网络阻塞发送和接收使用 SO_SNDTIMEO 和 SO_RCVTIMEO 来实现,你也可以选择select / poll / epoll等方式。

修改 platformNetwork_t 结构体以支持 socket

typedef struct
{
    int socket;
} platformNetwork_t;

接着实现platformNetwork.c里面的函数

#define rlogEnable 1 // 是否使能日志
#define rlogColorEnable 1 // 是否使能日志颜色
#define rlogLevel (rlogLvlWarning) // 日志打印等级
#define rlogTag "RyanMqttNet" // 日志tag
#include "platformNetwork.h"
#include "RyanMqttLog.h"
/**

  • @brief 连接mqtt服务器
  • @param userData
  • @param platformNetwork
  • @param host
  • @param port
  • @return RyanMqttError_e
  • 成功返回RyanMqttSuccessError, 失败返回错误信息
    */
    RyanMqttError_e platformNetworkConnect(void *userData, platformNetwork_t *platformNetwork, const char *host, const char port)
    {
    RyanMqttError_e result = RyanMqttSuccessError;
    struct addrinfo addrList = NULL;
    struct addrinfo hints = {
    .ai_family = AF_UNSPEC,
    .ai_socktype = SOCK_STREAM,
    .ai_protocol = IPPROTO_TCP};
    if (getaddrinfo(host, port, &hints, &addrList) != 0)
    {
    result = RyanSocketFailedError;
    goto exit;
    }
    platformNetwork->socket = socket(addrList->ai_family, addrList->ai_socktype, addrList->ai_protocol);
    if (platformNetwork->socket < 0)
    {
    result = RyanSocketFailedError;
    goto exit;
    }
    if (connect(platformNetwork->socket, addrList->ai_addr, addrList->ai_addrlen) != 0)
    {
    platformNetworkClose(userData, platformNetwork);
    result = RyanMqttSocketConnectFailError;
    goto exit;
    }
    exit:
    if (NULL != addrList)
    freeaddrinfo(addrList);
    return result;
    }
    /
  • @brief 非阻塞接收数据
  • @param userData
  • @param platformNetwork
  • @param recvBuf
  • @param recvLen
  • @param timeout
  • @return RyanMqttError_e
  • socket错误返回 RyanSocketFailedError
  • 接收超时或者接收数据长度不等于期待数据接受长度 RyanMqttRecvPacketTimeOutError
  • 接收成功 RyanMqttSuccessError
    */
    RyanMqttError_e platformNetworkRecvAsync(void *userData, platformNetwork_t *platformNetwork, char recvBuf, int recvLen, int timeout)
    {
    int32_t recvResult = 0;
    int32_t offset = 0;
    int32_t timeOut2 = timeout;
    struct timeval tv = {0};
    platformTimer_t timer = {0};
    if (-1 == platformNetwork->socket)
    return RyanSocketFailedError;
    platformTimerCutdown(&timer, timeout);
    while ((offset < recvLen) && (0 != timeOut2))
    {
    tv.tv_sec = timeOut2 / 1000;
    tv.tv_usec = timeOut2 % 1000 * 1000;
    if (tv.tv_sec <= 0 && tv.tv_usec <= 100)
    {
    tv.tv_sec = 0;
    tv.tv_usec = 100;
    }
    setsockopt(platformNetwork->socket, SOL_SOCKET, SO_RCVTIMEO, (char )&tv, sizeof(struct timeval)); // 设置错做模式为非阻塞
    recvResult = recv(platformNetwork->socket, recvBuf + offset, recvLen - offset, 0);
    if (recvResult <= 0) // 小于零,表示错误,个别错误不代表socket错误
    {
    // 下列3种表示没问题,但需要推出发送
    if ((errno == EAGAIN || // 套接字已标记为非阻塞,而接收操作被阻塞或者接收超时
    errno == EWOULDBLOCK || // 发送时套接字发送缓冲区已满,或接收时套接字接收缓冲区为空
    errno == EINTR)) // 操作被信号中断
    break;
    return RyanSocketFailedError;
    }
    offset += recvResult;
    timeOut2 = platformTimerRemain(&timer);
    }
    if (offset != recvLen)
    return RyanMqttRecvPacketTimeOutError;
    return RyanMqttSuccessError;
    }
    /
  • @brief 非阻塞发送数据
  • @param userData
  • @param platformNetwork
  • @param sendBuf
  • @param sendLen
  • @param timeout
  • @return RyanMqttError_e
  • socket错误返回 RyanSocketFailedError
  • 接收超时或者接收数据长度不等于期待数据接受长度 RyanMqttRecvPacketTimeOutError
  • 接收成功 RyanMqttSuccessError
    */
    RyanMqttError_e platformNetworkSendAsync(void *userData, platformNetwork_t *platformNetwork, char sendBuf, int sendLen, int timeout)
    {
    int32_t sendResult = 0;
    int32_t offset = 0;
    int32_t timeOut2 = timeout;
    struct timeval tv = {0};
    platformTimer_t timer = {0};
    if (-1 == platformNetwork->socket)
    return RyanSocketFailedError;
    platformTimerCutdown(&timer, timeout);
    while ((offset < sendLen) && (0 != timeOut2))
    {
    tv.tv_sec = timeOut2 / 1000;
    tv.tv_usec = timeOut2 % 1000 * 1000;
    if (tv.tv_sec <= 0 && tv.tv_usec <= 100)
    {
    tv.tv_sec = 0;
    tv.tv_usec = 100;
    }
    setsockopt(platformNetwork->socket, SOL_SOCKET, SO_SNDTIMEO, (char )&tv, sizeof(struct timeval)); // 设置错做模式为非阻塞
    sendResult = send(platformNetwork->socket, sendBuf + offset, sendLen - offset, 0);
    if (sendResult <= 0) // 小于零,表示错误,个别错误不代表socket错误
    {
    // 下列3种表示没问题,但需要推出发送
    if ((errno == EAGAIN || // 套接字已标记为非阻塞,而接收操作被阻塞或者接收超时
    errno == EWOULDBLOCK || // 发送时套接字发送缓冲区已满,或接收时套接字接收缓冲区为空
    errno == EINTR)) // 操作被信号中断
    break;
    return RyanSocketFailedError;
    }
    offset += sendResult;
    timeOut2 = platformTimerRemain(&timer);
    }
    if (offset != sendLen)
    return RyanMqttSendPacketTimeOutError;
    return RyanMqttSuccessError;
    }
    /

    @brief 断开mqtt服务器连接

@param userData
@param platformNetwork
@return RyanMqttError_e
*/
RyanMqttError_e platformNetworkClose(void *userData, platformNetwork_t *platformNetwork)
{
if (platformNetwork->socket >= 0)
{
closesocket(platformNetwork->socket);
platformNetwork->socket = -1;
}
return RyanMqttSuccessError;
}

3、总结
可以看到,RyanMqtt移植非常简单,有专门的platform层用来移植。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FreeRTOS
    +关注

    关注

    12

    文章

    484

    浏览量

    62159
  • RT-Thread
    +关注

    关注

    31

    文章

    1288

    浏览量

    40111
  • STM32F401
    +关注

    关注

    1

    文章

    16

    浏览量

    10493
  • MQTT协议
    +关注

    关注

    0

    文章

    97

    浏览量

    5365
  • TCP通信
    +关注

    关注

    0

    文章

    146

    浏览量

    4222
收藏 人收藏

    评论

    相关推荐

    RyanMqtt使用介绍和示例代码(1)

    此步骤不做过多解释,是lwip就用lwip,是at设备就用at_socket。推荐所有平台都使用SAL框架(RyanMqtt软件包会自动使能)。
    的头像 发表于 09-28 10:09 1316次阅读
    <b class='flag-5'>RyanMqtt</b>使用介绍和示例代码(1)

    RyanMqtt实现MQTT3.1.1协议的客户端

    RyanMqttgithub: https://github.com/Ryan-CW-Code/RyanMqtt此库已经制作软件包提pr给RT-Thread/packages,机器审核已通过,但
    发表于 12-01 15:19

    emWin移植指南手册

    emWin移植指南:EA LPC1788 BSP到Keil MCB1700
    发表于 12-09 06:55

    RyanMqtt在间隔1s发送消息时常就报错RyanSocketFailedError

    使用RyanMqtt库以及例子,在间隔1s发送消息时常就报错RyanSocketFailedError,然后就进入重连机制
    发表于 08-04 16:48

    ATWILC器件Linux移植指南

    本用户指南介绍了如何将 ATWILC1000 和 ATWILC3000 Linux 驱动程序移植到另一个平台,以及移植驱动程序需要进行哪些修改
    发表于 04-29 10:10 8次下载

    TDE移植指南

    电子发烧友网站提供《TDE移植指南.pdf》资料免费下载
    发表于 09-27 11:09 1次下载
    TDE<b class='flag-5'>移植</b><b class='flag-5'>指南</b>

    AJE移植指南

    电子发烧友网站提供《AJE移植指南.pdf》资料免费下载
    发表于 09-27 11:10 1次下载
    AJE<b class='flag-5'>移植</b><b class='flag-5'>指南</b>

    UM-B-097:681 至 683 移植指南

    UM-B-097:681 至 683 移植指南
    发表于 03-14 20:09 0次下载
    UM-B-097:681 至 683 <b class='flag-5'>移植</b><b class='flag-5'>指南</b>

    OpenHarmony富设备移植指南(1)导言

    OpenHarmony富设备移植指南导言。在研究学习OpenHamony移植的路上,文档资料的缺失让我倍感痛苦,如今移植树莓派4b以及小米6的成功让我确信我的
    的头像 发表于 02-06 14:04 1324次阅读
    OpenHarmony富设备<b class='flag-5'>移植</b><b class='flag-5'>指南</b>(1)导言

    OpenHarmony富设备移植指南(2)从postmarketOS获取移植资源

    OpenHarmony富设备移植指南(2)从postmarketOS获取移植资源
    的头像 发表于 02-08 10:58 2327次阅读
    OpenHarmony富设备<b class='flag-5'>移植</b><b class='flag-5'>指南</b>(2)从postmarketOS获取<b class='flag-5'>移植</b>资源

    UM-B-097:681 至 683 移植指南

    UM-B-097:681 至 683 移植指南
    发表于 07-05 20:36 0次下载
    UM-B-097:681 至 683 <b class='flag-5'>移植</b><b class='flag-5'>指南</b>

    N76E003系列到MS51系列的移植指南

    N76E003系列到MS51系列的移植指南
    的头像 发表于 08-10 16:22 1134次阅读
    N76E003系列到MS51系列的<b class='flag-5'>移植</b><b class='flag-5'>指南</b>

    PN7160安卓移植指南

    电子发烧友网站提供《PN7160安卓移植指南.pdf》资料免费下载
    发表于 08-17 11:40 3次下载
    PN7160安卓<b class='flag-5'>移植</b><b class='flag-5'>指南</b>

    Vitis HLS移植指南

    电子发烧友网站提供《Vitis HLS移植指南.pdf》资料免费下载
    发表于 09-13 09:21 0次下载
    Vitis HLS<b class='flag-5'>移植</b><b class='flag-5'>指南</b>

    【鸿蒙】标准系统移植指南

    标准系统移植指南 本文描述了移植一块开发板的通用步骤,和具体芯片相关的详细移植过程无法在此一一列举。后续社区还会陆续发布开发板移植的实例供开
    的头像 发表于 02-27 14:36 894次阅读
    【鸿蒙】标准系统<b class='flag-5'>移植</b><b class='flag-5'>指南</b>