# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

前言

本文研究如何使用RT_Thread onenet組件將設備連接到中移onnet雲平臺,onenet組件提供多種方式連接雲平臺,本文使用mqtt方式連接,完成一個三色燈項目。

一、理論基礎

1.onenet平臺介紹

OneNET定位爲PaaS服務,即在物聯網應用和真實設備之間搭建高效、穩定、安全的應用平臺:面向設備,適配多種網絡環境和常見傳輸協議,提供各類硬件終端的快速接入方案和設備管理服務;面向企業應用,提供豐富的API和數據分發能力以滿足各類行業應用系統的開發需求,使物聯網企業可以更加專注於自身應用的開發,而不用將工作重心放在設備接入層的環境搭建上,從而縮短物聯網系統的形成周期,降低企業研發、運營和運維成本。

中移OneNET是中國移動基於物聯網技術和產業特點打造的開放平臺和生態環境,將面向智能家居、可穿戴設備、車聯網、移動健康、智能創客等多個領域開放。

以上介紹過於官方,我來從開一個開發者的視角說下哈,目前onnet是國內比較早的一批做雲的平臺,論壇十分活躍,各種技術分享資料層出不窮,非常適合希望瞭解物聯網的朋友深入學習,下圖是目前onenet社區的會員和帖子總體情況:

# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

二、使用實例

1.雲端創建產品

接下來咱們進入正題,首先需要在雲端創建一個產品,步驟如下:

創建產品
登錄onenet官網,註冊一個賬號,地址:https://open.iot.10086.cn, 點進開發者中心,創建產品,產品配置信息如下:
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

注意;操作系統這裏使用RT_Thread,沒有此選項,選用linux即可

創建數據點
數據流模板->添加數據流模板
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

創建產品數據點,此處創建power和color兩個數據點,power表示總開關,power爲0時候,燈關閉;power不爲0的時候,color數值起作用,用以選擇不同模式。

創建後臺顯示數據面板
應用管理->獨立應用->添加應用
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

注意:紅色、綠色、藍色按鈕和顏色顯示圖片均鏈接color數據點,顏色顯示圖片僅顯示左右,按鈕可以下發選擇不同的燈顏色。

到此爲止,我們已經完成了產品的創建工作,接下來將要處理設備接入問題。

2.設備SDK移植

下載onenet組件包
打開包管理工具,進入RT-Thread online packages/IOT-internet of things目錄,選擇Paho MQTT、webClient、cJSON組件,如下所示:
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

選擇IOT Cloud --->進入配置頁面,將創建的產品信息配置進去,onenet配置信息如下:
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

注意:選擇Enable OneNET automatic register device,設備可以註冊到雲端,而不需要每個設備都手動在平臺註冊,然後把設備信息再寫入設備中,這樣一來將爲工廠產線減少工作量,解決燒錄容易出現設備信息寫錯等問題。

下載fal、easyflash組件
這兩個組件主要是用來存放設備自動註冊獲取的設備認證信息,下次設備啓動檢測是否含有設備信息,如果有可以直接進行mqtt連雲,否則需要重新註冊。

進入RT-Thread online packages/system packages目錄,選擇fal組件,如下所示:
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

進入RT-Thread online packages/tools packages目錄,選擇easyflash組件,如下所示:
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

移植fal、easyflash組件

在w60x路徑下新建ports文件夾,然後在ports下創建兩個移植組件fal和easyflash,注意fal下SConscrip指定Group名字爲fal和packages/fal-latest中對應,easyflash亦然;至於移植細節,之後會另行介紹,現在着重開發項目。

修改onenet組件

a.修改函數指針:cmd_rsp_cb

函數指針cmd_rsp_cb指向一個函數,用來接收onenet下發的數據,RT_Thread中的處理方式是雲端下發數據後,通過調用onenet_mqtt.cmd_rsp_cb,將數據拋給應用層的一個函數,並通過參數uint8_t *resp_data獲取應用層處理後的結果,緊接着返回給onenet;我這裏所作的修改是用戶自行控制上發數據,因此不需要原函數中後兩個參數,此外增加了char topic_name,字段,因爲用戶自己控制上報數據時候,需要區分數據是雲端下發的相應還是設備觸發的主動上報。
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

設備主動上報topic_name是$dp,需要調用:
send_mq_msg("$dp", send_msg.data_ptr, send_msg.data_size);

