Build Microsoft Azure SDK For OpenWrt System


      Cloud for organizing and analyzing  big data is a trend, Microsoft introduces Azure Cloud for IoT purpose. it has been announced cross-platform (even microchip) and easy to be ported.
      The Azure SDK is for this issue : if this SDK has been ported to the endpoint of IoT, the device could talk to Azure Cloud directly.
      But from my perspective, the called endpoints in Microsoft term are local-server/router, which be able to sort out data from the meters. The structure be :
In most case, the routers are embedded system based, those are high performance (compared to the microchip in the meters side), which are able to deliver the local reports to the persons on site.

Below steps is to explain how to port Azure to OpenWRT, which is very common in the IP routers.

零. Create a Azure acount:

  In the Azure website, https://www.azureiotsuite.com/, you should create a Microsoft account on Azure purpose. And it is necessary to input your credit number to pass the register. While the account has been created, do not forget to your e-mail box for certificating.
  Once your registering has been done, create a solution as remote monitor:



While launch the solution, add a device in that, that should be custom device(physics hardware device) :

Then, you could input the device id or it be generated automatically. In here, I request it generating the id in automation.

 The screen would show the information which should be recorded for later usage:
 The id, hostname and key should be input into sdk client code.

The last action in this step is to enable the device just be created:

 
(In the screen, you could seek out the column of device properties, where is the hostname, id and key written.)


一.  Build the Azure sdk in x86 Linux natively:
   You could refer to this  Azure website.

  Check the tools have been installed :

sudo apt-get install -y git cmake build-essential curl libcurl4-openssl-dev libssl-dev uuid-dev

Git clone the azure clone code from repository:

git clone --recursive https://github.com/Azure/azure-iot-sdk-c.git

Go into the azure sdk folder, create a folder for contain the built binaries and call cmake to configure the setting:


cd azure-iot-sdk-c
mkdir x86_64
cd x86_64
cmake ..
cmake --build . -- -j4 

(The last line could be simply replaced as "make -j4" )

And to the folder azure-iot-sdk-c/x86_64/serializer/samples/simplesample_mqtt, to run the binary simplesample_mqtt, which would generate fake datum and send it to the cloud:


gaiger@i5-3210M:~/azure-iot-sdk-c/x86_64/serializer/samples/simplesample_mqtt$ ./simplesample_mqtt 
Info: IoT Hub SDK for C, version 1.1.18
Error: Time:Fri Jul 14 00:18:32 2017 File:/home/gaiger/azure-iot-sdk-c/iothub_client/src/iothub_client_ll.c Func:IoTHubClient_LL_CreateFromConnectionString Line:547 Tokenizer error
Error: Time:Fri Jul 14 00:18:32 2017 File:/home/gaiger/azure-iot-sdk-c/iothub_client/src/iothub_client_ll.c Func:IoTHubClient_LL_CreateFromConnectionString Line:662 iotHubName is not found
Failed on IoTHubClient_LL_Create


The problem is very obvious that the device id, key and hostname should be written in the code.

Edit the file azure-iot-sdk-c/serializer/samples/simplesample_mqtt/simplesample_mqtt.c (NOT under the x86_64 folder),  modify the code about line 31:


/*String containing Hostname, Device Id & Device Key in the format:             */
/*  "HostName=<host_name>;DeviceId=<device_id>;SharedAccessKey=<device_key>"    */
//static const char* connectionString = "[device connection string]";
static const char* connectionString = \
                                "HostName=test-0-remote-monitor23fea.azure-devices.net;" \
                                "DeviceId=CoolingSampleDevice_2656;" \
                                "SharedAccessKey=3OuqHv7YyejvxVdsYcV2OIZSof5scZRIBnKVq61Sy6s=";

There should be no space inside the string, or the sending action would not be successful.

Rebuild (you could type make in folder azure-iot-sdk-c/x86_64/serializer/samples/simplesample_mqtt) and run again, the command line reveals :


gaiger@i5-3210M:~/azure-iot-sdk-c/x86_64/serializer/samples/simplesample_mqtt$ ./simplesample_mqtt 
Info: IoT Hub SDK for C, version 1.1.18
IoTHubClient accepted the message for delivery
Message Id: 0 Received.
Result Call Back Called! Result is: IOTHUB_CLIENT_CONFIRMATION_OK 

The data has been sent successfully.

To the cloud site, the result has been recorded:
(DO NOT forget to set "Device to View" in upright corner of Azure website as the same as the set device id !!)



If you feel awful why your record has ONE datum only but I have three, do not worry that,for I have run the binary 3 times.

