本文講解模組商在支援MQTT的通訊模組上如何整合阿里雲IoT提供的Link SDK,以及完成相關需要完成的工作。本文檔基於Link SDK 3.0.1進行編寫。
情境說明
Link SDK是阿里雲IoT提供的用於將裝置串連到阿里雲IoT的裝置端SDK,用於完成裝置認證、資料通訊等功能。將Link SDK整合到通訊模組中,可以帶來以下好處:
- 裝置廠商在MCU上無需關心如何串連阿里雲IoT,只是通過調用模組提供的AT指令就可以串連阿里雲IoT,因此對MCU的資源消耗沒有增加
- 阿里將在認證夥伴頁面露出通過認證的模組型號、購買連結、開發指導等文檔,引導裝置商以及服務提供者購買通過認證的通訊模組串連阿里雲IoT。
使用整合了SDK的模組開發裝置的示意圖如下所示:
裝置商開發裝置的流程為:
- 購買整合了阿里雲Link SDK的模組
- 在MCU上通過模組提供的AT指令串連阿里雲,以及從阿里雲IoT收發資料
- 在阿里雲IoT上部署雲端服務,對裝置進行管理
對於模組商而言,在模組上需要完成的工作包括:
- 將Link SDK正確的整合到模組上
- 提供相應的串連阿里雲IoT物聯網的AT指令供MCU調用
文檔目標
本文講解如何將Link SDK整合到已支援MQTT協議的模組上,讓模組商瞭解整合SDK的大概過程。為了簡化難度,本文只講解如何整合Link SDK的裝置簽名模組,從而讓模組可以接入到阿里雲物聯網平台。
建議模組商盡量多的整合Link SDK的功能,比如物模型、OTA、裝置影子等功能,因為模組上整合的Link SDK的功能越多,意味著裝置商串連阿里雲物聯網平台時可以使用的功能越多。
由於模組已支援MQTT協議,SDK將會利用模組上已具備的MQTT功能模組,Link SDK提供API產生串連阿里雲物聯網平台MQTT Broker所需要的ClientID、UserName、Password等資訊,然後模組商使用這些資料與阿里雲物聯網平台的Broker建立串連,並繼而實現對MQTT Topic的訂閱或者向指定Topic發送資料。
在阿里雲物聯網平台建立基礎版產品
模組商在調試的時候,需要建立一個測試產品和一個測試裝置,用於驗證SDK是否已正常工作。請按照下面的過程建立裝置:
- 點擊阿里雲物聯網平台登入控制台,模組商需要註冊一個阿里雲帳號,註冊阿里雲帳號是免費的
- 建立一個測試裝置,在裝置頁面可以擷取到裝置的device_name和device_secret。
下面講解模組商在模組上整合SDK的開發過程:
整合SDK的開發過程
模組商整合SDK時,需要進行下面幾個開發過程:
SDK配置與代碼抽取
SDK包含的功能較多,為了避免SDK消耗過多的RAM和Flash,SDK提供配置和抽取工具讓開發人員只使用需要的組件。
配置SDK
下面講解模組商如何配置需要的軟體功能。
回合組態命令
- Linux系統
進入SDK的根目錄下,運行命令
make menuconfig
- Windows系統
運行SDK根目錄下的config.bat
config.bat
上面的兩種方式都會啟動SDK的組態工具,介面如下所示:
註:
- 功能配置選項前的號表示該功能被使能,沒有號表示該功能未被使能
- 表格中顯示的是可配置的選項,因此即使將所有選項都失效,SDK中不能失效的功能仍然有效
- 按下空格鍵可以選中或者失效某個功能,使用小鍵盤的上下鍵來在不同功能之間切換;
- 如果想知道每個選項的具體含義,先用方向鍵將高亮光條移到那個選項上,再按鍵盤上的“h”按鍵,將出現協助文本,說明選項是什麼含義,開啟了和關閉了意味著什麼。
對於本情境而言:
- 如果開發環境支援stdint.h,那麼使能PLATFORM_HAS_STDINT
- 如果開發環境支援malloc/free,那麼使能PLATFORM_HAS_DYNMEM
- 如果運行環境有OS,那麼使能PLATFORM_HAS_OS
其它選項均不使能。配置完成之後,通過“Exit”按鍵退出,並根據提示儲存配置。
抽取SDK代碼
下面講解如何抽取SDK代碼
運行抽取命令
- Linux系統
進入SDK的根目錄下,運行命令
sh ./extract.sh
- Windows系統
運行SDK根目錄下的config.bat
extract.bat
SDK檔案加入使用者工程
使用者可以將output目錄下的eng檔案夾複製到自己的工程目錄,然後將代碼檔案添加到工程中。
需要添加的檔案位於目錄eng/dev_sign,eng/infra,eng/wrappers中,編譯的時候也需要指定標頭檔尋找路徑包含這幾個目錄
HAL對接
無
將Link SDK與已有MQTT融合
產生MQTT ClientID、UserName、Password
MQTT Client串連MQTT Broker時需要指定ClientID、UserName、Password等資訊,Link SDK提供API IOT_Sign_MQTT()用於產生這些資料:
int32_t IOT_Sign_MQTT(iotx_mqtt_region_types_t region, iotx_dev_meta_info_t *meta, iotx_sign_mqtt_t *signout)
註:使用該函數需要包含標頭檔dev_sign_internal.h:
#include "dev_sign_internal.h"
入參說明:
- region
指定串連的阿里雲IoT的雲端網站,可選值定義在檔案eng/infra/infra_defs.h中:
typedef enum {
IOTX_CLOUD_REGION_SHANGHAI, /* Shanghai */
IOTX_CLOUD_REGION_SINGAPORE, /* Singapore */
IOTX_CLOUD_REGION_JAPAN, /* Japan */
IOTX_CLOUD_REGION_USA_WEST, /* America */
IOTX_CLOUD_REGION_GERMANY, /* Germany */
IOTX_CLOUD_REGION_CUSTOM, /* Custom setting */
IOTX_CLOUD_DOMAIN_MAX /* Maximum number of domain */
} iotx_mqtt_region_types_t;
如果模組在中國售賣,將該參數設定為IOTX_CLOUD_REGION_SHANGHAI
meta
指定裝置的身份資訊,資料結構定義如下:
typedef struct {
char product_key[IOTX_PRODUCT_KEY_LEN + 1];
char product_secret[IOTX_PRODUCT_SECRET_LEN + 1];
char device_name[IOTX_DEVICE_NAME_LEN + 1];
char device_secret[IOTX_DEVICE_SECRET_LEN + 1];
} iotx_dev_meta_info_t;
其中的四個變數是產品在阿里雲物聯網平台定義完畢之後,由裝置商為每個裝置申請的;在實際產品開發時,這幾個參數需要由MCU通過AT指令傳遞給模組
出參說明
- signout
該參數輸出串連MQTT Broker時需要的ClientID、Username、Password等資訊,結構定義如下:
typedef struct {
char hostname[DEV_SIGN_HOSTNAME_MAXLEN];
uint16_t port;
char clientid[DEV_SIGN_CLIENT_ID_MAXLEN];
char username[DEV_SIGN_USERNAME_MAXLEN];
char password[DEV_SIGN_PASSWORD_MAXLEN];
} iotx_sign_mqtt_t;
其中hostname是阿里雲物聯網雲端網站MQTT Broker的網域名稱,port是阿里雲物聯網雲端網站的MQTT Broker的連接埠號碼。
傳回值說明
成功該函數將返回0,錯誤將返回-1。
使用樣本
eng\examples\dev_sign_example.c中示範了如何使用IOT_Sign_MQTT()函數,下面是程式碼片段樣本:
#define EXAMPLE_PRODUCT_KEY "a1X2bEnP82z"
#define EXAMPLE_PRODUCT_SECRET "7jluWm1zql7bt8qK"
#define EXAMPLE_DEVICE_NAME "example1"
#define EXAMPLE_DEVICE_SECRET "ga7XA6KdlEeiPXQPpRbAjOZXwG8ydgSe"
/* Implenment this HAL or using "printf" of your own system if you want to print something in example*/
void HAL_Printf(const char *fmt, ...);
int main(int argc, char *argv[])
{
iotx_mqtt_region_types_t region = IOTX_CLOUD_REGION_SHANGHAI;
iotx_dev_meta_info_t meta;
iotx_sign_mqtt_t sign_mqtt;
memset(&meta,0,sizeof(iotx_dev_meta_info_t));
memcpy(meta.product_key,EXAMPLE_PRODUCT_KEY,strlen(EXAMPLE_PRODUCT_KEY));
memcpy(meta.product_secret,EXAMPLE_PRODUCT_SECRET,strlen(EXAMPLE_PRODUCT_SECRET));
memcpy(meta.device_name,EXAMPLE_DEVICE_NAME,strlen(EXAMPLE_DEVICE_NAME));
memcpy(meta.device_secret,EXAMPLE_DEVICE_SECRET,strlen(EXAMPLE_DEVICE_SECRET));
if (IOT_Sign_MQTT(region,&meta,&sign_mqtt) < 0) {
return -1;
}
...
}
在該樣本中,裝置的product_key、product_secret、device_name、device_secret都使用了固定的數值,在實際產品運行時需要由MCU告知模組。
在模組商調試時,模組商需要自己到阿里雲物聯網平台去建立產品和測試裝置,然後使用平台為裝置產生的product_key、product_secret、device_name、device_secret。
上傳模組商編碼和模組型號
如果模組商希望將模組送到阿里雲IoT進行模組認證,那麼模組商需要將模組商編碼和模組型號進行上報,這樣阿里雲物聯網平台可以統計通過指定模組商串連到平台的裝置數量,也可以統計通過模組商的某個型號模組串連裝置的數量。
模組商編碼和模組型號請在整合SDK前聯絡阿里進行擷取,請將郵件發送到郵箱 linkcertification@list.alibaba-inc.com,並在主題中標註“模組/晶片型號申請”。註:非模組商無需申請編__碼。
當模組與阿里雲物聯網平台建立串連之後,請複製並調用下面的函數進行資訊上報,其中參數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資訊報文, 由於使用模組上內建的MQTT功能,
模組商整合時需要將下面的publish函數換為實際的publish函數*/
res = IOT_MQTT_Publish(pclient, topic, &topic_msg);
if (res < 0) {
return -1;
}
return 0;
}
調試
將模組串連到阿里雲物聯網平台
模組商需要編寫函數用於將模組串連到阿里雲物聯網平台,模組上的MQTT應該會提供一個串連MQTT Broker的函數,在其中需要輸入通過調用Link SDK提供的API IOT_Sign_MQTT()擷取到的網域名稱、連接埠、ClientID、usename、password等資訊。
下面是將SDK與一個mosquitto整合(一個開源的MQTT庫)的樣本虛擬碼:
//呼叫簽章函數擷取MQTT username、password、clientID等資訊
IOT_Sign_MQTT(region,&meta,&sign_mqtt);
mosquitto_lib_init();
//下面的代碼設定了mqtt clientID和cleansession參數
mosq = mosquitto_new(sign_mqtt.clientid,0/*dont clean session*/,NULL);
if(mosq==NULL){
printf("Error:Failed creating mosquitto client\n\r");
return(-1);
}
//下面的代碼設定了MQTT串連時使用的username和password
if(0 != mosquitto_username_pw_set(mosq, sign_mqtt.username, sign_mqtt.password)){
printf("Error:Failed setting username or password\n\r");
return(-1);
}
...
//下面的代碼用於建立到MQTT Broker的串連,其中使用到了網域名稱、連接埠
if(mosquitto_connect(mosq, sign_mqtt.hostname, sign_mqtt.port, kaInterval)){
printf("Error: Failed connecting cloud.\n\r");
sleep(1);
return -1;
}
註:MQTT建立串連時還需要指定keepalive間隔,該值的推薦值為60秒,模組商也可以將該參數的設定提供AT指令給MCU進行動態修改,阿里雲物聯網平台接受的keepalive間隔範圍為30~1200秒。
模組正常串連到物聯網平台之後,如果模組沒有斷開MQTT串連,模組將處於線上狀態。可以在阿里雲物聯網平台的控制台找到測試裝置,查看其狀態(下圖展示了一個線上裝置的狀態):
如何判斷程式可正常發送資料到物聯網平台
當模組與阿里雲物聯網平台已建立串連之後,可以向topic /${productKey}/${deviceName}/get發送一個訊息,來檢查資料是否已可以正常發送到物聯網平台。
注意:/${productKey}/${deviceName}/get這個topic預設只有“訂閱”許可權,請在物聯網平台的控制台將其修改為“發布和訂閱”,避免訊息發送到雲端後被雲端丟棄; 把該topic修改為“發布和訂閱”,主要是為了讓example程式運行不出錯。
下面是一個參考實現樣本:
int res = 0;
iotx_mqtt_topic_info_t topic_msg;
const char *fmt = "/%s/%s/get";
char *topic = NULL;
int topic_len = 0;
char *payload = "hello,world";
topic_len = strlen(fmt) + strlen(product_key) + strlen(device_name) + 1;
topic = HAL_Malloc(topic_len);
if (topic == NULL) {
HAL_Printf("memory not enough\n");
return -1;
}
memset(topic, 0, topic_len);
//此處產生topic
HAL_Snprintf(topic, topic_len, fmt, product_key, device_name);
//下面的代碼產生一個訊息
memset(&topic_msg, 0x0, sizeof(iotx_mqtt_topic_info_t));
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);
//下面的代碼向指定的topic發送訊息,請使用模組上的MQTT Publish函數替換下面的發送函數
res = IOT_MQTT_Publish(handle, topic, &topic_msg);
在物聯網平台的控制台上,在具體的裝置的Log Service中,可以查看物聯網平台是否已接收到該資料:
註:
- 日誌會顯示在什麼時間收到了來自裝置的訊息,也會顯示收到訊息的topic,並不會顯示訊息的內容
- 阿里雲物聯網平台目前不支援QoS2
如何判斷程式已正確訂閱TOPIC
模組商可以向topic /{productKey}/${deviceName}/get訂閱資料,這樣當裝置上報一個資料到物聯網平台後,平台又會將資料發送回裝置,從而讓模組商得知訂閱是否工作正常。下面是樣本虛擬碼:
int res = 0;
const char *fmt = "/%s/%s/get";
char *topic = NULL;
int topic_len = 0;
topic_len = strlen(fmt) + strlen(product_key) + strlen(device_name) + 1;
topic = HAL_Malloc(topic_len);
if (topic == NULL) {
HAL_Printf("memory not enough\n");
return -1;
}
//下面的代碼用於組裝topic
memset(topic, 0, topic_len);
snprintf(topic, topic_len, fmt, product_key, device_name);
/*下面的代碼用於訂閱topic,以及指定訊息處理函數。
請使用模組上實際的MQTT subscribe函數替換下面的代碼*/
res = IOT_MQTT_Subscribe(handle, topic, IOTX_MQTT_QOS0, example_message_arrive, NULL);
在物聯網平台的控制台,模組商可以查看平台是否將資料發送給了裝置:
註:
- 日誌會顯示在什麼時間發送了訊息到裝置端,不會顯示訊息內容
- 模組商需要在模組上查看收到的資料是否是自己上傳的資料,來確保接收資料工作正常
AT指令實現
模組商還需要提供AT指令給MCU調用,由於模組以前已支援MQTT,所以應該已經提供MQTT串連配置、發起串連、中斷連線、訂閱、Publish等介面。整合了Link SDK之後,模組商可以新增針對阿里雲的AT指令,也可以修改目前已有的AT指令。
下面是推薦增加的AT指令:
指令項 | 說明 |
阿里裝置身份資訊設定 | 設定裝置的product_key, product_secret, device_name, device_secret |
阿里雲端region設定 | 阿里雲IoT提供中國、美國、日本等多個雲端網站,可以讓MCU指定需要串連的阿里雲IoT的網站以及連接埠資訊 |
其餘的MQTT指令可以繼續使用模組商已有的指令