This article complies to the Typographic Conventions for Torizon Documentation.
When you develop native C/C++ applications for an embedded target you need a toolchain, that provides a compiler and all the libraries you need to convert your code into native code, for a specific target platform.
Development for Torizon is not an exception. Using containers you may not target Torizon OS itself, but the distribution you plan to run inside your application’s container. In this article, we will provide some generic information and some instructions targeting the Debian-based containers we provide as a base to develop your own apps.
Usually, applications, running on a PC/server, can be built directly on the target system, installing the compiler and other required components locally. This approach can be very efficient when your system has a good amount of processing power and also the capability of running all the tools needed to edit and debug your code. Many embedded systems are not so powerful and, anyway, it would not be so easy to integrate them with the server-based build system commonly used in CI/CD environments.
In short consider the following between cross/native-compilation:
Another option is to use emulation to run an ARM32 or ARM64 container on your development PC. In this case, you should expect build times that are one hundred or more times those you can expect with a cross-compilation or when building code natively on the same machine.
Cross-compilation within a container environment provides notable advantages for example:
Debian provides very good support for cross-compilation. It provides pre-packaged cross-compiling toolchains for all the architectures it supports and multi-architecture support.
Multi-arch support allows you to install Debian packages for different architectures on top of a running Debian system. This will allow you, for example, to install armhf or arm64 development packages on an x64 system. Such a feature greatly simplifies building complex codebases with multiple dependencies and, specifically for Torizon where the application can run in a Debian container also on the target, it allows you to quickly generate an image for the target (with the non-dev native packages) where your application can run.
On a Debian system you can add support for a different architecture (armhf in this sample) by running dpkg:
$ dpkg --add-architecture armhf $ apt-get update
After having installed the new architecture and updated the packages list (this will download up to date package lists also for the new architecture) you can install packages using the standard package-management tools:
$ apt-get install libzip-dev:armhf
You can specify the package required architecture by adding the architecture id after the package name with a colon as the divider.
To simplify development of applications targeting our standard base containers, Toradex provides some Torizon base SDK containers with the basic toolchains required to target our modules already pre-installed and configured. These are also tagged in such a way to make the choice of which container to use simple.
There are two versions of each architecture-specific SDK. One providing just the build tools, another one that works as an SSH server. The latter can be used for tools, like Visual Studio, that supports Linux development on a remote machine.
SDKs are named
Currently, supported ARCH values are armhf (for 32 bit containers) and arm64 (for 64 bit containers).
If you want to use a specific library in your code, or need to build code that references a specific library to run in a Torizon container, you’ll have to find which Debian packages provide your library for usage at runtime and during development. You will have to install the development package in the build container and the runtime package in the container you plan to run on the device.
Debian provides hundreds of ready to use packages, most of the times available for all the architectures they support. The best place to search for those packages is packages.debian.org.
As an example consider the following case where we want to build code requiring libzip:
If you are not sure about what development package matches a specific runtime and vice-versa you can go on the package’s property page and check the source package. Source package contain codebase used to build one or more packages and for libraries, this means, at least, a runtime one and a “-dev” one.
Once you found the development package, you can install it in your build container:
$$ apt-get install libzip-dev:armhf
And install the runtime one in the target container:
## apt-get install libzip4
Notice that for the build container you’ll have to use the architecture-specific suffix.
Docker multi-stage builds can be used to build multiple containers in sequence. This is very useful if you want to automate the build of Torizon applications.
You can use our base SDK container, add the development libraries you need and the code you have to build, build it and then copy the newly built executable in a target container where you also install the runtime libraries.
# take SDK container as base FROM torizon/debian-cross-toolchain-armhf:latest AS build # install all required development packages RUN apt-get install libzip-dev:armhf .... # get code you need to build RUN git clone ... # build code RUN cd myapp && make/cmake/etc. # create 2nd container for target FROM torizon/arm32v7-debian-base:latest # install runtime libraries RUN apt-get install libzip4 ... # take application executable from build container COPY --from=build /myapp/out/myappexecutable /usr/bin/ # configure container to run your application at startup CMD /usr/bin/myappexecutable
It is often the case that an application may require other processes or services that interact with it. However it is generally a best practice to only have 1 main process running per container. That being said you need not limit your containerized solution to just one container.
It is quite common to have a solution that utilizes multiple containers running in unison. For this purpose Docker provides their "docker-compose" tool. This tool allows the orchestration of multi-container solutions. For more information about working with multiple containers and how this can be of benefit please refer to our article about multi-container solutions here.
To get the most from the upcoming sections and test things in practice, we recommend that you clone the torizon-samples repository to your computer:
$ cd ~ $ git clone https://github.com/toradex/torizon-samples.git $ cd torizon-samples/weather
For this example we will be creating the following:
For our sample app we will use the free plan which allows for 60 API calls per minute.
If you want to actually run this application yourself, an account needs to be created on openweathermap.org. This is required in order to get the API key needed to make calls to the openweathermap servers. The registration process and how to obtain key is described here. The key is referred to as 'appid' in the requests.
For an actual working application you'll need to update the API key in the demo code. This value is currently hard-coded and needs to be replaced at the following functions in the weather.cpp file.
You can also modify the geographic coordinates you want by modifying these functions:
Specifically for those who have Windows as a development environment, Toradex provides a Visual Studio extension that makes C/C++ development for Torizon simple:
This section builds on the Multi-stage builds section and shows how to build a C++ application with CMake. The workflow here will be mostly manual using command line utilities to build your application. If you seek a more automated IDE approach to development please consider our IDE integrations mentioned above.
CMake is an open-source, cross-platform family of tools designed to build, test and package software.
Take note of the src directory which contains the main source code files for the application.
To compile this application with cmake we need a couple of files namely armhf-toolchain.cmake and CMakeLists.txt:
Finally we use the Dockerfile to build a containerized application. This Dockerfile is derived from the information available in the Multi-stage builds section. The Dockerfile also needs to be updated with correct architecture details during build-time when building for a different architecture.
Now that you're familiar with the files we can begin building the container. The following command will create a container image on your development PC.
# for armhf $ docker-compose build # for arm64 $ docker-compose build --build-arg IMAGE_ARCH=arm64v8 --build-arg TOOLCHAIN_ARCH=aarch64 --build-arg PKG_ARCH=arm64
Now that we've built the application container image you'll need to deploy it to your target device. For methods to deploy the container image to the target please see the article here.
For this application, we'll need a total of 3 containers.
We'll set up all 3 as shown in this docker-compose.yml file. This will also create the networks and volumes we need, link the containers, and expose Grafana on port 3000. You'll need to copy this file to your target TorizonCore device.
Finally we can start our application using
docker-compose. Make sure you've properly deployed the built container image, and to execute the below command from the same directory you copied
# docker-compose up -d
With the application up and running, you can now point your browser to http://X.X.X.X:3000, where X.X.X.X is the IP Address of the target device. Login with the default credentials (user: admin, password: admin).
Go to the Dashboards menu and select Manage. To set up the dashboard, click Import then Upload and browse for the Weather-