• [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文件,抽出其中的pkdnds,分别是productKeydeviceNamedeviceSecret;接下来是构建开发环境,主要是从云端下载一个sdk:linkkit2.2.1.tar.gz;接下来将托来的SDK调用make进行编译,生成aliyun-iot-c-sdklib库文件,然后分别把对应的libinclude分别复制到根目录下的libinclude中;最后再次使用make进行cleanall,然后启动。(下面抽几个核心代码贴下)

    • json读取pkdnds:(我会用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!
    ");
        /*
         * 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!
    ");
    
        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}
    ", 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"}
    ", 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 git@github.com: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,但是updateget数据都有误)。

    楼主依次做了如下操作,均失败(阿里云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 git@github.com: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|园友交流群
    
  • 相关阅读:
    BZOJ1222: [HNOI2001]产品加工(诡异背包dp)
    洛谷P1208 [USACO1.3]混合牛奶 Mixing Milk(贪心)
    SDOI 2018划水记
    【Leetcode】Search in Rotated Sorted Array II
    HDU 4089 Activation
    linux scp ssh命令不用输入密码
    封装fastjson为spring mvc的json view
    codility上的练习(3)
    git 拉取远程分之到本地
    Oracle 索引扫描的4种类型
  • 原文地址:https://www.cnblogs.com/zjutlitao/p/10269835.html
Copyright © 2020-2023  润新知