Now, for easy debugging, modify the file azure-iot-sdk-c/serializer/samples/simplesample_mqtt/simplesample_mqtt.c to keep generate data per two seconds, about line 223:


                        /* wait for commands */
                        while (1)
                        {
                            IoTHubClient_LL_DoWork(iotHubClientHandle);
#if(0)                            
                            ThreadAPI_Sleep(100);
#else
                            ThreadAPI_Sleep(2*1000);
                            //myWeather->DeviceId = "myFirstDevice";
                            myWeather->WindSpeed = avgWindSpeed + (rand() % 4 + 2);
                            myWeather->Temperature = minTemperature + (rand() % 10);
                            myWeather->Humidity = minHumidity + (rand() % 20);
                            printf("WindSpeed = %d, Temperature = %3.1f, Humidity = %3.1f\r\n", 
                                myWeather->WindSpeed, myWeather->Temperature, myWeather->Humidity);
                            {
                                unsigned char* destination;
                                size_t destinationSize;
                                if (SERIALIZE(&destination, &destinationSize, 
                                        myWeather->DeviceId, myWeather->WindSpeed, 
                                        myWeather->Temperature, myWeather->Humidity) != CODEFIRST_OK)
                                {
                                    (void)printf("Failed to serialize\r\n");
                                }
                                else
                                {
                                    sendMessage(iotHubClientHandle, destination, destinationSize, myWeather);
                                    free(destination);
                                }
                            }
#endif
                        }


Rebuild and run again, you will find the data  from client to cloud ceaselessly:

gaiger@i5-3210M:~/azure-iot-sdk-c/x86_64/serializer/samples/simplesample_mqtt$ ./simplesample_mqtt 
Info: IoT Hub SDK for C, version 1.1.18
IoTHubClient accepted the message for delivery
WindSpeed = 12, Temperature = 22.0, Humidity = 72.0
IoTHubClient accepted the message for delivery
WindSpeed = 14, Temperature = 26.0, Humidity = 71.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 29.0, Humidity = 77.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 26.0, Humidity = 78.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 20.0, Humidity = 74.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 25.0, Humidity = 61.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 21.0, Humidity = 73.0
IoTHubClient accepted the message for delivery
WindSpeed = 15, Temperature = 22.0, Humidity = 61.0
IoTHubClient accepted the message for delivery
Message Id: 0 Received.
:


To here, the x86 based client has been done.

二. Build OpenWrt system for preparing necessary toolchain and libraries.

I adopted OpenWRT-CC. To grab the code, use git clone in terminal :


sudo apt-get install subversion build-essential git-core libncurses5-dev zlib1g-dev gawk flex quilt libssl-dev xsltproc libxml-parser-perl mercurial bzr ecj cvs unzip git wget
git clone https://github.com/domino-team/openwrt-cc.git

The first line is for download the depended libraries, the second is to download the code.

Go into the openwrt-cc folder, type below command and wait a while (for downloading the libraries for menu),  you could configure the building:

.config - OpenWrt Configuration
 ──────────────────────────────────────────────────────────────────────────────
  ┌───────────────────────── OpenWrt Configuration ─────────────────────────┐
  │  Arrow keys navigate the menu.  <Enter> selects submenus ---> (or empty │  
  │  submenus ----).  Highlighted letters are hotkeys.  Pressing <Y>        │  
  │  includes, <N> excludes, <M> modularizes features.  Press <Esc><Esc> to │  
  │  exit, <?> for Help, </> for Search.  Legend: [*] built-in  [ ]         │  
  │ ┌─────────────────────────────────────────────────────────────────────┐ │  
  │ │        Target System (Atheros AR7xxx/AR9xxx)  --->                  │ │  
  │ │        Subtarget (Generic)  --->                                    │ │  
  │ │        Target Profile (TP-LINK TL-WR720N)  --->                     │ │  
  │ │        Target Images  --->                                          │ │  
  │ │        Global build settings  --->                                  │ │  
  │ │        Gl.iNet package choice shortcut  --->                        │ │  
  │ │    [ ] Advanced configuration options (for developers)  ----        │ │  
  │ │    [ ] Build the OpenWrt Image Builder                              │ │  
  │ │    [ ] Build the OpenWrt SDK                                        │ │  
  │ │    [*] Package the OpenWrt-based Toolchain                          │ │  
  │ └────┴(+)─────────────────────────────────────────────────────────────┘ │  
  ├─────────────────────────────────────────────────────────────────────────┤  
  │        <Select>    < Exit >    < Help >    < Save >    < Load >         │  
  └─────────────────────────────────────────────────────────────────────────┘  
    

