LiteOS雲端對接教程05-LiteOS基於MQTTS對接華爲OC平臺實戰

1. LiteOS OC MQTT 抽象組件

概述

爲了適應各種各樣的使用mqtt接入華爲OC的模式,特採用該層次接口,對上提供應用所需的接口,對下允許接入方式的靈活適配。

OC MQTT AL的api接口聲明在中,使用相關的接口需要包含該頭文件。

配置並連接

對接服務器的所有信息保存在結構體oc_mqtt_config_t中,其定義在oc_mqtt_al.h中,如下:

typedef struct
{
    en_oc_mqtt_mode  boot_mode;     //對接模式:直連模式和bs模式
    uint8_t          lifetime;      //保持連接時長
    char            *server_addr;   //mqtt服務器地址,ip或者域名
    char            *server_port;   //mqtt服務器端口
    en_mqtt_al_security_t   sec_type;      //當前僅支持crt模式
    char            *id;            //設備對接ID,默認使用NOTEID(設備標識碼)對接
    char            *pwd;           //設備祕鑰
    //int           device_mode;    //未使用
    fn_oc_mqtt_msg_deal     msg_deal;//接收到數據的回調函數
    void            *msg_deal_arg;   //回調函數參數
}oc_mqtt_config_t;

其中boot_mode是對接模式,對應華爲平臺的兩種模式:

typedef enum
{
    en_oc_mqtt_mode_bs_static_nodeid_hmacsha256_notimecheck_json =0,
    en_oc_mqtt_mode_nobs_static_nodeid_hmacsha256_notimecheck_json,
    en_oc_mqtt_mode_last,
}en_oc_mqtt_mode;

本實驗中使用的是直連模式,選擇第二種。

在配置結構體完成之後,調用配置函數進行配置並連接,API如下:

/**
 * @brief the application use this function to configure the mqtt agent
 *
 * @param[in] param, refer to oc_mqtt_config_t
 *
 * @return code: define by en_oc_mqtt_err_code while 0 means success
 */
int oc_mqtt_config(oc_mqtt_config_t *param);

函數參數很清楚,將存放對接信息的結構體指針傳入即可,API的返回值是由en_oc_mqtt_err_code定義的枚舉類型,方便定位問題:

typedef enum
{
    en_oc_mqtt_err_ok          = 0,      ///< this means the status ok
    en_oc_mqtt_err_parafmt,              ///< this means the parameter err format
    en_oc_mqtt_err_network,              ///< this means the network wrong status
    en_oc_mqtt_err_conversion,           ///< this means the mqtt version err
    en_oc_mqtt_err_conclientid,          ///< this means the client id is err
    en_oc_mqtt_err_conserver,            ///< this means the server refused the service for some reason(likely the id and pwd)
    en_oc_mqtt_err_conuserpwd,           ///< bad user name or passwd
    en_oc_mqtt_err_conclient,            ///< the client id /user/pwd is right, but does not allowed
    en_oc_mqtt_err_subscribe,            ///< this means subscribe the topic failed
    en_oc_mqtt_err_publish,              ///< this means subscribe the topic failed
    en_oc_mqtt_err_configured,           ///< this means we has configured, please deconfigured it and then do configure again
    en_oc_mqtt_err_noconfigured,         ///< this means we have not configure it yet,so could not connect
    en_oc_mqtt_err_noconected,           ///< this means the connection has not been built, so you could not send data
    en_oc_mqtt_err_gethubaddrtimeout,    ///< this means get the hub address timeout
    en_oc_mqtt_err_sysmem,               ///< this means the system memory is not enough
    en_oc_mqtt_err_system,               ///< this means that the system porting may have some problem,maybe not install yet
    en_oc_mqtt_err_last,
}en_oc_mqtt_err_code_t;

數據上報

連接成功之後,向華爲雲平臺上報數據需要關注兩部分:

  • 發佈消息主題:這個OC_MQTT組件會自動幫我們搞定;
  • 發佈消息指令:發佈時由用戶指定;
  • 發佈消息內容:在使用JSON數據格式通信時,需要在本地將數據封裝爲JSON格式,OC_MQTT組件中已經編寫了一個助手程序,幫助我們封裝和數據。

數據封裝

上報華爲雲平臺的數據格式如下:

