本文描述在支持TCP的廣域網(wǎng)模組上集成SDK的方法。
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。
設(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è)備的身份信息。
- 點(diǎn)擊阿里云物聯(lián)網(wǎng)平臺登錄控制臺,模組商需要注冊一個阿里云賬號,注冊阿里云賬號是免費(fèi)的。
- 登錄阿里云物聯(lián)網(wǎng)平臺的控制臺之后,按照創(chuàng)建產(chǎn)品描述的步驟創(chuàng)建一個產(chǎn)品。在產(chǎn)品頁面可以獲取到產(chǎn)品的ProductKey和ProductSecret。
- 參照創(chuàng)建設(shè)備描述的步驟添加一個測試設(shè)備,在設(shè)備頁面可以獲取到設(shè)備的DeviceName和DeviceSecret。
集成SDK的開發(fā)過程
模組商在模組上集成SDK時,需要進(jìn)行下面幾個開發(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的配置工具,界面如下所示:
使能需要的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)。
其它功能均無需使能。
抽取選中功能的源代碼
將SDK代碼文件加入客戶編譯環(huán)境
客戶將上一個步驟中得到的Link SDK的代碼文件從output目錄復(fù)制到自己的工程目錄中,并修改自己的編譯環(huán)境或者開發(fā)工具將這些代碼文件集成到編譯環(huán)境。
實(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);
注:
- 設(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í)時性要求較高,可以將時間改小。
上面圖中第一個框是發(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)行不出錯。
如何判斷設(shè)備已成功發(fā)送數(shù)據(jù)到云端
注:上圖中的內(nèi)容只能看見消息發(fā)送到了哪個topic,消息的內(nèi)容并不會顯示出來。
如何判斷設(shè)備已可成功接收來自云端數(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)行修改。