I reckon that I’ll always try to start my technical articles with the following tl;dr to introduce a summary of a lengthy post.
TL;DR – Azure IoT Edge is a project which enables edge processing and analytics in IoT solutions. The modules within the IoT Edge gateway can be written in different programing languages (native C, as well as different module language bindings available such as Node.js, .NET, .NET Core and Java) and can run on platforms such as Windows, and various Linux distros. As part of what I do in my day job, I work with customers and partners as they build their edge modules. One of the key asks is to be able to write modules in .NET Core and deploy on Raspberry Pi due to its ease of use and popularity for PoC and prototyping purposes. This article explains how to run modules written for .NET Core within the same Azure IoT Edge framework.
This post is timely as the .NET Core engineering team just announced less than a week ago that the .NET Core Runtime ARM32 builds is now available. More details about this announcement available here. Please do take note that these builds are community supported, not yet supported by Microsoft and have a preview status. For prototyping purposes, I wouldn’t complain too much about this. My plan was to get the existing modules for .NET Core cross-compiled on my dev machine for linux-arm as well as .NET Core runtime 2.0.0. This cannot be performed on Raspbian because only .NET Core runtime is available, not SDK, and there are native runtime shared libraries for Raspbian which must be cross-compiled. I will demonstrate how you could pull a Docker container with the right cmake toolchain to cross-compile for Raspbian,
According to the documentation in .NET Core Sample for Azure IoT Edge, the current version of the .NET Core binding and sample modules were tested and written in .NET Core v1.1.1. However I’d also tested that this works with .NET Core 2.0.0.
Cross-compiling Azure IoT Edge native runtime for ARM32
If you do a search in NuGet, you will find that there are a number of NuGet packages for Azure IoT Edge which contain native runtime libraries for a number of platforms namely Windows x64, Ubuntu 16.04 LTS x64, Debian 8 x64 and .NET Standard. How about for Raspbian which is a flavour of Debian 8 on ARM32? Instead of waiting for this to be released, you cross-compile the runtime libraries. After all this is one of the benefits of the open-source nature of Azure IoT Edge project.
Within the Azure IoT Edge repo jenkins folder, we have a build script just for cross-compiling for Raspberry Pi. This script is called raspberrypi_c.sh and it will spit out an output which is the cmake toolchain file called toolchain-rpi.cmake. To make things simpler, the Azure IoT engineering team had created a Docker image. However there is no guarantee that this image will be kept on Docker Hub at all times. Now that it is, you can find it here. There are heaps of other Docker images available as well.
In your dev machine which has Docker daemon installed (not your Raspberry Pi), pull down the image by:
docker pull aziotbld/raspberrypi-c
Then run the Docker container:
docker run -it aziotbld/raspberrypi-c
Inside the Docker container do the following steps:
- Update and Install all the pre-requisite libraries.
apt-get update
apt-get install -y libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev uuid-dev apt-transport-https
2. Install .NET Core SDK on this Docker container. The image contains Ubuntu 14.04.
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
apt-get update
apt-get install dotnet-sdk-2.0.0
3. Git clone the Azure IoT Edge repository.
git clone https://github.com/Azure/iot-edge.git4
4. Target .NET Core 2.0.0 for Azure IoT Edge .NET Core binding and modules. To do so modify all of the .NET Core modules’ csproj files by doing the following:
i. Change <TargetFramework> from “netstandard1.3” to netcoreapp2.0 so that it looks like the following:
<TargetFramework>netcoreapp2.0</TargetFramework>
Note: .NET Standard is a specification for .NET APIs which form the base class libraries (BCL). In the original, specifying .NET Standard 1.3 means that .NET Core 1.0 implements .NET Standard 1.3, which also means that it exposes all APIs defined in .NET Standard versions 1.0 through 1.3. More information about this here.
ii. Comment <NetStandardImplicitPackageVersion> like the line listed here.
You can now publish specifically for the linux-arm runtime. In the shell script for tools/build_dotnet_core.sh, add a -r flag to the dotnet commands. E.g., in the lines for
dotnet restore and dotnet build, after adding the -r flag, the lines look like the following:
dotnet restore -r linux-arm $project
dotnet build -r linux-arm $build_config $project
5. For some reasons the Test nuget packages were missing, I trust everything is tested fine, so I will comment the last few lines of my build_dotnet_core.sh script.
#for project in $projects_to_test
#do
# dotnet test $project
# [ $? -eq 0 ] || exit $?
#done
6. Run
cd iot-edge
chmod 755 ./tools/build_dotnet_core.sh
./tools/build_dotnet_core.sh
7. Now it is time to cross-compile native runtime library for Raspbian 8 ARM32. Create a symbolic link to the RPiTools in /home/jenkins because the raspberrypi_c.sh script expects the RPiTools in the home directory which for the root user is the following:
ln -sd /home/jenkins/RPiTools/ /root/RPiTools
8. Run
chmod ./jenkins/raspberrypi_c.sh ./jenkins/raspberrypi_c.sh
Note: If you encounter cmake errors, just delete the install-dep directory and re-run the shell script above.
This creates a toolchain file at ./toolchain-rpi.cmake
9. Now is time to build Azure IoT Edge with .NET Core binding targeting 2.0.0 along with the cmake toolchain file.
./tools/build.sh --disable-native-remote-modules --enable-dotnet-core-binding --toolchain-file ./toolchain-rpi.cmake
10. All the native runtime libraries required to run your gateway on Raspbian and targeting .NET Core runtime 2.0.0 are in the following directories:
iot-edge/install-deps/lib/*.so
iot-edge/build/bindings/dotnetcore/libdotnetcore.so
iot-edge/build/samples/dotnet_core_module_sample/ - *.so and *.dll
iot-edge/build/modules/ - *.so and *.dll
11. Now it is worthwhile to commit the changes you have made in the Docker container in a new image which is properly tagged/labelled. You should also copy the entire iot-edge folder in a tar ball out of this Docker container, and move it to your Raspberry Pi device. Steps required to do this is outside of this tutorial.
12. You can check out the Azure IoT Edge samples from its GitHub repo.
13. I tried out this simulated_ble sample within the dotnetcore folder. Please make sure that you have .NET Core 2.0.0 runtime installed on your Raspberry Pi device. I added this within the a loaders section in my gw-config.json file. Actually I’m not too sure if this is how it works in Linux, but I was paranoid anyhow. The right way is to export the directory in the LD_LIBRARY_PATH environment variable, I think. 🙂
"loaders": [
{
"type": "dotnetcore",
"name": "dotnetcore",
"configuration": {
"binding.path": "/home/pi/iot-edge/build/bindings/dotnetcore/libdotnetcore.so",
"binding.coreclrpath": "/opt/dotnet-2.0.0/shared/Microsoft.NETCore.App/2.0.0/libcoreclr.so",
"binding.trustedplatformassemblieslocation": "/opt/dotnet-2.0.0/shared/Microsoft.NETCore.App/2.0.0/"
}
}
],
14. To be even more paranoid, I copied all the native runtime libraries (*.so) and DLLs for the .NET Core binding modules into my execution folder. I also copied and rename a native gateway host as gw and place it within the same folder.
15. The result…..
pi@raspberryfai:~/iot-edge-samples/dotnetcore/simulated_ble/src/bin/Debug/netcoreapp2.0$ ./gw gw-config.json
Gateway is running.
Device: 01:02:03:03:02:01, Temperature: 10.00
Device: 01:02:03:03:02:01, Temperature: 11.00
Device: 01:02:03:03:02:01, Temperature: 12.00
Device: 01:02:03:03:02:01, Temperature: 13.00
Device: 01:02:03:03:02:01, Temperature: 14.00
VOILA!