{
    "msgType": "deviceReq",
    "data": [{
        "serviceId": "Lightness",
        "serviceData": {
            "Lightness": 123
        }
    }]
}

這個數據格式是固定的,用戶指定的只有serviceIdservice_data,所以OC_MQTT提供了一個助手組件,使用時包含如下的頭文件即可:

#include <oc_mqtt_assistant.h>

在該文件中,對於封裝有效數據的結構體是:

typedef struct
{
    char           *name;    ///< key name
    char           *buf;     ///< used to storage the key value
    int             len;     ///< how long the key value
    en_value_type   type;    ///< the value type
}tag_key_value;
  • name:鍵名,比如Lightness;
  • buf:鍵值,比如123;
  • len:有效數據緩衝區長度;
  • type:鍵值類型;

鍵值類型已經枚舉出了,支持如下三種類型,如下:

typedef enum
{
    en_key_value_type_int = 0,
    en_key_value_type_string,
    en_key_value_type_array,
}en_value_type;

上述這個 tagkeyvalue 只是一條有效數據,對於需要同時上報多條數據的情況,助手程序也提供了一個鏈表,使用時只需要將next指針指向下一條數據即可。

typedef struct
{
    void            *next;
    tag_key_value    item;
}tag_key_value_list;

將數據存放在鏈表及結構體中之後,完成了初步封裝,需要再加上上報華爲雲的信息,整體存放在下面的結構體中:

typedef struct
{
    char                  *serviceid;
    tag_key_value_list    *paralst;     //之前封裝的有效數據鏈表
    char                  *eventtime;
    en_oc_mqtt_has_more    hasmore;
}tag_oc_mqtt_report;

其中eventtime可以留空,值爲NULL即可,hasmore用來表明是否還有更多的信息,枚舉列表如下:

typedef enum
{
    en_oc_mqtt_has_more_no = 0,
    en_oc_mqtt_has_more_yes =1,
}en_oc_mqtt_has_more;

最後,上報數據封裝完成,使用如下API將結構體數據格式化爲一個cjson*類型的數據,便於使用:

cJSON *oc_mqtt_json_fmt_report(tag_oc_mqtt_report  *report);

上報消息

上面我們封裝的消息是一個cjson數據鏈表,接下來首先包含cJSON組件的頭文件:

#include <cJSON.h>

然後調用cJSON組件提供的API將cjson數據鏈表直接打印爲一個不格式化的字符串,方便發送:

(char *) cJSON_PrintUnformatted(const cJSON *item);

最後,調用OC_MQTT提供的API,上報這個字符串:

/**
 * @brief the application use this function to send message to the default topic(old interface)
 *
 * @param[in] msg:the message to send
 *
 * @param[in] msg_len:the message length
 *
 * @param[in] qos: defines as the mqtt does
 *
 * @return code: define by en_oc_mqtt_err_code while 0 means success
 */
int oc_mqtt_report(uint8_t *msg,int len, int qos);

  • msg:剛剛轉化完成打印出的字符串指針;
  • len:字符串長度;
  • qos:發佈消息質量,如下;

發佈消息質量枚舉值如下(在mqtt_al.h中):

/** @brief enum all the qos supported for the application */
typedef enum
{
    en_mqtt_al_qos_0 = 0,     ///< mqtt QOS 0
    en_mqtt_al_qos_1,         ///< mqtt QOS 1
    en_mqtt_al_qos_2,         ///< mqtt QOS 2
    en_mqtt_al_qos_err
}en_mqtt_al_qos_t;

命令接收

配置連接華爲雲OC平臺時,OC_MQTT組件會**自動訂閱主題**:

/huawei/v1/devices/{NoteId}/command/json

當OC平臺發佈該主題數據時,OC_MQTT組件會拉起接收回調函數將數據保存,進而用戶解析接收到的JSON數據即可,其中在上一篇文章中測試時,平臺發送開啓命令,客戶端接收到的消息如下:

{
    "msgType":"cloudReq",
    "serviceId":"Lightness",
    "paras":{"LED_Ctrl":"on"},
    "cmd":"LED_Ctrl",
    "hasMore":0,
    "mid":10
}

平臺發送關閉命令,客戶端接收到的消息如下:

