本文描述在支援TCP的廣域網路模組上整合SDK的方法。
Link SDK是阿里雲IoT提供的用於將裝置串連到阿里雲IoT的裝置端SDK,用於完成裝置認證、資料通訊等功能。將Link SDK整合到通訊模組中,可以帶來以下好處:
- 裝置廠商在MCU上無需關心如何串連阿里雲IoT,只是通過調用模組提供的AT指令就可以串連阿里雲IoT,因此對MCU的資源消耗沒有增加。
- 阿里將在認證夥伴頁面露出通過認證的模組型號、購買連結、開發指導等文檔,引導裝置商以及服務提供者購買通過認證的通訊模組串連阿里雲IoT。
裝置商開發裝置的流程為:
- 購買整合了阿里雲Link SDK的模組。
- 在MCU上通過模組提供的AT指令串連阿里雲,以及從阿里雲IoT收發資料。
- 在阿里雲IoT上部署雲端服務,對裝置進行管理。
對於模組商而言,在模組上需要完成的工作包括:
- 將Link SDK正確的整合到模組上。
- 提供相應的串連阿里雲IoT物聯網的AT指令供MCU調用。
下面的文檔只講解如何將SDK的MQTT功能整合到模組上。對於模組商來說,整合SDK的功能越多,MCU側裝置廠商的開發功能越少,因此建議模組商儘可能多的整合SDK的功能,比如OTA、物模型等。
在阿里雲物聯網平台上的操作
為了驗證模組上整合的SDK是否運行正確,需要將一個測試裝置串連到阿里雲物聯網平台。使用者需要在阿里雲物聯網平台上建立一個產品,並建立一個該產品的裝置執行個體以擷取到裝置的身份資訊。
整合SDK的開發過程
模組商在模組上整合SDK時,需要進行下面幾個開發過程。
SDK配置與代碼抽取
配置SDK
SDK包含的功能較多,下面講解如何配置本情境中需要的功能。
回合組態命令
- Linux系統
進入SDK的根目錄下,運行命令
make menuconfig
- Windows系統
運行SDK根目錄下的config.bat
config.bat
上面的兩種方式都會啟動SDK的組態工具,介面如下所示:
使能需要的SDK功能
在功能配置介面,按下空格鍵可以選中或者失效某個功能,使用小鍵盤的上下鍵來在不同功能之間切換;如果想知道每個選項的具體含義,先用方向鍵將高亮光條移到那個選項上,再按鍵盤上的“h”按鍵,將出現協助文本,說明選項是什麼含義,以及開啟了和關閉了意味著什麼。
- 如果編譯環境有內建標準標頭檔<stdint.h>,請使能選項:
PLATFORM_HAS_STDINT
- 如果目標系統上運行有嵌入式作業系統,請使能選項:
PLATFORM_HAS_OS
- 由於模組支援TCP但是不支援MQTT,因此必須使能下面三項配置:
- FEATURE_MQTT_COMM_ENABLED,使用阿里SDK提供的MQTT API與雲端通訊。
- FEATURE_MQTT_DEFAULT_IMPL,使用阿里SDK中內建的MQTT Client實現,使用者需要實現相關的TCP串連的建立、串連、資料收發過程。
- FEATURE_MQTT_DIRECT,裝置端指定阿里雲物聯網雲端網站。
建議使能FEATURE_SUPPORT_TLS,讓資料與物聯網平台之間的資料通訊是加密的。本文檔中為了降低適配工作量,未使能該選項。
其它功能均無需使能。
抽取選中功能的原始碼
將SDK代碼檔案加入客戶編譯環境
客戶將上一個步驟中得到的Link SDK的代碼檔案從output目錄複寫到自己的工程目錄中,並修改自己的編譯環境或者開發工具將這些代碼檔案整合到編譯環境。
實現HAL對接函數
Link SDK被設計為可以在不同的作業系統上運行,或者甚至在不支援作業系統的MCU上運行,因此與系統相關的操作被定義成一些HAL函數,需要客戶進行實現;另外,由於不同的通訊模組上的OS不同,所以與通訊模組上TCP相關的操作也被定義成HAL函數需要客戶進行實現。
所有HAL函數位於檔案為output/eng/wrappers/wrapper.c中。
系統相關HAL
必須實現函數:
** ** | 函數名 | 說明 |
1 | HAL_Malloc | 對應標準C庫中的malloc(), 按入參長度開闢一片可用記憶體, 並返回首地址 |
2 | HAL_Free | 對應標準C庫中的free(), 將入參指標所指向的記憶體空間釋放 |
3 | HAL_Printf | 對應標準C庫中的printf(), 根據入參格式字串將字元文本顯示到終端。如果使用者的調試環境有更好的調試手段,該函數無需實現 |
4 | HAL_Snprintf | 類似printf, 但輸出的結果不再是顯示到終端, 而是存入指定的緩衝區記憶體 |
5 | HAL_UptimeMs | 返回一個uint64_t類型的數值, 表達裝置啟動後到目前時間點過去的毫秒數 |
6 | HAL_SleepMs | 按照指定入參的數值, 睡眠相應的毫秒, 比如參數是10, 那麼就會睡眠10毫秒 |
另外,在SDK加壓後的目錄(非代碼抽取目錄)wrappers/os下有HAL的參考實現,使用者可以查看是否有自己需要的OS參考實現,若未提供則使用者需要自己進行實現。
可選實現函數
如果模組沒有運行OS,或者SDK的MQTT API並沒有在多個線程中被調用,以下函數可以不用修改wrapper.c中相關的函數實現;在有OS情境下並且MQTT API被APP在多個線程中調用,則需要使用者對接以下函數:
** ** | 函數名 | 說明 |
1 | HAL_MutexCreate | 建立一個互斥鎖, 傳回值可以傳遞給HAL_MutexLock/Unlock |
2 | HAL_MutexDestroy | 銷毀一個互斥鎖, 這個鎖由入參標識 |
3 | HAL_MutexLock | 申請互斥鎖, 如果當前該鎖由其它線程持有, 則當前線程睡眠, 否則繼續 |
4 | HAL_MutexUnlock | 釋放互斥鎖, 此後當前在該鎖上睡眠的其它線程將取得鎖並往下執行 |
5 | HAL_SemaphoreCreate | 建立一個訊號量, 傳回值可以傳遞給HAL_SemaphorePost/Wait |
6 | HAL_SemaphoreDestroy | 銷毀一個訊號量, 這個訊號量由入參標識 |
7 | HAL_SemaphorePost | 在指定的計數訊號量上做自增操作, 解除其它線程的等待 |
8 | HAL_SemaphoreWait | 在指定的計數訊號量上等待並做自減操作 |
9 | HAL_ThreadCreate | 根據配置參數建立thread |
TCP相關HAL
MQTT基於TCP進行通訊,模組商需要實現下面四個TCP HAL函數。
序號 | 函數名 | 說明 |
1 | HAL_TCP_Establish | 建立一個TCP串連。注意:* 入參host是一個網域名稱,需要轉換為IP地址* 傳回值是tcp的socket號 |
2 | HAL_TCP_Destroy | 關閉tcp串連,入參是HAL_TCP_Establish的傳回值,傳回值0表示成功 |
3 | HAL_TCP_Write | 通過TCP串連發送資料。注意:* 該函數傳入了一個逾時時間,如果逾時仍未將資料發送結束那麼函數也需要返回;* 如果TCP串連已斷開,需要返回一個小於0的負數 |
4 | HAL_TCP_Read | 在指定的時間內讀取資料並返回,該函數的入參中指定了可接收的資料的最大長度,如果從TCP中讀取到該最大長度的資料,那麼可以立即返回 |
產品相關HAL
下面的HAL用於擷取產品的身份認證資訊,裝置廠商需要設計如何在裝置上燒寫裝置身份資訊,並通過下面的HAL函數將其讀出後提供給SDK:
序號 | 函數名 | 說明 |
1 | HAL_GetProductKey | 擷取裝置的ProductKey , 用於標識裝置的產品型號 |
2 | HAL_GetDeviceName | 擷取裝置的DeviceName , 用於唯一標識單個裝置 |
3 | HAL_GetDeviceSecret | 擷取裝置的DeviceSecret , 用於標識單個裝置的密鑰 |
註:這幾個參數在實際產品開發時應該由裝置廠商通過AT指令告知模組,模組商調試時可以將自己建立的測試裝置的ProductKey、DeviceName、DeviceSecret直接通過上面這幾個函數返回。
參照example實現產品功能
模組商可參考output檔案夾中的 eng/examples/mqtt_example.c進行功能調試, 裝置廠商可以將該檔案複製到產品工程中,對其進行修改後使用。
該example將串連裝置到阿里雲,訂閱一個指定的topic並發送資料給該topic,即裝置上報的訊息會被物聯網平台發送給裝置,下面是example的大概過程說明:
注意:需要在雲端將該topic從預設的許可權從”訂閱”修改為”發布和訂閱”,如下圖所示:
從程式入口的 main() 函數看起, 首先是調用模組提供的HAL函數擷取產品的身份資訊:
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函數是擷取裝置的認證資訊,模組商可以在相應的HAL函數中填入測試裝置的認證資訊即可。
接下來對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);
註:
- 裝置商需要根據自己的產品設計,訂閱自己希望訂閱的TOPIC,以及註冊相應的處理函數。
- 上面例子程式中第一個橙色圈選的代碼是指定topic的格式:/$ProductKey/$DeviceName,這個topic是在物聯網平台建立一個產品時預設產生的。
- 第二個橙色圈選的代碼是產生topic的內容。
- 上圖的第三個框展示了如何訂閱一個指定的topic以及當通過該topic接收到資料時的處理函數。
以下段落示範MQTT的發布功能,即將業務報文上報到雲平台:
while (1) {
if (0 == loop_cnt % 20) {
example_publish(pclient);
}
IOT_MQTT_Yield(pclient, 200);
loop_cnt += 1;
}
註:
- 上面的代碼是周期性的將固定的訊息發送給雲端,裝置商需要根據自己的產品功能,在必要的時候才上傳資料給物聯網平台。
- 客戶可以刪除main函數中example_publish(pclient)語句,避免周期發送無效資料給到雲端。
- IOT_MQTT_Yield是讓SDK去接收來自MQTT Broker的資料,其中200毫秒是等待時間,如果使用者的訊息數量比較大、或者即時性要求較高,可以將時間改小。
上面圖中第一個框是發送的訊息的內容,第二個框是調用SDK提給的API將訊息發送給指定的topic。
上傳模組商編碼和模組型號
如果模組商希望將模組送到阿里雲IoT進行模組認證,那麼模組商需要將模組商編碼和模組型號進行上報,這樣阿里雲物聯網平台可以統計通過指定模組商串連到平台的裝置數量,也可以統計通過模組商的某個型號模組串連裝置的數量。
模組商編碼和模組型號請在整合SDK前聯絡阿里進行擷取,請按如下模板發送訊息,聯絡我們。
主題:裝置接入Link SDK產品-模組/晶片型號申請
當模組與阿里雲物聯網平台建立串連之後,請複製並調用下面的函數進行資訊上報,其中參數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發送PID,MID資訊報文 */
res = IOT_MQTT_Publish(pclient, topic, &topic_msg);
if (res < 0) {
return -1;
}
return 0;
}
功能調試
下面的資訊截圖以mqtt_example.c為例編寫。
如何判斷裝置已串連到阿里雲
注意:/${productKey}/${deviceName}/get這個topic預設只有“訂閱”許可權,請在物聯網平台的控制台將其修改為“發布和訂閱”,避免訊息發送到雲端後被雲端丟棄; 把該topic修改為“發布和訂閱”,主要是為了讓example程式運行不出錯。
如何判斷裝置已成功發送資料到雲端
註:上圖中的內容只能看見訊息發送到了哪個topic,訊息的內容並不會顯示出來。
如何判斷裝置已可成功接收來自雲端資料
AT指令實現
如果模組不提供開發環境給使用者進行二次開發,而是外接一個MCU並且產品的商務邏輯運行在MCU上,那麼模組商還需要提供AT指令給MCU調用,由於模組以前只支援TCP,所以需要提供MQTT串連配置、發起串連、中斷連線、訂閱、Publish等介面。
下面是推薦增加的AT指令(模組商也可以合并這些指令或者拆分指令),格式由模組商自行定義:
指令項 | 說明 |
阿里裝置身份資訊設定 | 設定裝置的product_key, product_secret, device_name, device_secret |
阿里雲端region設定 | 阿里雲IoT提供中國、美國、日本等多個雲端網站,可以讓MCU指定需要串連的阿里雲IoT的網站以及連接埠資訊 |
建立MQTT串連 | 建立到阿里雲MQTT Broker的串連,該指令中可以指定MQTT clean session、keepalive間隔、Req Timeout時間 |
斷開MQTT串連 | 斷開與阿里雲IoT的MQTT串連 |
訂閱某個Topic | 對某個Topic進行訊息訂閱 |
取消Topic訂閱 | 取消對某個topic的訊息訂閱 |
向某個topic發送資料 | 向指定的topic發送資料 |
註:當MCU通過AT指令將裝置身份資訊傳遞到模組時,建議將裝置身份資訊儲存到全域變數中,並在HAL_GetProductKey、HAL_GetDeviceName、HAL_GetDeviceSecret等幾個HAL函數中將其進行返回,這樣example程式不用進行修改。