設備應答雲端下發的topic_name通過cmd_rsp_cb對應的回調函數可以獲得,回調函數中首先獲取數據,然後調用:
send_mq_msg(topic_name, (uint8_t *)res_buf, rt_strlen(res_buf));

b.發送隊列統一發送數據,解決高頻控制或常問題

send_mq_msg()函數實際上是把數據扔進發送隊列中,發送隊列在一個線程中等待,檢測到有內容扔進隊列的時候,則從隊列中取出數據發送給onenet,實現代碼如下所示:

static void onenet_upload_entry(void *parameter)
{
    mq_send_msg_t send_msg = { 0x00 };
    led_blue_on();
    send_mq_msg("$dp", (uint8_t *)POST_DATA4, rt_strlen((char *)POST_DATA4));

    while (1)
    {
        rt_memset(&send_msg, 0x00, sizeof(send_msg));
        if (RT_EOK == rt_mq_recv(mq_send, &send_msg, sizeof(send_msg), RT_WAITING_FOREVER))
        {
            if (onenet_mqtt_upload_data(send_msg.topic_name, (const char *)send_msg.data_ptr))
            {
                rt_kprintf("upload has an error, stop uploading\n");
                break;
            }
            else
            {
                if (NULL==rt_strstr(send_msg.topic_name, "$dp"))  //上次上報是返回給mqtt服務器, 需要更新下數據以求同步數據到onnect後臺web頁面
                {
                    send_mq_msg("$dp", send_msg.data_ptr, send_msg.data_size);
                }
                rt_kprintf("buffer : %s\ntopic_name is:%s", send_msg.data_ptr, send_msg.topic_name);
                rt_free(send_msg.data_ptr);
            }
        }
        rt_thread_mdelay(50);
    }
    exit:
    rt_kprintf("upload thread exit!!!");
}

c.發送所有狀態數據到onenet

RT_Thread提供的是一次性發送一個數據點給雲端,我修改後也支持一次性上報所有數據,這樣APP只需要每次刷新各個狀態就行,不需要判斷是哪個數據發生了改變。
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

函數具體實現如下:

rt_err_t onenet_mqtt_upload_data(const char *topic_name, const char *msg_str)
{
    char *send_buffer = RT_NULL;
    rt_err_t result = RT_EOK;
    size_t length = 0;

    assert(msg_str);

  send_buffer = ONENET_MALLOC(strlen(msg_str) + 4);
    if (!(send_buffer))
    {
        log_e("ONENET mqtt upload string data failed! No memory for send buffer!");
        return -RT_ENOMEM;
    }
  *(send_buffer + strlen(msg_str) + 3) = 0x00;

    strncpy(&send_buffer[3], msg_str, strlen(msg_str));
    length = strlen(msg_str);
    /* mqtt head and json length */
    send_buffer[0] = 0x03;
    send_buffer[1] = (length & 0xff00) >> 8;
    send_buffer[2] = length & 0xff;
  length += 3;

    result = onenet_mqtt_publish(topic_name, (uint8_t *)send_buffer, length);
    if (result < 0)
    {
        log_e("onenet mqtt publish digit data failed!");
        goto __exit;
    }

__exit:
    if (send_buffer)
    {
        ONENET_FREE(send_buffer);
    }

    return result;
}

3.程序分析

mqtt組件入口
main()函數中註冊網絡狀態變化函數,然後連接網絡,連接成功後,調用onenet_mqtt_init()開啓mqtt服務:首先獲取設備信息,如果獲取不到,則進行註冊流程,獲取設備信息後,進行mqtt初始化設置,設置認證參數和數據接收、連接狀態變化回調函數。

int onenet_mqtt_init(void)
{
    int result = 0;

    if (init_ok)
    {
        LOG_D("onenet mqtt already init!");
        return 0;
    }

    if (onenet_get_info() < 0)
    {
        result = -1;
        goto __exit;
    }

    onenet_mqtt.onenet_info = &onenet_info;
    onenet_mqtt.cmd_rsp_cb = RT_NULL;

    if (onenet_mqtt_entry() < 0)
    {
        result = -2;
        goto __exit;
    }

__exit:
    if (!result)
    {
        LOG_I("RT-Thread OneNET package(V%s) initialize success.", ONENET_SW_VERSION);
        init_ok = RT_TRUE;
    }
    else
    {
        LOG_E("RT-Thread OneNET package(V%s) initialize failed(%d).", ONENET_SW_VERSION, result);
    }

    return result;
}

數據下發入口