{
    "msgType":"cloudReq",
    "serviceId":"Lightness",
    "paras":{"LED_Ctrl":"off"},
    "cmd":"LED_Ctrl",
    "hasMore":0,
    "mid":11
}

接下來編寫解析任務時可以參考此數據。

下發數據接收回調函數

實現該回調函數:

static int app_msg_deal(void *arg,mqtt_al_msgrcv_t *msg)

實現的時候,接收數據大小在msg->msg.len中,接收數據在msg->msg.data中。

實現之後在最開始的連接信息結構體中配置即可:

如果下發命令速度較快,可以使用隊列接收數據,但是需要注意,使用會佔用更多的動態內存空間。

接收數據處理任務

接收數據處理任務需要單獨創建一個任務,與接收回調函數之間使用一個信號量進行同步,具體參考下面的示例。

OC_MQTT組件自動初始化

在SDK目錄中的IoT_LINK_1.0.0iot_linklink_main.c中可以看到自動初始化函數:

2. 配置準備

Makefile配置

因爲本次實驗用到的組件較多:

  • AT框架
  • ESP8266設備驅動
  • 串口驅動框架
  • cJSON組件
  • SAL組件
  • MQTT組件
  • MBEDTLS組件
  • OC_MQTT組件

這些實驗代碼全部編譯下來,有350KB,而小熊派開發板所使用的主控芯片STM32L431RCT6的 Flash 僅有256KB,會導致編譯器無法鏈接出可執行文件,所以要在makefile中修改優化選項,修改爲-Os參數,即最大限度的優化代碼尺寸,並去掉-g參數,即代碼只能下載運行,無法調試,如圖:

ESP8266設備配置

在工程目錄中的OS_CONFIG/iot_link_config.h文件中,配置ESP8266設備的波特率和設備名稱:

WIFI對接信息配置

SDK:C:UsersAdministrator.icodesdkIoT_LINK_1.0.0(其中Administrator是實驗電腦的用戶名)。

在SDK目錄中的iot_linknetworktcpipesp8266_socketesp8266_socket_imp.c文件中,配置連接信息:

之後修改同路徑下的esp8266_socket_imp.mk文件,如圖,將 TOPDIR 改爲 SDKDIR :

修改mbedtls路徑配置

在SDK目錄中的iot_linknetworkdtlsmbedtlsmbedtls.mk文件中,如圖,將 TOPDIR 改爲 SDKDIR :

修改paho_mqtt文件路徑

在SDK目錄中的iot_linknetworkmqttpaho_mqttpaho_mqtt.mk文件中,如圖,將 TOPDIR 改爲 SDKDIR :

3. 上雲實驗

編寫實驗文件

在 Demo 文件夾下創建cloud_test_demo文件夾,在其中創建oc_tls_mqtt_demo.c文件。

編寫以下代碼:

#include <osal.h>
#include <oc_mqtt_al.h>
#include <oc_mqtt_assistant.h>
#include <cJSON.h>
#include <string.h>

#define DEFAULT_LIFETIME            60
#define DEFAULT_SERVER_IPV4         "49.4.93.24"
#define DEFAULT_SERVER_PORT         "8883"
#define CN_MQTT_EP_NOTEID           "321321321321"
#define CN_MQTT_EP_PASSWD           "4ac51ec23edeb3eb34e4"

#define recv_buf_len 150
static char recv_buffer[recv_buf_len];   //下發數據接收緩衝區
static int  recv_datalen;                //表示接收數據長度

osal_semp_t recv_sync;  //命令接收回調函數和處理函數之間的信號量

static int app_msg_deal(void *arg,mqtt_al_msgrcv_t *msg)
{
    int ret = -1;

    if(msg->msg.len < recv_buf_len)
    {
        //不超過緩衝區,保存數據
        memcpy(recv_buffer,msg->msg.data,msg->msg.len );
        recv_buffer[msg->msg.len] = '\0';
        recv_datalen = msg->msg.len;

        //釋放信號量,交由數據處理線程進行處理
        osal_semp_post(recv_sync);
        ret = 0;
    }
    return ret;
}

static int task_recv_cmd_entry(void *args)
{
    while(1)
    {
        /* 阻塞等待信號量 */
        osal_semp_pend(recv_sync,cn_osal_timeout_forever);

        if(strstr(recv_buffer, "on"))
        {
                printf("-----------------LED ON !!! --------------------\r\n");
        }
        else if(strstr(recv_buffer, "off"))
        {
                printf("-----------------LED OFF !!! --------------------\r\n");
        }
    }
    return 0;
}

