本文描述在支持TCP的廣域網(wǎng)模組上集成SDK的方法。

說明 基于Link SDK v3.0.1編寫。

Link SDK是阿里云IoT提供的用于將設(shè)備連接到阿里云IoT的設(shè)備端SDK,用于完成設(shè)備認(rèn)證、數(shù)據(jù)通信等功能。將Link SDK集成到通信模組中,可以帶來以下好處:

  • 設(shè)備廠商在MCU上無需關(guān)心如何連接阿里云IoT,只是通過調(diào)用模組提供的AT指令就可以連接阿里云IoT,因此對MCU的資源消耗沒有增加。
  • 阿里將在認(rèn)證合作伙伴頁面露出通過認(rèn)證的模組型號、購買鏈接、開發(fā)指導(dǎo)等文檔,引導(dǎo)設(shè)備商以及服務(wù)提供商購買通過認(rèn)證的通信模組連接阿里云IoT。
使用集成了SDK的模組開發(fā)設(shè)備的示意圖如下所示:集成了SDK的模組開發(fā)設(shè)備

設(shè)備商開發(fā)設(shè)備的流程為:

  • 購買集成了阿里云Link SDK的模組。
  • 在MCU上通過模組提供的AT指令連接阿里云,以及從阿里云IoT收發(fā)數(shù)據(jù)。
  • 在阿里云IoT上部署云端服務(wù),對設(shè)備進(jìn)行管理。

對于模組商而言,在模組上需要完成的工作包括:

  • 將Link SDK正確的集成到模組上。
  • 提供相應(yīng)的連接阿里云IoT物聯(lián)網(wǎng)的AT指令供MCU調(diào)用。

下面的文檔只講解如何將SDK的MQTT功能集成到模組上。對于模組商來說,集成SDK的功能越多,MCU側(cè)設(shè)備廠商的開發(fā)功能越少,因此建議模組商盡可能多的集成SDK的功能,比如OTA、物模型等。

在阿里云物聯(lián)網(wǎng)平臺上的操作

為了驗(yàn)證模組上集成的SDK是否運(yùn)行正確,需要將一個測試設(shè)備連接到阿里云物聯(lián)網(wǎng)平臺。用戶需要在阿里云物聯(lián)網(wǎng)平臺上創(chuàng)建一個產(chǎn)品,并創(chuàng)建一個該產(chǎn)品的設(shè)備實(shí)例以獲取到設(shè)備的身份信息。

  1. 點(diǎn)擊阿里云物聯(lián)網(wǎng)平臺登錄控制臺,模組商需要注冊一個阿里云賬號,注冊阿里云賬號是免費(fèi)的。
  2. 登錄阿里云物聯(lián)網(wǎng)平臺的控制臺之后,按照創(chuàng)建產(chǎn)品描述的步驟創(chuàng)建一個產(chǎn)品。在產(chǎn)品頁面可以獲取到產(chǎn)品的ProductKey和ProductSecret。
  3. 參照創(chuàng)建設(shè)備描述的步驟添加一個測試設(shè)備,在設(shè)備頁面可以獲取到設(shè)備的DeviceName和DeviceSecret。

集成SDK的開發(fā)過程

模組商在模組上集成SDK時,需要進(jìn)行下面幾個開發(fā)過程。

集成SDK的開發(fā)過程

SDK配置與代碼抽取

配置SDK

SDK包含的功能較多,下面講解如何配置本場景中需要的功能。

運(yùn)行配置命令

  • Linux系統(tǒng)

    進(jìn)入SDK的根目錄下,運(yùn)行命令

    make menuconfig
                
  • Windows系統(tǒng)

    運(yùn)行SDK根目錄下的config.bat

    config.bat

上面的兩種方式都會啟動SDK的配置工具,界面如下所示:

界面顯示1

使能需要的SDK功能