This setting menu  is pretty similar with linux kernel configuration menu, actually, the linux kernel would be built in OpenWrt package indeed.

For Azure client SDK issue, the following item should be set as Built-in(star note,* ), (press space botton to switch the status):


Package the OpenWrt-based Toolchain

Base System ---> librt

Libraries ---> SSL  ---> libopenssl

Libraries ---> libcurl 

Libraries --->libuuid

And set the column, " Target Profile " as the same as your device.

For me, I select TP-LINK TL-WR720N, for my development board (bought from 淘寶Alibaba of the Celestial Empire) marking out it is this model in its setting website:


Or you could find which model is your router in the webiste of OpenWrt, or the model would be written in the sticker in the button of your router.

Final action: type make and wait the building be done:


gaiger@i5-3210M:~/openwrt-cc$ make -j8
 make[1] world
 make[2] tools/install
 make[2] package/cleanup
 make[3] -C tools/patch compile
 make[3] -C tools/sstrip compile
 make[3] -C tools/make-ext4fs compile
 make[3] -C tools/firmware-utils compile
 make[3] -C tools/flock compile
:

How much time would be consumed depending how much money you spent in your computer. For my thinkpad T430@i5-3210m, it costs about 30 minutes.
Note : If you want to cleanse the configured setting, you could type "make distclean" back to the origin.


The built package will be organized and placed in folder staging_dir:

gaiger@i5-3210M:~/openwrt-cc$ ls staging_dir/
host
target-mips_34kc_uClibc-0.9.33.2
toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2

For my building, the target-mips_34kc_uClibc-0.9.33.2 and toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2 are the folders for system and toolchain respectively.

Check if the built gcc work, in my case, it exists in folder openwrt-cc/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/:

gaiger@i5-3210M:~/openwrt-cc$ ./staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-uclibc-gcc 
mips-openwrt-linux-uclibc-gcc: fatal error: no input files
compilation terminated.



三. Build Azure SDK for OpenWrt.

Back to the azure SDK folder, as x86, create a folder to hoard the built binary for target OpenWrt. My device is TP-LINK TL-WR720N v3@Atheros AR9330 rev 1, its CPU model is MIPS 24Kc V7.4. I name the folder as mips_24kc.
It is, create a folder named mips_24kc under azure-iot-sdk-c: ~/azure-iot-sdk-c/ mips_24kc

Create a file inside the target folder(mips_24kc), the file name is mips_24kc.cmake, to configure cross-compilation information for cmake usage :



INCLUDE(CMakeForceCompiler)

SET(CMAKE_SYSTEM_NAME Linux)     # this one is important
SET(CMAKE_SYSTEM_VERSION 1)     # this one not so much

# this is the location of the amd64 toolchain targeting your device
SET(CMAKE_C_COMPILER /home/gaiger/openwrt-cc/staging_dir/toolchain-mips_24kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-uclibc-gcc)
SET(CMAKE_FIND_ROOT_PATH /home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2)

# search for programs in the build host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

# for libraries and headers in the target directories
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# system location of target device
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

All you have to do is to change the CMAKE_C_COMPILER line as the location of the cross-compiler built from OpenWrt package, and modify CMAKE_FIND_ROOT_PATH line as the path of OpenWrt the system just built. The path and location should be in IN THE FULL PATH FORM WITHOUT TILDE NOTE REPRESENTING as home

Note : one is location, another is path.


The next action is to configure :

gaiger@i5-3210M:~/azure-iot-sdk-c/mips_24kc$ cmake .. \
-DCMAKE_TOOLCHAIN_FILE=mips_24kc.cmake 

-- The C compiler identification is GNU 4.8.3
-- The CXX compiler identification is GNU 4.8.3
-- Check for working C compiler: /home/gaiger/openwrt-cc/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-uclibc-gcc
:
:
:
-- AMQP Target architecture: GENERIC
-- MQTT Target architecture: GENERIC
-- iothub architecture: GENERIC
-- Configuring done
-- Generating done
-- Build files have been written to: /home/gaiger/azure-iot-sdk-c/mips_24kc

It is time to build the libraries:

But, while you type "make", it will occur error :

gaiger@i5-3210M:~/azure-iot-sdk-c/mips_24kc$ make 
:
:
[ 18%] Built target aziotsharedutil
[ 18%] Linking C executable iot_c_utility
/home/gaiger/openwrt-cc/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/lib/gcc/mips-openwrt-linux-uclibc/4.8.3/../../../../mips-openwrt-linux-uclibc/bin/ld: cannot find -lcurl
collect2: error: ld returned 1 exit status