static int task_report_msg_entry(void *args)
{
    int ret = -1;

    oc_mqtt_config_t config;    // oc_mqtt 連接信息配置結構體
    tag_key_value_list lst;     //有效數據結構體
    int lightness_value = 0;   //亮度值
    tag_oc_mqtt_report report;  //上報數據結構體
    cJSON* root = NULL;         //存放上報數據的cjson鏈表
    char* report_msg = NULL;    //存放上報數據的字符串

    /* 設置連接信息 */
    config.boot_mode = en_oc_mqtt_mode_nobs_static_nodeid_hmacsha256_notimecheck_json;
    config.msg_deal = app_msg_deal;
    config.msg_deal_arg = NULL;
    config.lifetime = DEFAULT_LIFETIME;
    config.server_addr = DEFAULT_SERVER_IPV4;
    config.server_port = DEFAULT_SERVER_PORT;
    config.id = CN_MQTT_EP_NOTEID;
    config.pwd= CN_MQTT_EP_PASSWD;
    config.sec_type = en_mqtt_al_security_cas;

    /* 配置並對接雲平臺 */
    ret = oc_mqtt_config(&config);
    if(ret != en_oc_mqtt_err_ok)
    {
        printf("config and connect error, ret = %d.\r\n", ret);
        return -1;
    }
    else
    {
        printf("config and connect success.\r\n");
    }

    /* 連接成功後,開始上報消息 */
    while(1)
    {
        //封裝數據鏈表
        lst.item.name = "Lightness";
        lst.item.buf = (char*)&lightness_value;
        lst.item.len = sizeof(lightness_value);
        lst.item.type = en_key_value_type_int;
        lst.next = NULL;

        //封裝上報數據
        report.hasmore = en_oc_mqtt_has_more_no;
        report.paralst= &lst;
        report.serviceid = "Lightness";
        report.eventtime = NULL;

        //轉換爲cjson*類型數據
        root = oc_mqtt_json_fmt_report(&report);
        if(NULL != root)
        {
            //打印爲未格式化的字符串(去掉格式符,減小上報數據長度)
            report_msg = cJSON_PrintUnformatted(root);

            //發佈消息
            ret = oc_mqtt_report((uint8_t *)report_msg,strlen(report_msg),en_mqtt_al_qos_1);
            if(ret != en_oc_mqtt_err_ok)
            {
                printf("report fail, ret = %d.\r\n", ret);
                return -1;
            }
            else
            {
                printf("report success: lightness_value = %d, msg = %s\r\n", lightness_value, report_msg);
            }
            /* 一次消息發佈完畢 */
            //釋放內存
            osal_free(report_msg);
            cJSON_Delete(root);

        }
        //改變亮度值,實際可以替換爲採集實際亮度值
        lightness_value  ;
        //掛起5s
        osal_task_sleep(5*1000);
    }
}


int standard_app_demo_main()
{
    /* 創建信號量 */
    osal_semp_create(&recv_sync,1,0);

    /* 創建任務 */
    osal_task_create("task_reportmsg",task_report_msg_entry,NULL,0x800,NULL,8);
    osal_task_create("task_recv_cmd",task_recv_cmd_entry,NULL,0x400,NULL,8);

    return 0;
}

添加路徑

在user_demo.mk中添加如下:

    #example for oc_tls_mqtt_demo
    ifeq ($(CONFIG_USER_DEMO), "oc_tls_mqtt_demo")    
        user_demo_src  = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/cloud_test_demo/oc_tls_mqtt_demo.c}
    endif

添加位置如下:

配置.sdkconfig

特別說明:實驗時需要關閉shell組件,否則會因動態內存分配失敗而導致TLS無法連接到華爲OC平臺。

上報數據實驗結果

編譯,下載,在雲端的實驗現象如下:

在本地的實驗現象如下:

命令下發實驗結果

在雲端下發“on”命令:

在串口助手中可以看到:

下發“off”命令:

在串口助手中可以看到:

發佈了50 篇原創文章 · 獲贊 2 · 訪問量 5588
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章