在功能配置界面,按下空格鍵可以選中或者失效某個功能,使用小鍵盤的上下鍵來在不同功能之間切換;如果想知道每個選項(xiàng)的具體含義,先用方向鍵將高亮光條移到那個選項(xiàng)上,再按鍵盤上的“h”按鍵,將出現(xiàn)幫助文本,說明選項(xiàng)是什么含義,以及打開了和關(guān)閉了意味著什么。

  • 如果編譯環(huán)境有自帶標(biāo)準(zhǔn)頭文件<stdint.h>,請使能選項(xiàng):

    PLATFORM_HAS_STDINT

  • 如果目標(biāo)系統(tǒng)上運(yùn)行有嵌入式操作系統(tǒng),請使能選項(xiàng):

    PLATFORM_HAS_OS

  • 由于模組支持TCP但是不支持MQTT,因此必須使能下面三項(xiàng)配置:
    • FEATURE_MQTT_COMM_ENABLED,使用阿里SDK提供的MQTT API與云端通信。
    • FEATURE_MQTT_DEFAULT_IMPL,使用阿里SDK中自帶的MQTT Client實(shí)現(xiàn),用戶需要實(shí)現(xiàn)相關(guān)的TCP連接的創(chuàng)建、連接、數(shù)據(jù)收發(fā)過程。
    • FEATURE_MQTT_DIRECT,設(shè)備端指定阿里云物聯(lián)網(wǎng)云端站點(diǎn)。

建議使能FEATURE_SUPPORT_TLS,讓數(shù)據(jù)與物聯(lián)網(wǎng)平臺之間的數(shù)據(jù)通信是加密的。本文檔中為了降低適配工作量,未使能該選項(xiàng)。

其它功能均無需使能。

抽取選中功能的源代碼

運(yùn)行SDK根目錄下的extract.sh(windows下運(yùn)行extract.bat),客戶選中的功能所對應(yīng)的代碼將會被放置到文件夾output。

將SDK代碼文件加入客戶編譯環(huán)境

客戶將上一個步驟中得到的Link SDK的代碼文件從output目錄復(fù)制到自己的工程目錄中,并修改自己的編譯環(huán)境或者開發(fā)工具將這些代碼文件集成到編譯環(huán)境。

客戶需要集成的文件包括eng目錄下面的dev_sign、infra、mqtt、wrappers目錄下的代碼文件。

實(shí)現(xiàn)HAL對接函數(shù)

Link SDK被設(shè)計(jì)為可以在不同的操作系統(tǒng)上運(yùn)行,或者甚至在不支持操作系統(tǒng)的MCU上運(yùn)行,因此與系統(tǒng)相關(guān)的操作被定義成一些HAL函數(shù),需要客戶進(jìn)行實(shí)現(xiàn);另外,由于不同的通信模組上的OS不同,所以與通信模組上TCP相關(guān)的操作也被定義成HAL函數(shù)需要客戶進(jìn)行實(shí)現(xiàn)。

所有HAL函數(shù)位于文件為output/eng/wrappers/wrapper.c中。

系統(tǒng)相關(guān)HAL

必須實(shí)現(xiàn)函數(shù):

** ** 函數(shù)名 說明
1 HAL_Malloc 對應(yīng)標(biāo)準(zhǔn)C庫中的malloc(), 按入?yún)㈤L度開辟一片可用內(nèi)存, 并返回首地址
2 HAL_Free 對應(yīng)標(biāo)準(zhǔn)C庫中的free(), 將入?yún)⒅羔標(biāo)赶虻膬?nèi)存空間釋放
3 HAL_Printf 對應(yīng)標(biāo)準(zhǔn)C庫中的printf(), 根據(jù)入?yún)⒏袷阶址畬⒆址谋撅@示到終端。如果用戶的調(diào)試環(huán)境有更好的調(diào)試手段,該函數(shù)無需實(shí)現(xiàn)
4 HAL_Snprintf 類似printf, 但輸出的結(jié)果不再是顯示到終端, 而是存入指定的緩沖區(qū)內(nèi)存
5 HAL_UptimeMs 返回一個uint64_t類型的數(shù)值, 表達(dá)設(shè)備啟動后到當(dāng)前時間點(diǎn)過去的毫秒數(shù)
6 HAL_SleepMs 按照指定入?yún)⒌臄?shù)值, 睡眠相應(yīng)的毫秒, 比如參數(shù)是10, 那么就會睡眠10毫秒

