應用場景:設備的硬件由一個MCU加上一個通信模組構成,設備的應用邏輯運行在MCU上,通信模組支持MQTT功能并提供AT指令給MCU使用, MCU控制模組連接云端服務以及收發數據。

本示例中:示例app + SDK + 模組對接代碼一起的RAM消耗為6KB。

對于這樣的場景,設備廠商需要將Link SDK集成并運行在MCU上, 讓Link SDK通過通信模組連接到阿里云物聯網平臺。

文檔目標

下面的文檔關注于講解用戶如何把SDK移植到MCU, 并與通信模組協作來與阿里云物聯網平臺通信。為了簡化移植過程, 下面的文檔在MCU上以開發一個基礎版產品作為案例進行講解,如果用戶需要在MCU上使用SDK的其它功能,可以在MCU上將基礎版的example正確運行之后,再重新配置SDK,選中其它功能再進行產品功能開發。

設備端開發過程

設備端的開發過程如下所示。

SDK配置與代碼抽取

SDK中有各種功能模塊,用戶需要決定。

  • SDK獲取
  • 需要使用哪些功能(SDK配置)

    SDK提供了配置工具用于配置需要使能哪些功能,每個功能的配置選項名稱類似FEATURE_MQTT_XXX,下面的章節中會講解具體有哪些功能可供配置。

  • SDK如何與外部模組進行數據交互

    上圖中的三根紅色虛線代表SDK可以與MQTT模組進行數據交互的三種方式。

  • MQTT Wrapper

    MQTT Wrapper提供了接口函數定義用于與MQTT Client交互,當MCU外接MQTT模組時可以通過實現相關接口函數來驅動MQTT模組中的MQTT Client與阿里云物聯網平臺上的MQTT Broker/Server建連/收發MQTT消息。開發者可以實現相關的wrapper函數來代碼來驅動MQTT模組進行MQTT的連接,無需使能ATM/AT MQTT/AT Parser等功能。

  • AT MQTT

    當MQTT模組發送MQTT消息給MCU時,如果模組發送給MCU的數據的速度超過了MCU上處理MQTT消息的速度,可能導致丟包,因此SDK中實現了一個AT MQTT模塊用于對收到的MQTT消息進行緩存。開發者如果使能本模塊,本模塊將提供MQTT Wrapper函數的實現,開發者需要實現的函數將是AT MQTT HAL中定義的函數,在這些函數中驅動MQTT模組。

  • AT Parser

    MCU與模組之間通常使用UART進行連接,因此開發者需要開發代碼對UART進行初始化,通過UART接收來自模組的數據。由于UART是一個字符一個字符的接收數據,因此開發者還需要對收到的數據組裝并判斷AT指令是否承載MQTT數據,如果是才能將MQTT數據發送給AT MQTT模塊。SDK中提供了AT Parser模塊用于完成這些功能,如果開發者尚未實現在UART上的數據收發/解析等功能, 可以使能AT Parser功能來減少開發工作量。

    當開發者使能AT Parser后,AT Parser將會提供AT MQTT HAL的實現,因此開發者需要實現的函數是AT Parser HAL中定義的函數。

配置SDK

SDK包含的功能較多,為了節約對MCU RAM/Flash資源的消耗,用戶需要根據自己的產品功能定義需要SDK中的哪些功能。

運行配置命令

  • Linux系統

進入SDK的根目錄下,運行命令。

make menuconfig            
  • Windows系統

運行SDK根目錄下的config.bat。

config.bat          

使能需要的SDK功能

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

如果編譯環境有自帶標準頭文件<stdint.h>,請使能選項。

  • PLATFORM_HAS_STDINT

如果目標系統上運行有嵌入式操作系統,請使能選項。

  • PLATFORM_HAS_OS

請務必使能:

  • FEATURE_MQTT_COMM_ENABLED,用于讓SDK提供MQTT API供應用程序調用,并關閉。
  • FEATURE_MQTT_DEFAULT_IMPL,該選項用于包含阿里提供的MQTT Client實現,因為模組支持MQTT Client,所以關閉該選項。

SDK連接MQTT模組有幾種不同的對接方法,為了簡化對接,本文檔中使能。

  • FEATURE_ATM_ENABLED