當有平臺數據下發,設備會進入mqtt_callback()回調函數,回調函數中處理數據,然後拋給應用層數據接收處理函數onenet_cmd_rsp_cb(),可通過onenet_set_cmd_rsp_cb(onenet_cmd_rsp_cb)在應用層設置。

static void onenet_cmd_rsp_cb(char *topic_name, uint8_t *recv_data, size_t recv_size)
{
    char res_buf[128] = { 0 };
  int value = 0;

    rt_kprintf("recv data is %.*s\n", recv_size, recv_data);

  value = atoi((char *)recv_data);
  rt_kprintf("recv int data is:%d\r\n", value);
    /* match the command */
    if (value == 10) //總開關,默認爲紅色
    {
        /* led on */
        led_red_on();

        rt_snprintf(res_buf, sizeof(res_buf), "{\"power\":\"10\",\"color\":\"2\"}");

    }else if (value == 0)
  {
        /* led off */
        led_off();

        rt_snprintf(res_buf, sizeof(res_buf), "{\"power\":\"0\",\"color\":\"1\"}");

  }
  else if (value == 2)//紅色
    {
        /* red led on */
        led_red_on();

        rt_snprintf(res_buf, sizeof(res_buf), "{\"power\":\"10\",\"color\":\"2\"}");

    }
    else if (value == 3)//綠色
    {
        /* green led on */
        led_green_on();

        rt_snprintf(res_buf, sizeof(res_buf), "{\"power\":\"10\",\"color\":\"3\"}");

    }
    else if (value == 4)//藍色
    {
        /* blue led on */
        led_blue_on();

        rt_snprintf(res_buf, sizeof(res_buf), "{\"power\":\"10\",\"color\":\"4\"}");

    }
  send_mq_msg(topic_name, (uint8_t *)res_buf, rt_strlen(res_buf));
}

數據上報入口

當設備通過連接到onenet服務器的時候,會進入mqtt_online_callback()回調函數,數據上報入口函數就是在這裏觸發,創建一個發送線程,檢測發送隊列中的數據,一旦有數據需要發送,立刻取出隊列發送給雲端。

static void mqtt_online_callback(MQTTClient *c)
{
    LOG_D("Enter mqtt_online_callback!");
    onenet_upload_cycle();
}

4.配置

在applications目錄下新建一個文件夾:3-cloud/1-onenet_led,然後同理需要修改aplications/SConscript腳本。

Import('RTT_ROOT')
Import('rtconfig')
from building import *

cwd = GetCurrentDir()
src  = Glob('3-cloud/1-onenet_led/*.c')
CPPPATH = [cwd]

group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)

Return('group')

三、下載運行

在ENV控制檯,輸入scons命令,在build/Bin目錄下生成rtthread_1M.FLS,
燒錄運行後,設備端按照下圖所示連接電路:
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

設備Log如下所示:

 \ | /
- RT -     Thread Operating System
 / | \     4.0.0 build Jun 30 2019
 2006 - 2018 Copyright by rt-thread team
