arduino-cli

arduino-cli

mit Docker oder Github Actions

Man kann den Arduino zwar auch lokal mit der Arduino IDE programmieren oder Visual Studio Code mit PlatformIO nutzen, aber spätestens bei automatischen Tests wird man über die Arduino-CLI stolpern. Wir schauen uns an, was man mit der Arduino-CLI lokal machen kann und wie man sie sinnvoll mit Docker oder Github Actions einsetzt.

Wir sind jeden Dienstag um 21 Uhr live uns sprechen über digitale Dinge! Über einen Kommentar unter dem Video freuen wir uns sehr und noch schöner ist es, wenn wir uns im Live-Chat auf YouTube und Discord austauschen können. Du findest uns bei http://discord.digitale-dinge.de/

arduino-cli

Die offizielle Dokumentation zur arduino.cli befindet sich hier: https://arduino.github.io/arduino-cli/0.32/

Bei der arduino-cli handelt es sich um ein Werkzeug, das es ermöglicht, Arduino Code mit der Kommandozeile zu kompilieren und auf einen Arduino hochzuladen. Aber das Tool kann noch mehr!

Installation

https://arduino.github.io/arduino-cli/0.32/installation/

curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh

Konfidurationsdatei anlegen

arduino-cli config init

Arduino anschließen

Um zu sehen, welches Board an welchem seriellen Port angeschlossen ist, hilft dieser Befehl:

arduino-cli board list

Core installieren

arduino-cli core update-index

Alle installierten Cores listet dieser Befehlt auf:

arduino-cli core list

Um einen neuen Core zu installieren, nutzt man core install. Um den arduino:avr Core zu installieren, nutzt man diesen Befehl:

arduino-cli core install arduino:avr

Um Cores von anderen Quellen zu installieren, muss man zunächst die passende json-Datei herunterladen. Unter Linux kann man z.B. die Package Definition der sensebox MCU mit curl herunterladen:

curl -o /root/.arduino15/package_sensebox_index.json https://raw.githubusercontent.com/sensebox/senseBoxMCU-core/master/package_sensebox_index.json

Um diese Datei dann nutzen zu können, muss diese Zeile ausgeführt werden:

arduino-cli --additional-urls https://raw.githubusercontent.com/sensebox/senseBoxMCU-core/master/package_sensebox_index.json core install sensebox:samd

Einfacher geht es, wenn die URL der zusätzlichen Board direkt in der arduino-cli.yaml Datei eingetragen werden. Dazu wird ein Abschnitt board_manager benötigt, der unter additional_urls alle URLs auflistet:

arduino-cli.yaml:

...
board_manager:
  additional_urls:
    https://raw.githubusercontent.com/sensebox/senseBoxMCU-core/master/package_sensebox_index.json
    https://espressif.github.io/arduino-esp32/package_esp32_index.json

WICHTIG: nachdem die Datei verändert wurde, müssen die neuen Cores heruntergeladen und installiert werden. Dafür erneut diesen Befehl ausführen:

arduino-cli core update-index

Da wir in diesem Beispiel die Package Informationen für ESP32 Boards hinzugefügt haben, können wir nach diesem Core nun suchen:

arduino-cli core search esp32

Und natürlich können wir diesen Core nun auch installieren:

arduino-cli core install esp32:esp32

Neuen Sketch erstellen

arduino-cli sketch new Blink

Der neue Sketch beinhaltet eine setup() und eine loop() Funktion:

cat Blink.ino
void setup() {
}

void loop() {
}

Hier können wir nun wie gewohnt unseren Arduino-Programmcode eingeben:

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(1000);
    digitalWrite(LED_BUILTIN, LOW);
    delay(1000);
}

Sketch kompilieren

Möchte man den Sketch für den Arduino UNO kompilieren, muss dieser Befehl verwendet werden:

arduino-cli compile --fqbn arduino:avr:uno Blink.ino

Probleme beim Kompilieren für ESP32

Um den selben Sketch für ein ESP32 Nodemcu zu kompilieren, kann der Befehl leicht angepasst werden:

arduino-cli compile --fqbn esp32:esp32:nodemcuv2 Blink.ino

Da Python eine Katastophe ist, muss man ggf. folgende Befehle unter Linux ausführen, damit nicht die falsche Python Version genutzt wird, was in der Regel immer zu Problemen mit dem esptool führen wird:

