Я тут подумал, что давно про программирование ничего не писал. А тут и повод появился. Жорик-аннигилятор ведь вовсю обрастает кодом, который, естественно, хранится на домашнем GitLab сервере, но который всё ещё ни тестами не покрыт, ни билдится автоматически.. В общем, всячески игнорирует блага цивилизации.
Тут, конечно, есть некоторая уважительная причина — код пишется же на микроконтроллере, билдится через Arduino IDE, и с точки зрения CI/CD тут не сразу непонятно, с какой стороны ко всему этому подходить. Всё-таки, если тестировать, то.. как? Вводить какой-нибудь hardware abstraction layer? Или вот если автоматически собирать проект на GitLab сервере, то как тогда избавиться от окошек Arduino и делать всё исключительно в консоли? И как вообще спустя 2 месяца после начала проекта вспомнить, что за пакеты я устанавливал и откуда?
С тестами у меня уже какие-то мысли намечаются, но с автоматическими билдами и Continuous Integration вообще у Arduino, оказывается, всё вполне себе хорошо уже сейчас. Всего пара часов гугла и вот оно, работает. Теперь буду делиться впечатлениями.
Секретный рецепт
Если очень кратно, то есть такая штука как arduino-cli
, которая, пусть всё ещё и живёт в preview версии, вполне способна заменить своего взрослого оконного собрата. А дальше всё просто: берём гитлаб сервер, у которого настроен хотя бы один gitlab-runner (а иначе кто будет собирать билды?), в проект кладём .gitlab-ci.yml
, чтобы runner знал, что именно входит в билд, и изнутри дёргаем arduino-cli
.
Так как сам по себе GitLab CI слегка выходит за рамки этого поста, то вот ссылка на мой старый пост про сам GitLab, второй — про docker executors, один из которых будет сегодня использоваться, и мы сразу перейдём к созданию .gitlab-ci.yml
файла, настройки build окружения и самому билду. Поехали.
1. .gitlab-ci.yml
Значит, .gitlab-ci.yml
будет простым — запросит билд в docker контейнере, установит внутри arduino-cli
и библиотеки для проекта, и в качестве единственной стадии сборки скомпилирует проект.
Почему в docker контейнере? Просто я не люблю захламлять живую систему временными и большей частью бесполезными пакетами. А так запустил контейнер, поделал дела, да и выбросил его на помойку.
Запрашиваем билд в docker контейнере
На моём GitLab сервере зарегистрирован всего один gitlab-runner, который, по счастливому совпадению, сконфигурирован как docker-executor, и поэтому любые пришедшие к нему билды он будет запускать прямиком в докер контейнере. Более того, он и помечен тэгом docker
, так что для того, чтобы навсегда гарантировать, что мои esp32 билды пойдут именно туда, этот же тэг я буду использовать и в .gitlab-ci.yml
файле.
Для docker раннера ещё стоит указать какой именно образ контейнера мы хотим использовать, дабы он не скатился к дефолтному. На сегодня ubuntu:18.04
мне вполне хватит, так что заготовка для .gitlab-ci.yml
становится похожей вот на что:
1 2 3 4 5 6 7 8 9 10 11 |
image: ubuntu:18.04 stages: - build compile: stage: build tags: - docker script: - echo "Something like compilation should go here" |
2. Настройка окружения
Устанавливаем arduino-cli
arduino-cli
скачивается как обычный архив. Но так как кроме него мне нужно ещё будет настроить поддержку esp32 и установить уйму зависимостей, то стоит сразу завести какой-нибудь setup-build-env.sh
файл и складывать всё великолепие в него. Начнём с малого:
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/bash apt-get update cd ~ # Install arduino-cli apt-get install curl -y curl -L -o arduino-cli.tar.bz2 https://downloads.arduino.cc/arduino-cli/arduino-cli-latest-linux64.tar.bz2 tar xjf arduino-cli.tar.bz2 rm arduino-cli.tar.bz2 mv `ls -1` /usr/bin/arduino-cli |
Хотите верьте, хотите нет, но этого достаточно, чтобы привнести немного arduino в командную строку. Так как всё богатство запускается в контейнере, то по умолчанию это будет делать root
, так что всякие sudo
нам бесполезны.
Добавляем поддержку esp32
Теперь нужно заново научить arduino любить esp32. В Arduino IDE я помню ходил в меню board managers, регистрировал какой-то URL и радовался, когда esp32 борды появлялись в списке доступных. Похожий ритуал потребуется и тут, но уже из командной строки:
1 2 3 4 |
# Install esp32 core printf "board_manager:\n additional_urls:\n - https://dl.espressif.com/dl/package_esp32_index.json\n" > .arduino-cli.yaml arduino-cli core update-index --config-file .arduino-cli.yaml arduino-cli core install esp32:esp32 --config-file .arduino-cli.yaml |
Всё очень просто: создаём .arduino-cli.yaml
файл (произвольное имя, никакого скрытого смысла), записываем в него урл от esp32 настроек и устанавливаем, собственно, его поддержку. Чтобы узнать, что core install
должен устанавливать именно esp32:esp32
, я сначала запускал что-то вроде arduino-cli core search esp32 --config-file /root/.arduino-cli.yaml
.
Теперь можно, например, вызывать arduino-cli board listall
и подсмотреть полное имя своей esp32 платы. В будущем оно очень понадобится для компиляции. В моём случае это был ESP32 Dev Board с идентификатором esp32:esp32:esp32
.
1 2 3 4 5 6 7 |
arduino-cli board listall # ... # Dongsen Tech Pocket 32 esp32:esp32:pocket_32 # ESP32 Dev Module esp32:esp32:esp32 # ESP32 FM DevKit esp32:esp32:fm-devkit # ESP32 Pico Kit esp32:esp32:pico32 # ... |
Устанавливаем стандартные пакеты
За время своей короткой жизни Жорик-Аннигилятор успел обзавестить уймой зависимостей — для bmp280 сенсора, для аккселерометра, для асинхронного веб-сервера, и т.п. Я уже успел позабыть, что до этого устанавливал, но на то она и continuous integration, чтобы ронять компиляцию до тех пор, пока не вспомнишь. Я нашёл пять стандартных пакетов, без который мой проект уже не собрать, и заботливо переустановил их через тот же arduino-cli
:
1 2 3 4 5 6 |
# Install 'native' packages arduino-cli lib install "Adafruit BME280 Library" arduino-cli lib install "Adafruit Unified Sensor" arduino-cli lib install "HCSR04 ultrasonic sensor" arduino-cli lib install "ArduinoJson" arduino-cli lib install "MPU9250_asukiaaa" |
Устанавливаем нестандартные пакеты
Кроме стандартных пакетов была ещё и стопка нестандартных, клонированных с гитхаба и сложенных в папке libraries Arduino. Тот же трюк можно повторить и здесь. Главное, найти, где именно arduino-cli
хранит свои библиотеки.
К счастью, это можно сделать через тот же arduino-cli
, команду config dump
.
1 2 3 4 5 |
arduino-cli config dump # proxy_type: auto # sketchbook_path: /root/Arduino # arduino_data: /root/.arduino15 # board_manager: {} |
Итак:
1 2 3 4 5 6 7 8 |
# Install 'third-party' packages: find proper location and 'git clone' apt-get install git -y cd `arduino-cli config dump | grep sketchbook | sed 's/.*\ //'`/libraries # e.g. /root/Arduino/libraries git clone https://github.com/me-no-dev/AsyncTCP.git git clone https://github.com/me-no-dev/ESPAsyncWebServer.git git clone https://github.com/ThingPulse/esp8266-oled-ssd1306.git git clone https://github.com/RoboticsBrno/ESP32-Arduino-Servo-Library.git cd - |
Добавляем python и pyserial
У esp32 есть ещё 2 зависимости, о которых нечасто говорят вслух — python и pyserial, без которых проект, в общем, тоже не собрать. Всё это тоже вспоминаем кидаем в конфиг:
1 2 3 4 5 |
# Install python, pip and pyserial apt-get install python -y curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py pip install pyserial |
Всё вместе
Собрав все куски кода вместе в create-build-env.sh
файл…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/bin/bash apt-get update cd ~ # Install arduino-cli apt-get install curl -y curl -L -o arduino-cli.tar.bz2 https://downloads.arduino.cc/arduino-cli/arduino-cli-latest-linux64.tar.bz2 tar xjf arduino-cli.tar.bz2 rm arduino-cli.tar.bz2 mv `ls -1` /usr/bin/arduino-cli # Install python, pip and pyserial apt-get install python -y curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py pip install pyserial # Install esp32 core printf "board_manager:\n additional_urls:\n - https://dl.espressif.com/dl/package_esp32_index.json\n" > .arduino-cli.yaml arduino-cli core update-index --config-file .arduino-cli.yaml arduino-cli core install esp32:esp32 --config-file .arduino-cli.yaml # Install 'native' packages arduino-cli lib install "Adafruit BME280 Library" arduino-cli lib install "Adafruit Unified Sensor" arduino-cli lib install "HCSR04 ultrasonic sensor" arduino-cli lib install "ArduinoJson" arduino-cli lib install "MPU9250_asukiaaa" cd - # Install 'third-party' packages: find proper location and 'git clone' apt-get install git -y cd `arduino-cli config dump | grep sketchbook | sed 's/.*\ //'`/libraries git clone https://github.com/me-no-dev/AsyncTCP.git git clone https://github.com/me-no-dev/ESPAsyncWebServer.git git clone https://github.com/ThingPulse/esp8266-oled-ssd1306.git git clone https://github.com/RoboticsBrno/ESP32-Arduino-Servo-Library.git cd - |
…и попросив .gitlab-ci.yml
запустить его перед билдом…
1 2 3 4 5 6 7 8 |
image: ubuntu:18.04 before_script: - ./setup-build-env.sh stages: - build # ... |
… мы получим вполне полноценный CI, которому не хватает лишь самой команды компиляции. Конечно, установка всего и вся для каждого билда просто трындец как неэффективна и по-хорошему setup-build-env.sh
нужно было «впаять» прямо в контейнер. Но для начала сойдёт и так.
3. Собственно, шаг компиляции
Дальше всё вполне примитивно. После запуска команды arduino-cli board listall
я выяснил, что «официальное» имя моей esp32 платы esp32:esp32:esp32
. Файл проекта называется robot.ino
, поэтому, сложив слово compile с двумя словами выше, получится вот такая строка:
1 |
arduino-cli compile --fqbn esp32:esp32:esp32 -o ignored.bin robot.ino |
Из-за того, что preview версия arduino-cli
умудряется напортачить с дефолтным именем прошивки, пришлось явно указать, как назвать бинарник на выходе — -o ignored.bin
. И это всё!
Гранд-финал
Вот так теперь выглядит финальный .gitlab-ci.yml
файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
image: ubuntu:18.04 before_script: - ./setup-build-env.sh stages: - build compile: stage: build tags: - docker script: - arduino-cli compile --fqbn esp32:esp32:esp32 -o ignored.bin robot.ino |
Добавив его в проект и впервые запушив на сервер, получится вот такая неземная красота:
Мораль
В общем, делать continuous integration для esp32 и arduino проектов вполне себе можно. То, что у меня получилось, не особо образец для подражания — что-то можно оптимизировать, всё ещё не хватает тестов, но всё-таки это реально работает и по очкам ведёт у «нет CI вообще»! И теперь по крайней мере конфигурация зависимостей ясно прописана в коде, а не просто как-то работает на отдельно взятом лаптопе. В общем, со всех сторон вин.