對以上函數(shù)若需了解更多細(xì)節(jié), 可訪問SDK官方文檔頁面

另外,在SDK加壓后的目錄(非代碼抽取目錄)wrappers/os下有HAL的參考實(shí)現(xiàn),用戶可以查看是否有自己需要的OS參考實(shí)現(xiàn),若未提供則用戶需要自己進(jìn)行實(shí)現(xiàn)。

可選實(shí)現(xiàn)函數(shù)

如果模組沒有運(yùn)行OS,或者SDK的MQTT API并沒有在多個線程中被調(diào)用,以下函數(shù)可以不用修改wrapper.c中相關(guān)的函數(shù)實(shí)現(xiàn);在有OS場景下并且MQTT API被APP在多個線程中調(diào)用,則需要用戶對接以下函數(shù):

** ** 函數(shù)名 說明
1 HAL_MutexCreate 創(chuàng)建一個互斥鎖, 返回值可以傳遞給HAL_MutexLock/Unlock
2 HAL_MutexDestroy 銷毀一個互斥鎖, 這個鎖由入?yún)?biāo)識
3 HAL_MutexLock 申請互斥鎖, 如果當(dāng)前該鎖由其它線程持有, 則當(dāng)前線程睡眠, 否則繼續(xù)
4 HAL_MutexUnlock 釋放互斥鎖, 此后當(dāng)前在該鎖上睡眠的其它線程將取得鎖并往下執(zhí)行
5 HAL_SemaphoreCreate 創(chuàng)建一個信號量, 返回值可以傳遞給HAL_SemaphorePost/Wait
6 HAL_SemaphoreDestroy 銷毀一個信號量, 這個信號量由入?yún)?biāo)識
7 HAL_SemaphorePost 在指定的計(jì)數(shù)信號量上做自增操作, 解除其它線程的等待
8 HAL_SemaphoreWait 在指定的計(jì)數(shù)信號量上等待并做自減操作
9 HAL_ThreadCreate 根據(jù)配置參數(shù)創(chuàng)建thread

TCP相關(guān)HAL

MQTT基于TCP進(jìn)行通信,模組商需要實(shí)現(xiàn)下面四個TCP HAL函數(shù)。

序號 函數(shù)名 說明
1 HAL_TCP_Establish 建立一個TCP連接。注意:* 入?yún)ost是一個域名,需要轉(zhuǎn)換為IP地址* 返回值是tcp的socket號
2 HAL_TCP_Destroy 關(guān)閉tcp連接,入?yún)⑹荋AL_TCP_Establish的返回值,返回值0表示成功
3 HAL_TCP_Write 通過TCP連接發(fā)送數(shù)據(jù)。注意:* 該函數(shù)傳入了一個超時時間,如果超時仍未將數(shù)據(jù)發(fā)送結(jié)束那么函數(shù)也需要返回;* 如果TCP連接已斷開,需要返回一個小于0的負(fù)數(shù)
4 HAL_TCP_Read 在指定的時間內(nèi)讀取數(shù)據(jù)并返回,該函數(shù)的入?yún)⒅兄付丝山邮盏臄?shù)據(jù)的最大長度,如果從TCP中讀取到該最大長度的數(shù)據(jù),那么可以立即返回

產(chǎn)品相關(guān)HAL

下面的HAL用于獲取產(chǎn)品的身份認(rèn)證信息,設(shè)備廠商需要設(shè)計(jì)如何在設(shè)備上燒寫設(shè)備身份信息,并通過下面的HAL函數(shù)將其讀出后提供給SDK:

序號 函數(shù)名 說明
1 HAL_GetProductKey 獲取設(shè)備的ProductKey, 用于標(biāo)識設(shè)備的產(chǎn)品型號
2 HAL_GetDeviceName 獲取設(shè)備的DeviceName, 用于唯一標(biāo)識單個設(shè)備
3 HAL_GetDeviceSecret 獲取設(shè)備的DeviceSecret, 用于標(biāo)識單個設(shè)備的密鑰

對以上函數(shù)若需了解更多細(xì)節(jié), 可直接訪問SDK官方文檔頁面