sudo apt install python-is-python3

Damit niemals wieder diese Python2-Katastrophe passieren kann, sollte man die Installation von Python2 verbieten:

sudo apt-mark hold python2 python2-minimal python2.7 python2.7-minimal libpython2-stdlib libpython2.7-minimal libpython2.7-stdlib

Wer diese Probleme hat, wird auch diesen Befehl wahrscheinlich einmal ausführen müssen, um pyserial passend zu installieren:

pip install pyserial

LED_BUILTIN und Compiler Flags

Nicht bei jedem Board ist LED_BUILTIN definiert. Wenn man z.B. ein generisches ESP32 Board verwendet, an dem die LED an Pin 13 angeschlossen ist, kann man Compiler Flags über den --build-property Parameter anhängen:

  arduino-cli compile -b esp32:esp32:esp32 --build-property "build.extra_flags=-DLED_BUILTIN=13" Blink.ino

Wo sind die Binaries?

Um die Binaries (hex, elf, usw.) ins Sketchverzeichnis zu bekommen, muss der Parameter -e hinzugefügt werden. Für unser Blink Beispiel also:

arduino-cli compile --fqbn arduino:avr:uno -e Blink.ino

Die Binaries befinden sich dann im build Unterverzeichnis.

Sketch auf Board flashen

Für den Arduino UNO können wir das zuvor kompilierte Sketch jetzt mit diesem Befehl auf das Board flashen - passe den seriellen Port (hier /dev/ttyACM0) ggf. an:

arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno .

Mein NodeMCU ESP32S3 wurde als /dev/ttyUSB0 erkannt und ich kann das Programm entsprechend auf das DevBoard laden:

arduino-cli upload -p /dev/ttyUSB0 --fqbn sp32:esp32:esp32s3 Blink.ino

Falls das ESP32 Board nicht erkannt wird

Häufig kommt es vor, dass man den seriellen Port des ESP32 Boards nicht aufgelistet bekommt, weil der BRLTTY Service schneller war. Das erkennt man leicht daran, dass eine ähnliche Ausgabe wie die folgende auf den Befehl sudo dmesg folgt:

usb 2-3: ch341-uart converter now attached to ttyUSB0
input: BRLTTY 6.5 Linux Screen Driver Keyboard as /devices/virtual/input/input38
usb 2-3: usbfs: interface 0 claimed by ch341 while 'brltty' sets config #1

Wenn Du nicht blind bist, kannst Du den BRLTTY Service einfach deaktivieren. Ich mache dazu folgende Eingaben:

