You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
6.3 KiB
92 lines
6.3 KiB
\subsection{Build infrastructure and automatic deployment}
|
|
|
|
The CI system, which is based on \textit{drone} \cite{drone} allows to execute commands, whenever a new version is published into the projects \textit{Git} repository.
|
|
A corresponding \textit{drone} configuration called \texttt{.drone.yml} exists beside the source code (Listing~\ref{lst:drone}).
|
|
Within this configuration file, settings relevant to the build process are provided to the build environment.
|
|
First, the \texttt{CONFIG=maglab} option lets the build system use an additional configuration file (\texttt{Configurion.mk.maglab}), which is stored inside the framework repository and provides environment specific information, such as the WiFi SSID.
|
|
To keep secrets like the WiFi password and the private key unexposed, it is not written down in the configuration file.
|
|
Instead, to include secrets into a build process while allowing to keep the configuration public, \textit{drone} allows to encrypt these with a repository specific key.
|
|
Using this method, the password is stored as \texttt{.drone.sec} file inside the repository from where it is injected into the build environment.
|
|
Also noticeable in Listing~\ref{lst:drone} is the firmware version, which is configured to be the first 8 letters of the \textit{Git} commit hash uniquely identifying a version of the source code.
|
|
|
|
\begin{lstlisting}[language=,
|
|
caption={The \textit{drone} configuration for the \textit{ESPer} project.},
|
|
label=lst:drone, basicstyle=\ttfamily\scriptsize]
|
|
build:
|
|
image: maglab/sming
|
|
environment:
|
|
- CONFIG=maglab
|
|
- WIFI_PWD=$$WIFI_PWD
|
|
- VERSION=$${COMMIT:0:8}
|
|
commands:
|
|
- make clean
|
|
- make
|
|
publish:
|
|
sftp:
|
|
host: eddie.maglab.space
|
|
username: esper
|
|
files: [ dist/* ]
|
|
destination_path: './'
|
|
when: branch: master
|
|
\end{lstlisting}
|
|
|
|
For deployment, only the master branch is considered.
|
|
After a successful build, all distribution files (the firmware image and meta-information files) of all device types are copied to the repository server, where they are served by a \textit{HTTP 1.1} \cite{HTTP_1.1} server.
|
|
The configuration file (\texttt{Configurion.mk.maglab}) references this repository server as the source for updates.
|
|
|
|
Support for multiple devices of different type is implemented in both, the \textit{ESPer} framework itself and the build system.
|
|
The framework keeps control over the application life-cycle.
|
|
It ensures that device unspecific code is executed at the right time and provides an API for device specific functionality.
|
|
For this, a simple interface is specified by the framework, which must be implemented by each device.
|
|
A single function \texttt{Device* getDevice()} must be defined exactly once in each device specific folder.
|
|
To implement this interface, a static instance of \texttt{Device} is created and returned.
|
|
Each \texttt{Device} is populated with device specific \texttt{Feature} instances.
|
|
While the \texttt{Feature}-API leverages common run time polymorphism to share functionality between features, the initial \texttt{Device} creation uses compile time polymorphism, which reduces the need for memory management and increases performance by avoiding virtual function tables.
|
|
Listing~\ref{lst:create_device_socket} shows the complete device specific code used for a simple power socket, which is mainly confined to the device type and its capabilities (e.g., the GPIO pin numbers to use).
|
|
|
|
\begin{lstlisting}[caption={Device specific code for a socket driver.},
|
|
label=lst:create_device_socket, basicstyle=\ttfamily\scriptsize]
|
|
#include "Device.h"
|
|
#include "features/Socket.h"
|
|
|
|
Device device:
|
|
|
|
constexpr const char NAME[] = "socket";
|
|
constexpr const uint16_t GPIO = 12;
|
|
OnOffFeature<NAME, GPIO, false, 1> socket(&device);
|
|
|
|
Device* getDevice() {
|
|
return &device;
|
|
}
|
|
\end{lstlisting}
|
|
|
|
The actual compilation of the source code is mainly controlled using two \textit{Makefiles}.
|
|
The first one is a helper \textit{Makefile} built to accept a parameter for device type identifiers called \texttt{DEVICE}, and to create its whole output inside a subdirectory specific to the device type.
|
|
In addition, the primary \textit{Makefile} scans a project subdirectory and uses each directory in there as a container for device specific code.
|
|
For each of these directories, the helper \textit{Makefile} is called and the subdirectories name is used as the value of the \texttt{DEVICE} parameter.
|
|
By splitting the build and recompiling the framework each time before intermixing it with the device specific code, the device type identifier can be used inside the shared framework code.
|
|
While building a devices firmware, the meta-information file used during updates is also created and stored beside the firmware image.
|
|
For development, each device can be build separately by using the device type identifier as \textit{Makefile} target.
|
|
In addition, the suffix \texttt{/flash} can be used to flash a specific firmware to the device.
|
|
|
|
While building the firmware images for a device, the build environment provides some constants which are baked into the resulting firmware image.
|
|
Beside the environmental configuration like the WiFi credentials, \textit{MQTT} topics and other configurable tweaks, the current device and version identifiers are provided as compile time constants.
|
|
In addition, the public key used to verify firmware signatures during updates is derived from the private key and provided as an linker object which is linked into each firmware image (Listing~\ref{lst:public_key_object}).
|
|
This allows to use all the information inside the code without any overhead while being configurable during build time.
|
|
|
|
\begin{lstlisting}[caption={Creating the linker object containing the public key.},
|
|
label=lst:public_key_object, basicstyle=\ttfamily\scriptsize]
|
|
update_key_pub.bin:
|
|
echo -n "$(UPDATE_KEY)" | \
|
|
ecdsakeygen -p | \
|
|
xxd -r -p > "$@"
|
|
|
|
update_key_pub.o: update_key_pub.bin
|
|
$(OBJCOPY) -I binary $< \
|
|
-B xtensa \
|
|
-O elf32-xtensa-le $@
|
|
\end{lstlisting}
|
|
|
|
The build process will create the two firmware images, one for each ROM slot, and the meta-information file.
|
|
To create the meta-information file, the current version identifier is written to the \texttt{.version} file.
|
|
After the build, the signatures for both firmware images are created and attached to the file.
|