該選項使能之后具有下面的子選項可供選擇,需要使能。

  • FEATURE_AT_MQTT_ENABLED

如果用戶沒有用于AT命令收發/解析的框架,可以選擇(非必須)使用at_parser框架。

  • FEATURE_AT_PARSER_ENABLED

SDK基于at_parser提供了已對接示例, 如果模組是支持MQTT的sim800 2G模組或者支持ICA MQTT的WiFi模組,可以進行進一步選擇相應選項, 這樣開發的工作量將進一步減少。 如果不需要對接示例, 請忽略該步驟。

完整的配置開關說明表格如下, 但最終解釋應以上面提到的h按鍵觸發文本為準。

配置開關說明
PLATFORM_HAS_STDINT告訴SDK當前要移植的嵌入式平臺是否有自帶標準頭文件<stdint.h>
PLATFORM_HAS_OS目標系統是否運行某個操作系統FEATURE_MQTT_COMM_ENABLEDMQTT長連接功能, 打開后將使SDK提供MQTT網絡收發的能力和接口
FEATURE_MQTT_DEFAULT_IMPLSDK內包含的MQTT Client實現, 打開則表示使用SDK內置的MQTT客戶端實現
FEATURE_ASYNC_PROTOCOL_STACK對于使用SDK內置的MQTT客戶端實現的時候,需要用戶實現TCP相關的HAL,這些HAL的TCP發送數據/接收數據的定義是同步機制的,如果目標系統的TCP基于異步機制,可以使能該開關實現SDK從同步到異步機制的轉換
FEATURE_DYNAMIC_REGISTER動態注冊能力,即設備端只保存了設備的ProductKey和ProductSecret和設備的唯一標識,通過該功能從物聯網平臺換取DeviceSecret
FEATURE_DEVICE_MODEL_ENABLE使能設備物模型編程相關的接口以及實現FEATURE_DEVICE_MODEL_GATEWAY網關的功能以及相應接口
FEATURE_THREAD_COST_INTERNAL為收包啟動一個獨立線程FEATURE_SUPPORT_TLS標準TLS連接, 打開后SDK將使用標準的TLS1.2安全協議連接服務器
FEATURE_SUPPORT_ITLS阿里iTLS連接, 打開后SDK將使用阿里自研的iTLS代替TLS建立安全連接
FEATURE_ATM_ENABLED如果系統是使用MCU+外接模組的架構, 并且SDK運行在MCU上, 必須打開該選項, 然后進行配置
FEATURE_AT_MQTT_ENABLED如果MCU連接的通信模組支持MQTT AT, 則使用該選項
FEATURE_AT_PARSER_ENABLED如果用戶需要使用SDK提供的AT收發/解析的框架, 則可以使用該選項
FEATURE_AT_MQTT_HAL_ICA基于at_parser的ICA MQTT AT對接示例
FEATURE_AT_MQTT_HAL_SIM800基于at_parser的SIM800 MQTT對接示例

使能需要的SDK配置后,保持配置并退出SDK配置工具。

抽取選中功能的源代碼

運行SDK根目錄下的extract.bat,客戶選中的功能所對應的代碼將會被放置到文件夾output。

實現HAL對接函數

Link SDK被設計為可以在不同的操作系統上運行,或者甚至在不支持操作系統的MCU上運行,因此與系統相關的操作被定義成一些HAL函數,需要客戶進行實現。另外, 由于不同的通信模組支持的AT指令集不一樣,所以與通信模組上TCP相關的操作也被定義成HAL函數需要設備開發者進行實現。

由于不同的用戶使能的SDK的功能可能不一樣,因此需要對接的HAL函數會不一樣,設備開發者只需要實現位于文件output/eng/wrappers/wrapper.c中的HAL函數。下面對可能出現在文件wrapper.c的HAL函數進行講解。

MCU系統相關HAL

必須實現函數:

序號函數名說明
1HAL_Malloc對應標準C庫中的malloc(), 按入參長度開辟一片可用內存, 并返回首地址。
2HAL_Free對應標準C庫中的free(),將入參指針所指向的內存空間釋放。
3HAL_Printf對應標準C庫中的printf(),根據入參格式字符串將字符文本顯示到終端,如果用戶無需在串口上進行調試,該函數可以為空。
4HAL_Snprintf類似printf,但輸出的結果不再是顯示到終端,而是存入指定的緩沖區內存。
5HAL_UptimeMs返回一個uint64_t類型的數值,表達設備啟動后到當前時間點過去的毫秒數。
6HAL_SleepMs按照指定入參的數值,睡眠相應的毫秒,比如參數是10,那么就會睡眠10毫秒。