注:這幾個參數(shù)在實(shí)際產(chǎn)品開發(fā)時應(yīng)該由設(shè)備廠商通過AT指令告知模組,模組商調(diào)試時可以將自己創(chuàng)建的測試設(shè)備的ProductKey、DeviceName、DeviceSecret直接通過上面這幾個函數(shù)返回。

參照example實(shí)現(xiàn)產(chǎn)品功能

模組商可參考o(jì)utput文件夾中的 eng/examples/mqtt_example.c進(jìn)行功能調(diào)試, 設(shè)備廠商可以將該文件復(fù)制到產(chǎn)品工程中,對其進(jìn)行修改后使用。

該example將連接設(shè)備到阿里云,訂閱一個指定的topic并發(fā)送數(shù)據(jù)給該topic,即設(shè)備上報(bào)的消息會被物聯(lián)網(wǎng)平臺發(fā)送給設(shè)備,下面是example的大概過程說明:

注意:需要在云端將該topic從默認(rèn)的權(quán)限從”訂閱”修改為”發(fā)布和訂閱”,如下圖所示:

從程序入口的 main() 函數(shù)看起, 首先是調(diào)用模組提供的HAL函數(shù)獲取產(chǎn)品的身份信息:

int main(int argc, char *argv[])
{
    void                   *pclient = NULL;
    int                     res = 0;
    int                     loop_cnt = 0;
    iotx_mqtt_param_t       mqtt_params;

    HAL_GetProductKey(DEMO_PRODUCT_KEY);
    HAL_GetDeviceName(DEMO_DEVICE_NAME);
    HAL_GetDeviceSecret(DEMO_DEVICE_SECRET);

    EXAMPLE_TRACE("mqtt example");
            

注:

  • 上面的三個HAL_GetXXX函數(shù)是獲取設(shè)備的證書信息,模組商可以在相應(yīng)的HAL函數(shù)中填入測試設(shè)備的證書信息即可。

接下來對MQTT連接參數(shù)進(jìn)行指定,客戶可以根據(jù)自己的需要對參數(shù)進(jìn)行修改:

/* Initialize MQTT parameter */
    memset(&mqtt_params, 0x0, sizeof(mqtt_params));
    mqtt_params.port = sign_mqtt.port;
    mqtt_params.host = sign_mqtt.hostname;
    mqtt_params.client_id = sign_mqtt.clientid;
    mqtt_params.username = sign_mqtt.username;
    mqtt_params.password = sign_mqtt.password;
    mqtt_params.request_timeout_ms = 2000;
    mqtt_params.clean_session = 0;
    mqtt_params.keepalive_interval_ms = 60000;
    mqtt_params.read_buf_size = 1024;
    mqtt_params.write_buf_size = 1024;
    mqtt_params.handle_event.h_fp = example_event_handle;
    mqtt_params.handle_event.pcontext = NULL;
    pclient = IOT_MQTT_Construct(&mqtt_params);
            

通過調(diào)用接口 IOT_MQTT_Construct() 觸發(fā)SDK連接云平臺, 若接口返回值非NULL, 則連云成功之后調(diào)用example_subscribe對一個指定的topic進(jìn)行數(shù)據(jù)訂閱:

res = example_subscribe(pclient);
            
example_subscribe的函數(shù)內(nèi)容如下:

注:

  • 設(shè)備商需要根據(jù)自己的產(chǎn)品設(shè)計(jì),訂閱自己希望訂閱的TOPIC,以及注冊相應(yīng)的處理函數(shù)。
  • 上面例子程序中第一個橙色圈選的代碼是指定topic的格式:/$ProductKey/$DeviceName,這個topic是在物聯(lián)網(wǎng)平臺創(chuàng)建一個產(chǎn)品時默認(rèn)生成的。
  • 第二個橙色圈選的代碼是生成topic的內(nèi)容。
  • 上圖的第三個框展示了如何訂閱一個指定的topic以及當(dāng)通過該topic接收到數(shù)據(jù)時的處理函數(shù)。

以下段落演示MQTT的發(fā)布功能,即將業(yè)務(wù)報(bào)文上報(bào)到云平臺:

 while (1) {
        if (0 == loop_cnt % 20) {
            example_publish(pclient);
        }

        IOT_MQTT_Yield(pclient, 200);

        loop_cnt += 1;
    }
            

注:

  • 上面的代碼是周期性的將固定的消息發(fā)送給云端,設(shè)備商需要根據(jù)自己的產(chǎn)品功能,在必要的時候才上傳數(shù)據(jù)給物聯(lián)網(wǎng)平臺。
  • 客戶可以刪除main函數(shù)中example_publish(pclient)語句,避免周期發(fā)送無效數(shù)據(jù)給到云端。
  • IOT_MQTT_Yield是讓SDK去接收來自MQTT Broker的數(shù)據(jù),其中200毫秒是等待時間,如果用戶的消息數(shù)量比較大、或者實(shí)時性要求較高,可以將時間改小。
下面是example_publish函數(shù)體的內(nèi)容:

上面圖中第一個框是發(fā)送的消息的內(nèi)容,第二個框是調(diào)用SDK提給的API將消息發(fā)送給指定的topic。

上傳模組商編碼和模組型號

如果模組商希望將模組送到阿里云IoT進(jìn)行模組認(rèn)證,那么模組商需要將模組商編碼和模組型號進(jìn)行上報(bào),這樣阿里云物聯(lián)網(wǎng)平臺可以統(tǒng)計(jì)通過指定模組商連接到平臺的設(shè)備數(shù)量,也可以統(tǒng)計(jì)通過模組商的某個型號模組連接設(shè)備的數(shù)量。

模組商編碼和模組型號請?jiān)诩蒘DK前聯(lián)系阿里進(jìn)行獲取,請按如下模板發(fā)送消息,聯(lián)系我們

