[IOT] 阿里物聯網平臺產品建立體驗、linux SDK解析、基於ESP32移植aliyun-esp實現硬體和雲互動(硬核...
目錄:老少皆宜、超長乾貨文警告
- 1、快速入門建立產品 —— 小白,打包帶走去吹牛
- 2、程式碼分析 —— 老炮,快速瞭解能用上
- 2.1 從start.sh分析開發環境如何自動構建
- 2.2 從sample.c分析程式流程
- 2.3 資料下發流程分析
- 2.4 資料讀取與上報流程分析
- 3、移植到ESP32上搞IOT —— 二營長,把老子的義大利炮拿上來
- 3.1 搭建ESP32全自動命令列開發環境
- 3.2 基於ESP32移植並編譯阿里iotkit-embedded成lib
- 3.3 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 上下求索、坑外有坑
- 3.4 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 方法突破、絕處逢生
- 3.5 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 釋出訂閱、全部實現
- 3.6 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 全自動指令碼、免費送
- 附錄-LINK
字數:59710個
系統:linux、esp32
宣告:非軟文
1、快速入門建立產品 —— 小白,打包帶走去吹牛
連結: https://iot.console.aliyun.com/quick_start
直接看下面的視訊:(注意瀏覽器使能下unsafe load,因為我的視訊伺服器是自己搭的)
這裡建立了一個名字為 aliyun_test
的產品,在該產品下建立一個名為 linux_aliyun_teset
的裝置,並生成了一個基於linux平臺的嵌入式C開發工具包 aliyun_iot_device_quickstart.zip
。我們按照指引將工具包解壓、編譯、執行可看到通過MQTT雲端和本地進行通訊的效果:
unzip aliyun_iot_device_quickstart.zip cd aliyun_iot_device_quickstart sh ./start.sh
執行啟動指令碼後,可以在雲端的裝置日誌和本地termial中發現裝置通訊的LOG:
2、程式碼分析 —— 老炮,快速瞭解能用上
如果本文僅僅講體驗一下就太沒勁了!說不定部落格園小編還會把我的文章從主頁上“拉下馬”。
2.1 從start.sh分析開發環境如何自動構建
下面是沒有執行指令碼之前的 壓縮包內容 :
➜Downloadsmv aliyun_iot_device_quickstart aliyun_iot_device_quickstart2 ➜Downloadsunzip aliyun_iot_device_quickstart.zip ➜Downloadscd aliyun_iot_device_quickstart ➜aliyun_iot_device_quickstarttree . ├── device_id_password.json ├── makefile ├── sample.c └── start.sh 0 directories, 4 files
該壓縮包內包含:開始指令碼 start.sh , makefile ,應用層程式碼 sample.c ,裝置訪問Aliyun的核心資訊* .json 。
1)其中 start.sh 前50行都在檢測你的環境是否安裝必要工具,例如gcc、tar、cmake...;然後讀取 json
檔案,抽出其中的 pk\dn\ds
,分別是productKey\deviceName\deviceSecret;接下來是構建開發環境,主要是從雲端下載一個 sdk:linkkit2.2.1.tar.gz
;接下來將託來的SDK呼叫 make
進行編譯,生成 aliyun-iot-c-sdk
的 lib
庫檔案,然後分別把對應的 lib
和 include
分別複製到根目錄下的 lib
和 include
中;最後再次使用make進行clean\all,然後啟動。(下面抽幾個核心程式碼貼下)
-
從
json
讀取pk\dn\ds
:(我會用jq來處理)pk=`grep -Po '(?<=productKey": ")[0-9a-zA-Z]*' ${json}` dn=`grep -Po '(?<=deviceName": ")[\-_@\.:0-9a-zA-Z]*' ${json}` ds=`grep -Po '(?<=deviceSecret": ")[0-9a-zA-Z]*' ${json}`
-
下載sdk並解壓:
echo "download sdk tar" wget ${url} tar -zxf ${sdktar} ${sdkdir}/ rm -f ${sdktar}
-
編譯成庫:
cd ./iotx-sdk-c make distclean make cd .. cp -r ./iotx-sdk-c/output/release/lib ./lib cp -r ./iotx-sdk-c/output/release/include ./include
-
編譯並執行:
make clean -s make all PK=${pk} DN=${dn} DS=${ds} ./quickstart
2)其中 makefile 過於簡單,主要是用SDK生成的 lib
來編譯應用層程式碼 sample.c
,核心程式碼如下:
sample.o:sample.c $(CC) $(CFLAGS) $(INCLUDE) ${DID} -c $^ OBJS= sample.o .PHONY:all all:$(OBJS) $(LIB) $(CC) $(CFLAGS) $(INCLUDE) -o $(TARGET) $(OBJS) $(LIBVAR) $(LIBPATH) .PHONY:clean clean: rm -f *.o rm -f $(TARGET)
2.2 從sample.c分析程式流程
應用層程式碼總共約400行,下面是main函式:
int main(int argc, char **argv) { app_print_banner(); IOT_OpenLog("sample"); APP_TRACE("sample start!\n"); /* * C-SDK quick start sample * please check document: https://help.aliyun.com/document_detail/73708.html?spm=a2c4g.11174283.6.560.zfcQ3y *API introduce: https://help.aliyun.com/document_detail/68687.html?spm=a2c4g.11186623.6.627.RJcT3F */ app_setup_info(); app_linkkit_sample(); IOT_CloseLog(); APP_TRACE("sample end!\n"); return 0; }
其中最重要的就是 app_linkkit_sampl(void)
, 該函式的前30行主要負責初始化linkkit結構體並啟動linkkit:
- linkkit_ops_t結構體內的變數是從linkkit底層引出到應用層的函式指標,可見該結構體的作用相當於SDK層和應用層的咽喉;
- .on_connect函式指標,用於SDK層通知APP層裝置連線上雲了;
- .on_disconnect函式指標,用於SDK層通知APP層裝置與雲斷開了;
- .raw_data_arrived函式指標,用於SDK層將收到的原始資料通知到APP層;
- .thing_creat函式指標,用於SDK層通知APP層thing建立了(thing可能就是alithing吧);
- .thing_enable/disable = NULL;
- .thing_call_service函式指標,用於自定義服務回撥(暫時不太理解);
- .thing_prop_changed函式指標, 比較重要 ,從下圖log來看是雲端下發資料時裝置收資料的回撥函式;
- .linkit_data_arrived函式指標,暫時不清楚;
下圖非常詳細的展示了應用層上述函式指標的實現,以及程式執行起來後每部分的作用(建議單獨tab開啟圖片):
為了方便看,我把其縱向切割成3份:
-
linkkit_ops_t結構體初始化:
-
各種函式指標給linkkit_ops_t賦值:
-
LOG:
2.3 資料下發流程分析
我們建立的aliyun_test只有 兩個自定義功能 :
每次雲端有PROPERTY資料變化會出發下面的回撥函式,在該函式中我們通過判斷PROPERTY_ID,來區分不同功能點:
static int thing_prop_changed(const void *thing_id, const char *property, void *ctx) { int status[1]; char *data; if (memcmp(property, PROPERTY_ID_STATUS, strlen(PROPERTY_ID_STATUS)) == 0) { linkkit_get_value(linkkit_method_get_property_value, thing_id, property, status, NULL); APP_TRACE("Received a message: {\"%s\":%d}\n", property, status[0]); /* do user's data arrived process logical here. */ } else if (memcmp(property, PROPERTY_ID_DATA, strlen(PROPERTY_ID_DATA)) == 0) { linkkit_get_value(linkkit_method_get_property_value, thing_id, property, NULL, &data); APP_TRACE("Received a message: {\"%s\":\"%s\"}\n", property, data); HAL_Free(data);/* free memery as it was malloc by linkkit api linkkit_get_value() */ } return 0; }
下面是雲端主動推送資訊下來後本地列印的LOG:
2.4 資料讀取與上報流程分析
本DEMO啟動之後會每隔5S將上報所有(2個)property,具體邏輯是:先讀取STATUS和DATA,如果DATA沒有資料,則傳送hello world:
static int app_post_all_property(void) { int res; int status[1] = {0}; char *data = NULL; linkkit_get_value(linkkit_method_get_property_value, app_context.thing, PROPERTY_ID_STATUS, status, NULL); linkkit_get_value(linkkit_method_get_property_value, app_context.thing, PROPERTY_ID_DATA, NULL, &data); /* init data property to "Hello world" if it is value is NULL */ if (data == NULL) { linkkit_set_value(linkkit_method_set_property_value, app_context.thing, PROPERTY_ID_DATA, NULL, PROPERTY_ID_DATA_VALUE); linkkit_get_value(linkkit_method_get_property_value, app_context.thing, PROPERTY_ID_DATA, NULL, &data); } APP_TRACE("{\"%s\":%d, \"%s\":\"%s\"}", PROPERTY_ID_STATUS, status[0], PROPERTY_ID_DATA, data); HAL_Free(data);/* free memery as it was malloc by linkkit api linkkit_get_value() */ /* demo for post all property */ res = linkkit_post_property(app_context.thing, NULL, post_property_cb); if (res == SUCCESS_RETURN) { APP_TRACE("app post all properties every 5 seconds successfully"); } else { APP_TRACE("app post all properties every 5 seconds fail"); } return res; }
下面是本地主動週期性上報的LOG:
3、移植到ESP32上搞IOT —— 二營長,把老子的義大利炮拿上來
如果本文到此為止,老炮們肯定會吐槽這是個騙流量的文章!老炮內心OS中:阿里的linux上的除錯工具挺方便的,如果上面不寫程式碼分析,貼這麼多圖、變著花樣的貼圖,而且自己伺服器上搭的圖床頻寬還辣麼窄,看你寫啥(捂臉笑)。
3.1 搭建ESP32全自動命令列開發環境
通過下面兩個資料,大家可以自行搭建環境:
-
SDK介紹:對於ESP32樂鑫官方提供了一個IDF :
-
環境搭建:如果你想自己搭建開發環境,參見樂鑫官方資料:
不過!作為系統潔癖和拒絕重複造輪子的博主,已經寫了一個全自動構建環境的指令碼、並把該工具在github上開源了:esp32_linux_tool [5]
注:nbtool是博主專門放自己造的或收集到的牛逼輪子的github組
博主造的這個輪子比較好用,基於 all-in-one思想 (所有相關檔案在一個資料夾下;所有相關環境變數不需要額外配置):
-
用的時候需要git clone到本地,進入tool資料夾,執行bash run.sh tool會自動構建ESP-IDF開發環境:
git clone [email protected]:nbtool/esp32_linux_tool.git cd esp32_linux_tool cd tool bash run.sh help bash run.sh tool
-
當需要建立hello world時,只需要呼叫下面命令,即可從SDK中的DEMO複製到app資料夾下,並自動在app/hello_world下建立run.sh指令碼:
bash run.sh create ../sdk/esp-idf/examples/get-started/hello_world hello_world cd ../app/hello_world ./run.sh help
-
當需要編譯並燒寫韌體到ESP32中的時,只需要呼叫./run.sh flash即可:
./run.sh flash
-
當需要觀察LOG的時候,只需要:
./run.sh monitor
是不是很好用?(哈哈),想要了解其機制,只需要參考下tool下的run.sh即可~
3.2 基於ESP32移植並編譯阿里iotkit-embedded成lib
裝置廠商在裝置上整合 Link Kit C-SDK 後, 可以將裝置安全的接入到阿里雲IoT物聯網平臺, 從而讓裝置可以被阿里雲IoT物聯網平臺進行管理。
裝置需要支援TCP/IP協議棧才能整合SDK, zigbee/433/KNX這樣的非IP裝置需要通過閘道器裝置接入到阿里雲IoT物聯網平臺, 閘道器裝置需要整合Link Kit SDK [6] 。
基於我們的esp_32_linux_tool環境來編譯iotkit-embedded:
cd esp32_linux_tool cd app git clone https://github.com/aliyun/iotkit-embedded.git cd iotkit-embedded
在iot-embedded資料夾下建立一個run.sh檔案:
➜iotkit-embedded git:(master) ✗ cat run.sh #!/bin/bash #I don't like to set environment variables in the system, #so I put the environment variables in run.sh. #Every time I use run.sh, the enviroment variables will be set, after use that will be unsetted. PROJECT_ROOT=../.. TOOLS_PATH=$PROJECT_ROOT/tool SDK_PATH=$PROJECT_ROOT/sdk APP_PATH=$PROJECT_ROOT/app XTENSA_ESP32_ELF_PATH=$TOOLS_PATH/xtensa-esp32-elf ESP_IDF_PATH=$SDK_PATH/esp-idf the_sdk_path=`cd $ESP_IDF_PATH; pwd` the_tool_chain_path=`cd $XTENSA_ESP32_ELF_PATH/bin; pwd` export PATH="$PATH:$the_tool_chain_path" export IDF_PATH="$the_sdk_path" if [ "$1" == "reconfig" ]; then make reconfig elif [ "$1" == "make" ]; then make elif [ "$1" == "clean" ]; then make clean else echo "error, try bash run.sh help" fi
將 config.esp8266.aos
, 複製一份儲存為 config.esp32.aos
,做一些細微調整,最終如下:
➜iotkit-embedded git:(master) ✗ cat src/board/config.esp32.aos CONFIG_ENV_CFLAGS+= \ -DBOARD_ESP32 -u call_user_start \ -fno-inline-functions \ -ffunction-sections \ -fdata-sections \ -mlongcalls \ -DESPOS_FOR_ESP32 -Wl,-static \ -DXT_USE_THREAD_SAFE_CLIB=0 \ CONFIG_ENV_CFLAGS+= \ -Os \ -DCONFIG_HTTP_AUTH_TIMEOUT=500 \ -DCONFIG_MID_HTTP_TIMEOUT=500 \ -DCONFIG_GUIDER_AUTH_TIMEOUT=500 \ -DCONFIG_MQTT_TX_MAXLEN=640 \ -DCONFIG_MQTT_RX_MAXLEN=1200 \ CONFIG_src/ref-impl/tls:= CONFIG_src/ref-impl/hal:= CONFIG_examples:= CONFIG_tests:= CONFIG_src/tools/linkkit_tsl_convert := CROSS_PREFIX:= xtensa-esp32-elf-
最後,執行下面語句進行選擇平臺並編譯生成lib庫:
./run.sh reconfig ./run.sh make
最終生成libiot_sdk.a如下:
3.3 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 上下求索、坑外有坑
esp-aliyun [7] 是樂鑫官網提供的一個通過MQTT訪問aliyun iot伺服器的開源專案。該專案不僅依賴於3.2節介紹的iotkit-embedded [6] 生成的lib,而且尷尬的是辛辛苦苦編譯成功後,還不能和我們第一章建立的產品通訊(成功操作會連線到雲端保持online,但是update\get資料都有誤)。
樓主依次做了如下操作,均失敗(阿里雲IOT更新太快(資料不全)呀!):
- 對比linux SDK和esp-aliyun專案的區別,發現兩個實現方法不一樣: linux SDK用了linkkit(比較方便能和雲通訊,程式碼架構也清晰);esp-aliyun僅僅實現了MQTT的DEMO,只有沒有封裝太多的MQTT釋出訂閱函式(因此兩個程式碼有區別)
- 發現linkkit_ops_t專案本身也有DEMO: 這個在專案的examples下,其中包含mqtt/mqtt_example.c(和esp-aliyun一模一樣!!!)和linkkit/linkkit_example_solo.c(和linux SDK相似,用 IOT_Linkkit_xx實現高階玩法)
- 直接將linkkit/ linkkit_example_solo.c覆蓋掉esp-aliyun下的mqtt_example.c ,做合理修改,發現總是編譯不通過(嘗試各種iotkit-embedded引數配置,生成lib)
- 分析iotkit-embedded工程,發現編譯平臺為linux的時候能夠自動編譯example下面的demo, 同樣的操作選擇esp32的時候不能。
- 發現 AliOS Thing 似乎 實現 了上面我想要在esp-32上編譯執行基於IOT_Linkkit_xx的linkkit_example_solo.c demo!(捂臉笑)
但是樓主在前面已經立下flag,要基於我做的esp32_linux_tool實現一個能夠與第一章建立的aliyun_test互動的DEMO,那是絕對不能拿AliOS Thing來糊弄大家的(AliOS Thing之後講,哈哈)!
3.4 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 方法突破、絕處逢生
利用下班的一點點時間,將3.3的問題趟了兩天、阿里資料看了多遍,最終又找到了一個奇巧淫技!我將linux版的DEMO開一段時間,在aliyun後臺看所有互動的log的資料包,然後我參考這個資料包用mqtt_example.c裡的原始MQTT函式進行合成高階命令,然後實現和阿里雲通訊。
採用上述方法,我發現原來mqtt_example.c中 TOPIC和上報json資料格式是有問題的 :
-
其中TOPIC要按照建立產品的topic列表中來(其中釋出用來上報資料、訂閱用來收取資料):
/* These are pre-defined topics */ #define TOPIC_UPDATE"/"PRODUCT_KEY"/"DEVICE_NAME"/user/update" #define TOPIC_ERROR"/"PRODUCT_KEY"/"DEVICE_NAME"/user/update/error" #define TOPIC_GET"/"PRODUCT_KEY"/"DEVICE_NAME"/user/get" #define DEVICE_PROPERTY_POST"/sys/"PRODUCT_KEY"/"DEVICE_NAME"/thing/event/property/post"//裝置屬性上報 #define DEVICE_PROPERTY_POST_REPLY"/sys/"PRODUCT_KEY"/"DEVICE_NAME"/thing/event/property/post_reply"//裝置屬性上報變化訂閱(這個topic列表中沒有,我自己抓出來的) #define DEVICE_PROPERTY_SET"/sys/"PRODUCT_KEY"/"DEVICE_NAME"/thing/service/property/set"//裝置屬性設定訂閱 #define DEVICE_INFO_UPDATE"/sys/"PRODUCT_KEY"/"DEVICE_NAME"/thing/deviceinfo/update"//裝置標籤上報
-
其中json資料格式有一定的規範,不能直接組一個{"Status":1}就上報,要帶一部分引數:
{ "method":"thing.event.property.post", "id":"7", "version":"1.0", "params":{ "Status":1, "Data":"Hello, World!" } }
注:其實最後通過仔細閱讀Link Kit SDK使用者手冊 [8] ,也印證了我上面的嘗試~
3.5 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 釋出訂閱、全部實現
採用上述json格式,成功將資料上報到DEVICE_PROPERTY_POST的TOPIC下,通過進一步查後臺LOG發現一個隱藏的TOPIC:DEVICE_PROPERTY_POST_REPLY,通過訂閱該TOPIC每次上報導致資料變化就能被監聽到了!(和linux SDK版本一樣了,舒服)
一不做二不休,直接實現所有訂閱:
/* Subscribe the specific topic */ rc = IOT_MQTT_Subscribe(pclient, DEVICE_PROPERTY_SET, IOTX_MQTT_QOS1, _demo_message_arrive, NULL); if (rc < 0) { IOT_MQTT_Destroy(&pclient); EXAMPLE_TRACE("IOT_MQTT_Subscribe() failed, rc = %d", rc); return -1; } rc = IOT_MQTT_Subscribe(pclient, DEVICE_INFO_UPDATE, IOTX_MQTT_QOS1, _demo_message_arrive, NULL); if (rc < 0) { IOT_MQTT_Destroy(&pclient); EXAMPLE_TRACE("IOT_MQTT_Subscribe() failed, rc = %d", rc); return -1; } rc = IOT_MQTT_Subscribe(pclient, TOPIC_GET, IOTX_MQTT_QOS1, _demo_message_arrive, NULL); if (rc < 0) { IOT_MQTT_Destroy(&pclient); EXAMPLE_TRACE("IOT_MQTT_Subscribe() failed, rc = %d", rc); return -1; } rc = IOT_MQTT_Subscribe(pclient, DEVICE_PROPERTY_POST_REPLY, IOTX_MQTT_QOS1, _demo_message_arrive, NULL); if (rc < 0) { IOT_MQTT_Destroy(&pclient); EXAMPLE_TRACE("IOT_MQTT_Subscribe() failed, rc = %d", rc); return -1; }
實現資料週期性上報:
do { /* Generate topic message */ cnt++; msg_len = snprintf(msg_pub, sizeof(msg_pub), "{\"method\":\"thing.event.property.post\",\"id\":\"7\",\"version\":\"1.0\",\"params\":{\"Status\":%d,\"Data\":\"Hello, World!-%d\"}}",cnt%2 == 0,cnt); if (msg_len < 0) { EXAMPLE_TRACE("Error occur! Exit program"); return -1; } topic_msg.payload = (void *)msg_pub; topic_msg.payload_len = msg_len; rc = IOT_MQTT_Publish(pclient, DEVICE_PROPERTY_POST, &topic_msg); ... }
最終週期性上報資料並收到訂閱的回撥LOG截圖如下:
3.6 基於esp-aliyun和iotkit-embedded實現ESP32 DEMO —— 全自動指令碼、免費送
最後,為了感謝2018年來新老訪客的點贊(瘋狂暗示中),我編寫了一個超級輕量級全自動構建、編譯、燒寫、DEBUG的指令碼,助你一鍵體驗,爽翻。
GIT 地址: https://github.com/nbtool/esp32_linux_tool
體驗方法:
#克隆專案到本地 > git clone [email protected]:nbtool/esp32_linux_tool.git #構建esp32開發環境 > cd ./esp32_linux_tool/tool > ./run.sh help > ./run.sh tool #進入aliyun_demo應用資料夾,檢視幫助 > cd ../app/aliyun_demo > ./run.sh help |---------------------------------------------------- | ./run.sh op param | op: |create : downloads iotkit and aliyun-esp32 from github and make some change |sdk : param = reconfig/config/make/clean |app : param = deconfig/config/make/erase/flash/monitor/clean | examples: |first create sdk lib : create -> sdk reconfig -> sdk config -> sdk make |second create app : config -> make -> flash -> monitor |---------------------------------------------------- #編譯iotkit-embedded成lib > ./run.sh create > ./run.sh sdk reconfig > ./run.sh sdk make #編譯應用層程式碼,並燒寫檢視LOG > ./run.sh app config > ./run.sh app make > ./run.sh app flash > ./run.sh app monitor
注:可以從aliyun_demo/run.sh中瞭解如何實現自動化的~
: 完~ : 大家覺得不錯,可以點推薦給更多人~ : 明天年會,再幹一週,放假,用這篇超長文做個年終總結吧(笑)~LINKS
@beautifulzzzz 以藍芽技術為基礎的的末梢無線網路系統架構及創新型應用探索! 領域:智慧硬體、物聯網、自動化、前沿軟硬體 部落格:https://www.cnblogs.com/zjutlitao/ 園友交流群|微信交流群:414948975|園友交流群