Optional : you could set bash variable STAGING_DIR in terminal to disable compilation warnings, but they are just complaint only.

gaiger@i5-3210M:~$ export STAGING_DIR=/home/gaiger/openwrt-cc/staging_dir


The error occurs when the binary is being built in linking stage. Open the linking description file for simplesample_mqtt, which is under the build folder azure-iot-sdk-c/mips_24kc/serializer/samples/simplesample_mqtt/CMakeFiles/simplesample_mqtt.dir/link.txt :
 Note : I have organized the command for easy reading, actually that is txt file, the next line note (backslash, \) would not work in this case. 
  
/home/gaiger/openwrt-cc/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-uclibc-gcc  \
-fPIC  -Werror   CMakeFiles/simplesample_mqtt.dir/simplesample_mqtt.c.o \
CMakeFiles/simplesample_mqtt.dir/linux/main.c.o  -o \
simplesample_mqtt \
-rdynamic \
../../../iothub_client/libiothub_client_mqtt_transport.a \
../../libserializer.a \
../../../iothub_client/libiothub_client.a \
../../../umqtt/libumqtt.a \
../../../c-utility/libaziotsharedutil.a \
-lcurl \
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib/libssl.so \
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib/libcrypto.so \
-lpthread -lm -lrt -luuid -Wl,
-rpath,\
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib


Constrast to libssl.so and libcrypto.so in the full path form, linking libcurl and libuuid have been scribed in the abbreviation form. It is expedient method to solve the error that to modify the linking libraries in the full path form manually (it is drastic and primitive way):


/home/gaiger/openwrt-cc/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-uclibc-gcc  \
-fPIC  -Werror   CMakeFiles/simplesample_mqtt.dir/simplesample_mqtt.c.o \
CMakeFiles/simplesample_mqtt.dir/linux/main.c.o  -o \
simplesample_mqtt \
-rdynamic \
../../../iothub_client/libiothub_client_mqtt_transport.a \
../../libserializer.a \
../../../iothub_client/libiothub_client.a \
../../../umqtt/libumqtt.a \
../../../c-utility/libaziotsharedutil.a \
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib/libcurl.so \
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib/libssl.so \
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib/libcrypto.so \
-lpthread -lm -lrt /home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib/libuuid.so -Wl,
-rpath,\
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib

And type make under the folder azure-iot-sdk-c/mips-24kc/serializer/samples/simplesample_mqtt :

gaiger@i5-3210M:~/azure-iot-sdk-c/mips-24kc/serializer/samples/simplesample_mqtt$ make
[ 18%] Built target serializer
[ 75%] Built target aziotsharedutil
[ 78%] Built target umqtt
[ 90%] Built target iothub_client_mqtt_transport
[ 93%] Built target iothub_client
[ 96%] Linking C executable simplesample_mqtt
/home/gaiger/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib/libcrypto.so: warning: gethostbyname is obsolescent, use getnameinfo() instead.


The compilation has been passed.

This solution is just a work-around: manual replacement string is not a good way (but it works). The better way is to fix the CMake script, or write a bash script to darn all link.txt files automatically.

I have prepared a script to achieve the substitution.

Create a file named dornlink.sh under path azure-iot-sdk-c/mips_24kc:


# bin/bash
#Power by Gaiger Chen 撰也垓恪, to fix azure sdk error in linking stage

echo Back up file as link.txt in the same folders

find -name link.txt -exec cp {} {}.bak -f \
#find -name link.txt -exec rm {}.bak -f \;
#find . -ipath "*link.txt" -type f  -exec cp {} {}.bak \;
#find . -ipath "*link.txt" -type f  -exec rm {}.bak \;

FOUND_LINKINK_TXT=$(find -name link.txt)

OPENWRT_LIB_PATH=""

echo "$FOUND_LINKINK_TXT" | while read LINE_CONTENT
do
	if [ -z "$OPENWRT_LIB_PATH" ]; then
		OPENWRT_LIB_PATH=$(sed -rn 's/.* (.*)\/libssl.so .*/\1/p' "$LINE_CONTENT")
		echo "$OPENWRT_LIB_PATH"
	fi

	echo fix file: "$LINE_CONTENT"
	sed -i "s|-lcurl|$OPENWRT_LIB_PATH/libcurl.so|g" "$LINE_CONTENT"
	sed -i "s|-luuid|$OPENWRT_LIB_PATH/libuuid.so|" "$LINE_CONTENT"