對以上函數若需了解更多細節, 可訪問SDK官方文檔頁面

OS相關可選函數

如果MCU沒有運行OS,或者SDK的MQTT API并沒有在多個線程中被調用,以下函數可以不用修改wrapper.c中相關的函數實現。在有OS場景下并且MQTT API被APP在多個線程中調用,則需要用戶對接以下函數。

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

AT MQTT相關HAL

AT MQTT相關HAL函數位于抽取出來的文件wrapper.c中, 客戶需要在這些函數中調用模組提供的AT指令和模組進行數據交互. 函數說明如下。

序號函數名說明
1HAL_AT_MQTT_Init初始化MQTT參數配置。比如初始化MCU與通信模組之間的UART串口設置,初始化MQTT配置參數:clientID/clean session/user name/password/timeout/MQTT Broker的地址和端口等數值。返回值類型為iotx_err_t,其定義位于文件infra_defs.h。
2HAL_AT_MQTT_Deinit如果在HAL_AT_MQTT_Init創建了一些資源,可以在本函數中相關資源釋放掉。
3HAL_AT_MQTT_Connect連接MQTT服務器。入參:
proKey:產品密碼
devName:設備名
devSecret:設備密碼
注:只有通信模組集成了阿里的SDK的時候會使用到該函數的這幾個入參,如果模組上并沒有集成阿里的SDK,那么略過這幾個參數。該函數的入參并沒有指定服務器的地址/端口,這兩個參數需要在HAL_AT_MQTT_Init()中記錄下來。
4HAL_AT_MQTT_Disconnect斷開MQTT服務器。
5HAL_AT_MQTT_Subscribe向服務器訂閱指定的TOPIC。入參:
topic:主題
qos:服務器質量
mqtt_packet_id: 數據包的ID
mqtt_status: mqtt狀態
timeout_ms:超時時間
6HAL_AT_MQTT_Unsubscribe向服務器取消對指定topoic的訂閱。入參:
topic:主題
mqtt_packet_id:數據包的ID
mqtt_status:mqtt狀態
7HAL_AT_MQTT_Publish向服務器指定的Topic發送消息。
8HAL_AT_MQTT_State返回MQTT的狀態,狀態值定義在文件mal.h的數據結構iotx_mc_state_t中。

調用接收函數

MCU從模組收到MQTT消息之后,需要調用SDK提供的函數IOT_ATM_Input()(見atm/at_api.h)將MQTT 消息交付給SDK。下面的示例代碼演示當MCU從模組收到MQTT消息后,如何調用IOT_ATM_Input函數。

void handle_recv_data()
{
    struct at_mqtt_input param;
    ...

    param.topic = topic_ptr;
    param.topic_len = strlen(topic_ptr);
    param.message = msg_ptr;
    param.msg_len = strlen(msg_ptr);

    if (IOT_ATM_Input(&param) != 0) {
        mal_err("hand data to uplayer fail!\n");
    }
}
            

AT Parser相關HAL

如果選擇了at_parser框架, 則需要對接以下四個UART HAL函數, 函數聲明見at_wrapper.h. 如果用戶不使用at_parser框架請忽略該步。

序號函數名說明
1HAL_AT_Uart_Init該接口對UART進行配置(波特率/停止位等)并初始化。
2HAL_AT_Uart_Deinit該接口對UART去初始化。
3HAL_AT_Uart_Send該接口用于向指定的UART口發送數據。
4HAL_AT_Uart_Recv該接口用于從底層UART buffer接收數據。

產品相關HAL

下面的HAL用于獲取產品的身份認證信息, 設備廠商需要設計如何在設備上燒寫設備身份信息, 并通過下面的HAL函數將其讀出后提供給SDK。

序號函數名說明
1HAL_GetProductKey獲取設備的ProductKey, 用于標識設備的產品型號。
2HAL_GetDeviceName獲取設備的DeviceName,用于唯一標識單個設備。
3HAL_GetDeviceSecret獲取設備的DeviceSecret,用于標識單個設備的密鑰。