lwIP-2.0.2 initialized!
[32m[5] I/SAL_SOC: Socket Abstraction Layer initialize success.

[0m[32m[64] I/WLAN.dev: wlan init success

[0m[32m[95] I/WLAN.lwip: eth device init ok name:w0

[0m[32m[100] I/WLAN.dev: wlan init success

[0m[32m[132] I/WLAN.lwip: eth device init ok name:w1

[0m[D/FAL] (fal_flash_init:61) Flash device |                nor_flash | addr: 0x00000000 | len: 0x00100000 | blk_size: 0x00001000 |initialized finish.
[32;22m[I/FAL] ==================== FAL partition table ====================[0m
[32;22m[I/FAL] | name      | flash_dev |   offset   |    length  |[0m
[32;22m[I/FAL] -------------------------------------------------------------[0m
[32;22m[I/FAL] | app       | nor_flash | 0x00010000 | 0x00080000 |[0m
[32;22m[I/FAL] | download  | nor_flash | 0x00090000 | 0x00060000 |[0m
[32;22m[I/FAL] | fs_part   | nor_flash | 0x000f0000 | 0x0000b000 |[0m
[32;22m[I/FAL] | easyflash | nor_flash | 0x000fb000 | 0x00001000 |[0m
[32;22m[I/FAL] =============================================================[0m
[32;22m[I/FAL] RT-Thread Flash Abstraction Layer (V0.3.0) initialize success.[0m
[Flash] EasyFlash V3.3.0 is initialize success.
[Flash] You can get the latest version on https://github.com/armink/EasyFlash .
msh />[32m[4268] I/WLAN.mgnt: wifi connect success ssid:LBAGMY

[0m[D/ONENET] (onenet_mqtt_init:201) onnect mqtt init
[D/ONENET] (mqtt_connect_callback:85) Enter mqtt_connect_callback!
[36;22m[I/ONENET] RT-Thread OneNET package(V1.0.0) initialize success.[0m
[32m[5296] I/WLAN.lwip: Got IP address : 192.168.1.6

[0m[32m[5477] I/MQTT: MQTT server connect success

[0m[D/ONENET] (mqtt_online_callback:90) Enter mqtt_online_callback!
buffer : {"power":"10","color":"4"}
topic_name is:$dp[31m[30547] E/MQTT: [30547] wait Ping Response res: 0

[0m[D/ONENET] (mqtt_offline_callback:96) Enter mqtt_offline_callback!
[D/ONENET] (mqtt_connect_callback:85) Enter mqtt_connect_callback!
[32m[35662] I/MQTT: MQTT server connect success

[0m[D/ONENET] (mqtt_online_callback:90) Enter mqtt_online_callback!
buffer : {"power":"10","color":"4"}
topic_name is:$dp[D/ONENET] (mqtt_callback:60) topic $creq/a2a663b4-5b87-57ad-81d8-9e563659e540 receive a message
[D/ONENET] (mqtt_callback:62) message length is 1
recv data is 2
recv int data is:2

buffer : {"power":"10","color":"2"}
topic_name is:$crsp/a2a663b4-5b87-57ad-81d8-9e563659e540buffer : {"power":"10","color":"2"}
topic_name is:$dp[D/ONENET] (mqtt_callback:60) topic $creq/e08cd093-66ec-5e43-812c-ae7d2cd2cf5c receive a message
[D/ONENET] (mqtt_callback:62) message length is 1
recv data is 3
recv int data is:3

buffer : {"power":"10","color":"3"}
topic_name is:$crsp/e08cd093-66ec-5e43-812c-ae7d2cd2cf5cbuffer : {"power":"10","color":"3"}
topic_name is:$dp[D/ONENET] (mqtt_callback:60) topic $creq/65824a18-f17c-5286-b4b6-7f13a4d76246 receive a message
[D/ONENET] (mqtt_callback:62) message length is 1
recv data is 4
recv int data is:4

buffer : {"power":"10","color":"4"}
topic_name is:$crsp/65824a18-f17c-5286-b4b6-7f13a4d76246buffer : {"power":"10","color":"4"}
topic_name is:$dp

後臺顯示:

紅色燈模式

# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

綠色燈模式
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

藍色燈模式
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

四、結語

1.總結:

本節完,實際操作過程中需要注意的地方有如下幾點:

(1) 組件方式調整

之前的教程都是,每篇當作一個新的項目來做,每次都重新拉取組件包,但是更新本篇內容時候,我發現有時候組件包和RT_Thread menuconfig控制工具沒有很好的協調一致,比如本篇中用到的W600組件包,wm_libraries,這次配置工具並沒有該選項,於是通過RT_Thread官網尋找wm_libraries組件,提示可以在包管理工具中選擇,如下:
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

實際包管理工具如下圖所示,並沒有wm_libraries,但是有realtek的RTL8710組件,這也爲之後做個鋪墊,接下來會出一些RTL8710的教程,敬請期待。
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

考慮到目前存在的問題,爲了防止大家在使用的時候遇到和我類似的問題,從本篇開始,項目使用到的組件都會上傳到github,大家下載後,配置SConscript選擇需要運行的應用即可。

(2) 修改了RT_Thread的連接onenet連接組件包

a. 修改數據上報邏輯
原有組件包是每次回覆單個數據點的數據,修改後支持一次性上報所有數據點,同時調整mqtt數據上報處理邏輯。

b. 增加隊列緩衝發送機制
解決連續兩次調用數據發送接口,僅有第一次發出去的問題。

(3)資料獲取

如您在使用過程中有任何問題,請加QQ羣進一步交流,也可以github提Issue。

QQ交流羣:906015840 (備註:物聯網項目交流)

關注公衆後,回覆w600獲取資料

一葉孤沙出品:一沙一世界,一葉一菩提
# IT明星不是夢 # WIFI模塊開發教程之W600連雲篇1:onenet三色燈項目mqtt篇①

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章