done # while read LINE_CONTENT


FILE_NUM=$(echo "$FOUND_LINKINK_TXT" | wc -l)
echo "$FILE_NUM" files have been fixed.

After type chmod 777 dornlink.sh, run the script then rebuild,  the linking errors should be all gone.


To here, the cross-compiling has been done.

四. Deploy the binary and libraries to the target device.

Move the built binary to the target device:


scp simplesample_mqtt [email protected]:/tmp


Run the binary in the device, there is an error that the library could not be found :

root@JoySince:/tmp# ./simplesample_mqtt 
./simplesample_mqtt: can't load library 'libcurl.so.4'

Therefore, the depended libraries should be copy to the device.


In the folder ~/openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/usr/lib, the libraries libcurl.so.4 libssl.so.1.0.0 libcrypto.so.1.0.0 libuuid.so.1  and libmbedtls.so.9 should be copied to the device, respond to /usr/lib folder:

scp libcurl.so.4 libssl.so.1.0.0 libcrypto.so.1.0.0 libuuid.so.1  libmbedtls.so.9  [email protected]:/usr/lib



In the folder openwrt-cc/staging_dir/target-mips_34kc_uClibc-0.9.33.2/root-ar71xx/lib, the libraries libpthread.so.0 and librt.so.0 should be copied to the device:

scp libpthread.so.0 librt.so.0 [email protected]:/lib


Now, run the binary in device again:

root@JoySince:/tmp# ./simplesample_mqtt 
Info: IoT Hub SDK for C, version 1.1.18
IoTHubClient accepted the message for delivery
WindSpeed = 12, Temperature = 24.0, Humidity = 72.0
IoTHubClient accepted the message for delivery
Info: error:14090086:lib(20):func(144):reason(134)
Info: Closing tlsio from a state other than TLSIO_STATE_EXT_OPEN or TLSIO_STATE_EXT_ERROR
WindSpeed = 12, Temperature = 21.0, Humidity = 67.0
IoTHubClient accepted the message for delivery
WindSpeed = 12, Temperature = 28.0, Humidity = 62.0
IoTHubClient accepted the message for delivery
Info: Evaluated delay time 0 sec.  Retry attempt count 1

WindSpeed = 13, Temperature = 27.0, Humidity = 70.0
IoTHubClient accepted the message for delivery
Info: error:14090086:lib(20):func(144):reason(134)
Info: Closing tlsio from a state other than TLSIO_STATE_EXT_OPEN or TLSIO_STATE_EXT_ERROR

The sending data is not successful again.

It is the entity of digital certificates should be installed in the device, install it via okpg command :

root@JoySince:/tmp# opkg update && opkg install ca-certificates


After the installation done,  run the binary once :

root@JoySince:/tmp# ./simplesample_mqtt 
Info: IoT Hub SDK for C, version 1.1.18
IoTHubClient accepted the message for delivery
WindSpeed = 12, Temperature = 29.0, Humidity = 79.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 25.0, Humidity = 60.0
IoTHubClient accepted the message for delivery
WindSpeed = 14, Temperature = 21.0, Humidity = 60.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 23.0, Humidity = 78.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 23.0, Humidity = 77.0
IoTHubClient accepted the message for delivery
WindSpeed = 12, Temperature = 28.0, Humidity = 63.0
IoTHubClient accepted the message for delivery
WindSpeed = 14, Temperature = 26.0, Humidity = 60.0
IoTHubClient accepted the message for delivery
WindSpeed = 13, Temperature = 23.0, Humidity = 66.0
IoTHubClient accepted the message for delivery
Message Id: 0 Received.
Result Call Back Called! Result is: IOTHUB_CLIENT_CONFIRMATION_OK 
Message Id: 1 Received.
Result Call Back Called! Result is: IOTHUB_CLIENT_CONFIRMATION_OK 
Message Id: 2 Received.
Result Call Back Called! Result is: IOTHUB_CLIENT_CONFIRMATION_OK 
Message Id: 3 Received.
Result Call Back Called! Result is: IOTHUB_CLIENT_CONFIRMATION_OK 
Message Id: 4 Received.


In the Azure site, the data sent from the OpenWrt device have been received triumphantly.



Reference :

https://github.com/Azure/azure-iot-sdk-c/blob/master/doc/SDK_cross_compile_example.md
https://github.com/Azure/azure-iot-sdk-c/issues/58
https://github.com/Azure/iot-edge/issues/119
https://forum.openwrt.org/viewtopic.php?id=26725

猜你喜欢

转载自blog.csdn.net/u013606170/article/details/75167628