主題:設(shè)備接入Link SDK產(chǎn)品-模組/芯片型號申請

當(dāng)模組與阿里云物聯(lián)網(wǎng)平臺建立連接之后,請復(fù)制并調(diào)用下面的函數(shù)進(jìn)行信息上報(bào),其中參數(shù)pid是模組商編碼、mid是型號編碼:

#define PID_STRING_LEN_MAX          32  /* PID字符串最大長度 */
#define MID_STRING_LEN_MAX          32  /* MID字符串最大長度 */


int example_report_pid_mid(void *pclient, const char *product_key, const char *device_name, const char *pid, const char *mid)
{
    int res = 0;
    iotx_mqtt_topic_info_t topic_msg;

    const char topic_frag1[] = "/sys/";
    const char topic_frag2[] = "/thing/deviceinfo/update";
    char topic[sizeof(topic_frag1) + sizeof(topic_frag2) + IOTX_PRODUCT_KEY_LEN + IOTX_DEVICE_NAME_LEN] = {0};

    const char payload_frag1[] = "{\"id\":\"0\",\"version\":\"1.0\",\"params\":[{\"attrKey\":\"SYS_MODULE_ID\",\"attrValue\":\"";
    const char payload_frag2[] = "\",\"domain\":\"SYSTEM\"},{\"attrKey\":\"SYS_PARTNER_ID\",\"attrValue\":\"";
    const char payload_frag3[] = "\",\"domain\":\"SYSTEM\"}],\"method\": \"thing.deviceinfo.update\"}";
    char payload[sizeof(payload_frag1) + sizeof(payload_frag2) + sizeof(payload_frag3) + PID_STRING_LEN_MAX + MID_STRING_LEN_MAX] = {0};

    if (strlen(pid) > PID_STRING_LEN_MAX || strlen(mid) > MID_STRING_LEN_MAX) {
        return -1;
    }

    /* 組裝MQTT topic字符串 */
    memcpy(topic, topic_frag1, strlen(topic_frag1));
    memcpy(topic + strlen(topic), product_key, strlen(product_key));
    memcpy(topic + strlen(topic), "/", 1);
    memcpy(topic + strlen(topic), device_name, strlen(device_name));
    memcpy(topic + strlen(topic), topic_frag2, strlen(topic_frag2));

    /* 組裝MQTT payload字符串, payload中包含了PID, MID字符串 */
    memcpy(payload, payload_frag1, strlen(payload_frag1));
    memcpy(payload + strlen(payload), mid, strlen(mid));
    memcpy(payload + strlen(payload), payload_frag2, strlen(payload_frag2));
    memcpy(payload + strlen(payload), pid, strlen(pid));
    memcpy(payload + strlen(payload), payload_frag3, strlen(payload_frag3));

    topic_msg.qos = IOTX_MQTT_QOS0;
    topic_msg.retain = 0;
    topic_msg.dup = 0;
    topic_msg.payload = (void *)payload;
    topic_msg.payload_len = strlen(payload);

    /* 使用MQTT publish API發(fā)送PID,MID信息報(bào)文 */
    res = IOT_MQTT_Publish(pclient, topic, &topic_msg);
    if (res < 0) {
        return -1;
    }

    return 0;
}

            