代碼集成

如果設備商的開發環境使用makefile編譯代碼,可以將SDK抽取出來的代碼加入其編譯環境進行編譯。如果設備商使用KEIL/IAR這樣的開發工具, 可以將SDK抽取出來的代碼文件加入到IDE的工程中進行編譯。

參照example實現產品功能

如果要使用MQTT連云,可參考抽取文件夾中的 eng/examples/mqtt_example_at.c。設備廠商可以將該文件復制到產品工程中,對其進行修改后使用。

該example將連接設備到阿里云,訂閱一個指定的topic并發送數據給該topic,即設備上報的消息會被物聯網平臺發送給設備,下面是example的大概過程說明。

注意:需要在云端將該topic從默認的權限從"訂閱"修改為"發布和訂閱",如下圖所示。

f

從程序入口的main()函數看起,第一步是調用AT模塊初始化函數IoT_ATM_Init(),使模組處于ready狀態,第二步是調用用戶提供的HAL函數獲取產品信息。

int main(int argc, char *argv[])
{
    void *      pclient = NULL;
    int         res = 0;
    int         loop_cnt = 0;
    iotx_mqtt_region_types_t    region = IOTX_CLOUD_REGION_SHANGHAI;
    iotx_sign_mqtt_t            sign_mqtt;
    iotx_dev_meta_info_t        meta;
    iotx_mqtt_param_t           mqtt_params;
#ifdef ATM_ENABLED
    if (IOT_ATM_Init() < 0) {
        HAL_Printf("IOT ATM init failed!\n");
        return -1;
    }
#endif
    HAL_Printf("mqtt example\n");
    memset(&meta, 0, sizeof(iotx_dev_meta_info_t));
    HAL_GetProductKey(meta.product_key);
    HAL_GetDeviceName(meta.device_name);
    HAL_GetDeviceSecret(meta.device_secret);
            

注:

  • 上面的三個HAL_GetXXX函數是獲取設備的設備證書(ProductKey、DeviceName、DeviceSecret)信息,設備廠商需要自己設計設備的設備證書(ProductKey、DeviceName、DeviceSecret)存放的位置/并將其從指定位置讀取出來。
  • 由于設備的唯一標識DeviceName/設備密鑰DeviceSecret都是機密信息,設備廠商在設計時可以把相關信息加密后存放到Flash上,在HAL函數里面將其解密后提供給SDK,以避免黑客直接從Flash里面讀取設備的身份信息。

接下來對MQTT連接參數進行指定,客戶可以根據自己的需要對參數進行修改。

/* 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);
            

通過調用接口 IOT_MQTT_Construct() 觸發SDK連接云平臺,若接口返回值非NULL,則連云成功之后調用example_subscribe對一個指定的topic進行數據訂閱。

res = example_subscribe(pclient);
            

example_subscribe的函數內容如下。

注:

  • 設備商需要根據自己的產品設計,訂閱自己希望訂閱的TOPIC,以及注冊相應的處理函數。
  • 訂閱的topic的格式需要指定產品型號(product_key)以及設備標識(device_name),如上圖中第一個橙色框中的格式。
  • 上圖的第二個框展示了如何訂閱一個指定的topic以及其處理函數。

以下段落演示MQTT的發布功能,即將業務報文上報到云平臺。

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

        IOT_MQTT_Yield(pclient, 200);

        loop_cnt += 1;
    }
            

下面是example_publish函數體的部分內容。

注:

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

功能調試

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

如何判斷設備已連接到阿里云

下面的打印是HAL_Printf函數將信息打印到串口后運行example的輸出內容,其中使用橙色圈選的信息表明設備已成功連接到阿里云物聯網平臺。

d

如何判斷設備已成功發送數據到云端

登錄阿里網物聯網平臺的商家后臺,選中指定的設備,可以查看是否收到來自設備的消息,如下圖所示。

gg

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

如何判斷設備已可成功接收來自云端數據

在商家后臺的"下行消息分析"分析中可以看見由物聯網平臺發送給設備的消息。

也可在設備端查看是否已收到來自云端的數據,exmaple代碼中收到云端發送的數據的打印信息如下所示。

至此,SDK在MCU與模組之間的適配開發已結束,用戶可以進行產品業務功能的實現。