for f in /usr/lib/udev/rules.d/*brltty*.rules; do
    sudo ln -s /dev/null "/etc/udev/rules.d/$(basename "$f")"
done
sudo udevadm control --reload-rules

Und wenn das nicht hilft, kann man auch diesen Befehl probieren:

sudo systemctl mask brltty.path

Das ESP32 Board sollte nun als /dev/ttyUSB0 o.ä. identifiziert werden können.

Libraries installieren

Um Libraries aus "unsicheren" Quellen wie git oder zip installieren zu können, muss man diese Funktion zunächst in der Config aktivieren:

 arduino-cli config set library.enable_unsafe_install true

Um nun z.B. die TinyGSM Library in Version v0.11.5 zu installieren, führt man diese Zeile aus:

arduino-cli lib install --git-url https://github.com/vshymanskyy/TinyGSM.git#v0.11.5

Wenn man zuvor eine Library (z.B. für den BMP280 Sensor) als zip-Datei heruntergeladen hat, kann man sie mit diesem Befehl installieren:

arduino-cli lib install --zip-path BMP280.zip

Ein Beispiel: Morsen mit der LED

Alle offiziellen Libraries befinden sich in diesem Repository: https://github.com/arduino/library-registry

Wenn man dort nach "Morse" sucht, findet man u.a. die MorseArduino Library. Wir wissen bereits, dass wir sie über ihre git-URL wie folgt installieren können:

arduino-cli lib install --git-url https://github.com/etherkit/MorseArduino.git

Da es sich jedoch um eine Library aus dem offiziellen Repo handelt, können wir sie auch einfach so installieren:

arduino-cli lib install "Etherkit Morse"

Wenn wir dann noch die uralte SimpleTimer Library via git installieren:

arduino-cli lib install --git-url https://github.com/schinken/SimpleTimer.git

können wir folgendes Sketch erstellen:

arduino-cli sketch new MorseDinge
cd MorseDinge

Und dann kopieren wir folgendne Code in das neue Sketch:

#include <SimpleTimer.h> // https://github.com/schinken/SimpleTimer.git
#include <Morse.h> // https://github.com/etherkit/MorseArduino

#define SPEED 15

Morse morse(LED_BUILTIN, SPEED);
SimpleTimer timer;

void repeatMe()
{
    morse.update();
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    timer.setInterval(1, repeatMe);

    delay(1000);
    morse.send("DIGITALE DINGE");
}

void loop()
{
    timer.run();
}

Jetzt noch schnell den Sketch für den Arduino UNO kompilieren:

arduino-cli compile -b arduino:avr:uno MorseDinge.ino 

Und auf das Board hochladen:

arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno .

Arduino-CLI mit Docker

Heutzutage installiert man ja nichts mehr lokal, sondern verwendet für alles einen Container. Docker bietet sich da an!

Wer Docker noch nicht installiert hat, folgt bitte dieser Anleitung: https://docs.docker.com/get-docker/

Das Ziel ist, einen Docker Container zu erstellen, in dem unser Arduino Sketch mit Hilfe von arduino-cli kompiliert wird.

Fangen wir mit einem neuen Verzeichnis an und nennen es HalloDinge und erstellen wir darin ein Unterverzeichnis mit dem Namen build:

mkdir HalloDinge
cd HalloDinge
mkdir build

Im Verzeichnis HalloDinge erstellen wir die bereits bekannte Sketch-Datei HalloDinge.ino:

HalloDinge/HalloDinge.ino:

#include <SimpleTimer.h> // https://github.com/schinken/SimpleTimer.git
#include <Morse.h> // https://github.com/etherkit/MorseArduino

#define SPEED 15

Morse morse(LED_BUILTIN, SPEED);
SimpleTimer timer;

void repeatMe()
{
    morse.update();
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    timer.setInterval(1, repeatMe);

    delay(1000);
    morse.send("DIGITALE DINGE");
}

void loop()
{
    timer.run();
}

Im Verzeichnis HalloDinge erstellen wir nun eine Datei Dockerfile mit dem folgenden Inhalt:

HalloDinge/Dockerfile:

FROM ubuntu:23.04

RUN apt-get update
RUN apt-get install -y curl

# install arduicno-cli
RUN curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh

RUN arduino-cli config init

# aloow unsafe sources (zip, git)
RUN arduino-cli config set library.enable_unsafe_install true

# update arduino-cli
RUN arduino-cli core update-index

# install arduino avr
RUN arduino-cli core install arduino:avr

RUN arduino-cli lib install --git-url https://github.com/etherkit/MorseArduino.git

RUN arduino-cli lib install --git-url https://github.com/schinken/SimpleTimer.git

RUN mkdir /build 

WORKDIR /HalloDinge/

COPY ./HalloDinge.ino /HalloDinge

RUN arduino-cli compile -b arduino:avr:uno -e HalloDinge.ino 

CMD ["bash"]

Wir können basierend auf diesem Dockerfile ein Docker Image bauen mit diesem Befehl:

docker build -t arduino-build .

Und jetzt kommt der wichtigste Teil: wir wollen einen Container erstellen, der dieses Image benutzt. Der Container soll nur solange exisiteren bis er beendet wird und dann soll er automatisch gelöscht werden. Das erledigt der Parameter --rm. Da wir mit der Shell des Containers interagieren müssen, fügen wir den Parameter -it hinzu. Um die kompilierten Binaries zu extrahieren, mounten wir den Unterordner /build aus unserem HalloDinge Ordner mit dem Ordner /build im Docker Container. Das passiert durch den Paramter -v $(pwd)/build:/build/ und das ganze sieht dann so aus:

docker run --rm -it -v $(pwd)/build:/build/ arduino-build

Der Container wird gestartet und wir sehen die Shell des Containers. In dieser Shell können wir uns wie in jedem normalen Ubuntu umschauen, etwa das aktuelle Verzeichnis listen:

ls -la

Wir sehen, dass es einen Unterordner build gibt. Den Inhalt dieses Ordners wollen wir auf unseren Host kopieren. Das geht ganz einfach in dem Container:

cp -R build/* /build/

wir können nur mit exit den Container verlassen und dieser wird automatisch gelöscht.

Die extrahierten Binaries liegen auf unserem Host Computer im Unterordner /build

TIPP: Auch wenn wir hier im Beispiel den Container automatisch löschen lassen, bietet sich an, von Zeit zu Zeit alls Container, die nicht mehr laufen, zu entfernen. Das geht am einfachsten mit dem Befehl docker container prune

Github Actions

Ein noch einfacherer Weg, um Arduino Sourcecode automatisch kompilieren zu lassen, ist es, Github Actions zu verwenden.

Dazu legen wir in github ein neues Repo an und clonen es lokal.

In dem leeren Repo erzeugen wir einen Unterordner HalloDinge und erstelllen darin die Datei HalloDinge.ino mit dem selben Inhalt wie bisher:

meinRepo/HalloDinge/HalloDinge.ino:

#include <SimpleTimer.h> // https://github.com/schinken/SimpleTimer.git
#include <Morse.h> // https://github.com/etherkit/MorseArduino

#define SPEED 15

Morse morse(LED_BUILTIN, SPEED);
SimpleTimer timer;

void repeatMe()
{
    morse.update();
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    timer.setInterval(1, repeatMe);

    delay(1000);
    morse.send("DIGITALE DINGE");
}

void loop()
{
    timer.run();
}

Wir benötigen für Github Actions ein Unterverzeichnis workflows im Unterverzeichnis .github:

mkdir .github
cd .github
mkdir workflows
cd workflows

Jetzt übertragen wir unser Wissen aus dem Dockerfile in eine Datei namens main.yml in diesem neuen Verzeichnis:

meinRepo/.github/workflows.main.yml:

name: build and release for Arduino UNO

on: [workflow_dispatch]

jobs:
  compile:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Arduino CLI
        uses: arduino/setup-arduino-cli@v1

      - name: Install platform and libraries
        run: |
          arduino-cli config init
          arduino-cli config set library.enable_unsafe_install true
          arduino-cli core update-index
          arduino-cli core install arduino:avr
          arduino-cli lib install --git-url https://github.com/etherkit/MorseArduino.git
          arduino-cli lib install --git-url https://github.com/schinken/SimpleTimer.git

      - name: Compile Sketch
        run: arduino-cli compile --fqbn arduino:avr:uno -e ./HalloDinge

Der Workflow erhält den Namen "build and release for Arduino UNO" und wird nur manuell ausgeführt (workflow_dispatch). Hier könnte man alternativ auch push oder pull_request eintragen. Bei push kann man zusätzlich die branches festlegen, die überwacht werden sollten, etwa in dieser Form:

...
on:
  push:
    branches:
      - 'main'
...

Dieser Workflow besitzt nur einen einzigen Job mit dem Namen 'compile'. Für diesen Job wird Ubuntu verwendet und es folgen unter steps: die einzelnen Schritte, analog zu dem, was wir im Dockerfile gemacht hatten.

Diese Datei ist in github zu commiten und zu pushen.

Um die Action auszuführen, muss man in der github Web-UI in dem Repo auf den Reiter "Actions". Dort ist die "build and release for Arduino UNO" Action zu sehen und man kann rechts im Menü auf "Run workflow" clicken, nachdem man die Action durch einen Click ausgewählt hat.

Push to Releases

Ohne hier ins Detail gehen zu wollen, möchte ich noch erwähnen, dass es möglich ist, die kompilierten Binaries automatisch einem neuen Release zuzuordnen. Dazu muss die main.yml Datei um folgende Einträge ergänzt werden:

      - name: Push to Releases
        uses: ncipollo/release-action@v1
        with:
          artifacts: "HalloDinge/build/arduino.avr.uno/*"
          tag: v0.1.${{ github.run_number }}
          token: ${{ secrets.TOKEN }}

secrets.TOKEN ist dabei ein deploy Token, das man extra für diese Action in seinem User Account angelegt hat. Ohne dieses Token hat die release-action keine Berechtigung, eine neue Release anzulegen.

Das komplette Repo gibt es hier: https://github.com/renebohne/arduino-github-actions-tutorial