功能調(diào)試

下面的信息截圖以mqtt_example.c為例編寫。

如何判斷設(shè)備已連接到阿里云

注意:/${productKey}/${deviceName}/get這個topic默認(rèn)只有“訂閱”權(quán)限,請?jiān)谖锫?lián)網(wǎng)平臺的控制臺將其修改為“發(fā)布和訂閱”,避免消息發(fā)送到云端后被云端丟棄; 把該topic修改為“發(fā)布和訂閱”,主要是為了讓example程序運(yùn)行不出錯。

下面的打印是HAL_Printf函數(shù)將信息打印到串口后運(yùn)行example的輸出內(nèi)容,其中使用橙色圈選的信息表明設(shè)備已成功連接到阿里云物聯(lián)網(wǎng)平臺:

如何判斷設(shè)備已成功發(fā)送數(shù)據(jù)到云端

登錄阿里網(wǎng)物聯(lián)網(wǎng)平臺的商家后臺,選中指定的設(shè)備,可以查看是否收到來自設(shè)備的消息,如下圖所示:

注:上圖中的內(nèi)容只能看見消息發(fā)送到了哪個topic,消息的內(nèi)容并不會顯示出來。

如何判斷設(shè)備已可成功接收來自云端數(shù)據(jù)

在商家后臺的“下行消息分析”分析中可以看見由物聯(lián)網(wǎng)平臺發(fā)送給設(shè)備的消息:
也可在設(shè)備端查看是否已收到來自云端的數(shù)據(jù),exmaple代碼中收到云端發(fā)送的數(shù)據(jù)的打印信息如下所示:

AT指令實(shí)現(xiàn)

如果模組不提供開發(fā)環(huán)境給用戶進(jìn)行二次開發(fā),而是外接一個MCU并且產(chǎn)品的業(yè)務(wù)邏輯運(yùn)行在MCU上,那么模組商還需要提供AT指令給MCU調(diào)用,由于模組以前只支持TCP,所以需要提供MQTT連接配置、發(fā)起連接、斷開連接、訂閱、Publish等接口。

下面是推薦增加的AT指令(模組商也可以合并這些指令或者拆分指令),格式由模組商自行定義:

指令項(xiàng) 說明
阿里設(shè)備身份信息設(shè)置 設(shè)置設(shè)備的product_key, product_secret, device_name, device_secret
阿里云端region設(shè)置 阿里云IoT提供中國、美國、日本等多個云端站點(diǎn),可以讓MCU指定需要連接的阿里云IoT的站點(diǎn)以及端口信息
建立MQTT連接 建立到阿里云MQTT Broker的連接,該指令中可以指定MQTT clean session、keepalive間隔、Req Timeout時間
斷開MQTT連接 斷開與阿里云IoT的MQTT連接
訂閱某個Topic 對某個Topic進(jìn)行消息訂閱
取消Topic訂閱 取消對某個topic的消息訂閱
向某個topic發(fā)送數(shù)據(jù) 向指定的topic發(fā)送數(shù)據(jù)

注:當(dāng)MCU通過AT指令將設(shè)備身份信息傳遞到模組時,建議將設(shè)備身份信息存儲到全局變量中,并在HAL_GetProductKey、HAL_GetDeviceName、HAL_GetDeviceSecret等幾個HAL函數(shù)中將其進(jìn)行返回,這樣example程序不用進(jìn)行修改。