[IOT] 自制藍牙工牌辦公室定位系統 (一)—— 阿里物聯網平臺概覽及打通端到雲(硬核·乾貨)


字數: 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分析開發環境如何自動構建

下面是沒有執行腳本之前的壓縮包內容

➜  Downloads  mv aliyun_iot_device_quickstart aliyun_iot_device_quickstart2
➜  Downloads  unzip aliyun_iot_device_quickstart.zip
➜  Downloads  cd aliyun_iot_device_quickstart
➜  aliyun_iot_device_quickstart  tree
.
├── device_id_password.json
├── makefile
├── sample.c
└── start.sh

0 directories, 4 files

該壓縮包內包含:開始腳本start.shmakefile,應用層代碼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-sdklib庫文件,然後分別把對應的libinclude分別複製到根目錄下的libinclude中;最後再次使用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全自動命令行開發環境

通過下面兩個資料,大家可以自行搭建環境:


不過!作爲系統潔癖和拒絕重複造輪子的博主,已經寫了一個全自動構建環境的腳本、並把該工具在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中瞭解如何實現自動化的~
完~
大家覺得不錯,可以點推薦給更多人~
明天年會,再幹一週,放假,用這篇超長文做個年終總結吧(笑)~

[1]. MarkDown語法進階(三)(文字居中、圖片處理、插入視頻音樂、標準字體)
[2]. Aliyun IOT Console
[3]. ESP32-IDF GITHUB地址
[4]. ESP-IDF Program Guide
[5]. esp32_linux_tool GITHUB地址
[6]. iotkit-embedded GITHUB地址
[7]. esp-aliyun GITHUB地址
[8]. Link Kit SDK 用戶手冊


@beautifulzzzz
以藍牙技術爲基礎的的末梢無線網絡系統架構及創新型應用探索!
領域:智能硬件、物聯網、自動化、前沿軟硬件
博客:https://www.cnblogs.com/zjutlitao/
園友交流羣|微信交流羣:414948975|園友交流羣
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章