О чем статья
Эта статья посвящена описанию ключевых этапов создания образа для Cubieboard 1 на базе Yocto Linux, реализующего домашнюю метеостанцию с не совсем обычным набором функций: отображение параметров космической погоды, значения концентрации СО2 в воздухе и воспроизведение видео с удаленной видеокамеры. Детальное описание приложений, реализующих основную логику работы метеостанции, выходит за рамки данной статьи, при этом в статье приведены ссылки на исходный код этих приложений, с которыми читатель может ознакомиться самостоятельно.
Концепция
Давно хотелось разобраться, как работать с Embedded Linux и обзавестись метеостанцией с достаточно точным датчиком СО2 и отображением актуальных данных о космической погоде. Из этого и родился данный учебный проект метеостанции.
Основные цели проекта
Познакомиться с Yocto Project как одним из популярных инструментов создания образов Embedded Linux:
- Разобраться с синтаксисом и возможностями bitbake;
- Получить практический опыт кастомизации сборки Embedded Linux, используя различные техники:
— подключение различных слоев;
— создание своих конфигураций образов и дистрибутивов;
— кастомизация существующих рецептов;
— создание рецептов для Python приложений;
— создание рецептов для Rust приложений;
— конфигурирование ядра и Device Tree. - Получить опыт применения различных техник для дебага сборки;
Реализовать проект метеостанции со специфическим набором возможностей, в частности:
- Отображение текущих данных космической погоды и прогноза;
- Использование относительно точного датчика СО2;
- Наличие датчика влажности воздуха;
- Вывод информации в графическом виде на цветном дисплее;
- Вывод потокового видео с внешней камеры для наблюдения за северным сиянием в реальном времени для оправдания использования Linux в метеостанции.
Дополнительно: применить на практике язык Rust для написания некоторых модулей проекта в учебных целях.
В итоге были сформированы требования к прошивке и оборудованию.
- Использовать Yocto Project;
- Сбор сведений с датчиков:
— температура воздуха;
— относительный уровень влажности воздуха;
— концентрация СО2. - Сбор сведений из интернета:
— космическая погода: планетарный индекс геомагнитной активности Кр текущее значение, за прошедшие 24ч и прогноз;
— космическая погода: плотность потока протонов высоких энергий (солнечная радиация), текущее значение и прогноз; - Отправка всех собранных данных на MQTT брокер (внутренний или внешний);
- Отображение информации, используя данные с MQTT брокера;
- Отображение всех данных в графическом виде на цветном дисплее;
- Графический интерфейс реализовать, используя Slint GUI;
- Вывод потокового видео с внешней камеры с использованием аппаратного декодирования;
- Авто подключение к WiFi точке доступа через USB-WiFi модуль для отвязки устройства от сетевого кабеля;
- В качестве основной платы использовать Cubieboard (Allwinner A10), т. к. одна такая свободная плата была в наличии.
- В качестве датчика CO2 использовать MH-Z19b (надежный датчик с точностью ± (50ppm+5%) и готовыми open-source драйверами).
Реализация, используемые компоненты:
Hardware
Основная плата – Сubieboard1: Allwinner A10, HDMI.
Датчик CO2 – MH-Z19b, UART.
Датчик температуры и относительной влажности – HTU21D, I2C.
Дисплей – 7 inch LCD Wisecoco 1024×600, HDMI, USB touch.
USB WiFi – RTL8821CU.
Software
- Система сборки – Yocto Project;
- Базовый образ – core-image-base;
- Ядро Linux – Mainline Kernel 5.15;
- Загрузчик – Mainline U-boot;
- MQTT broker – Mosquitoo;
- Драйвер датчика CO2 – Python модуль «mh-z19» для Raspberry Pi от UedaTakeyuki https://github.com/UedaTakeyuki/mh-z19:
— Сервис MQTT клиента – «MH-Z19 Raspberry MQTT Client/Daemon» (Python) от R4scal https://github.com/R4scal/mhz19-mqtt-daemon; - Драйвер датчика HTU21D – модуль «htu21d-rs» (Rust) от jdeeny https://github.com/jdeeny/htu21d-rs:
— Сервис MQTT клиента – свой на Rust; - Провайдер погоды с сервисом MQTT клиента – свой на Rust:
— https://github.com/TEPOTPOH/mqtt-weather-provider; - GUI с сервисом MQTT клиента – свой на основе Slint GUI на Rust:
— https://github.com/TEPOTPOH/slint-meteo-gui; - Декодер/конвертер видео – Gstreamer.
Структура
Функциональная схема
Схема подключения
Детали образа
Набор используемых слоев
- Базовые:
poky git://git.yoctoproject.org/poky (ветка «kirkstone»):
— meta;
— meta-poky;
— meta-yocto-bsp;
meta-openembedded git://git.openembedded.org/meta-openembedded (ветка «kirkstone»):
— meta-oe;
— meta-python;
— meta-networking; - Слой для поддержки аппаратуры:
— meta-sunxi https://github.com/linux-sunxi/meta-sunxi (ветка «kirkstone»); - Слой для сборки Rust приложений, требующих более новую версию Rust (в частности, Slint GUI), чем предоставляет стандартное решение «meta-rust», а также для более простой конфигурации рецепта сборки Rust приложений. Слой использует уже скомпилированные Rust и Cargo. Сборка приложений с помощью «meta-rust-bin» быстрее по сравнению со стандартным способом сборки, используя слой «meta-rust»:
— meta-rust-bin https://github.com/rust-embedded/meta-rust-bin; - Свой слой, реализующий метеостанцию:
— meta-meteo https://github.com/TEPOTPOH/yocto-sunxi-meteo (ветка «main»); - Опциональный слой с драйверами для USB Wifi модулей на базе чипов от Realtek:
— meta-rtlwifi https://github.com/EmbeddedAndroid/meta-rtlwifi.
Схема слоев и рецептов
Слой meta-meteo
Конфигурация слоя
meta-meteo/conf/layer.conf
В зависимости слоя meta-meteo добавлен базовый слой, слой аппаратной поддержки и слой для сборки Rust приложений — это слои без которых meta-meteo собрать не получится.
layer.conf
BBFILE_COLLECTIONS += "meta-meteo"
BBFILE_PATTERN_meta-meteo = "^${LAYERDIR}/"
BBFILE_PRIORITY_meta-meteo = "6"
LAYERDEPENDS_meta-meteo = "core meta-sunxi rust-bin-layer"
LAYERSERIES_COMPAT_meta-meteo = "kirkstone"
Конфигурация дистибутива
meta-meteo/conf/distro/cubieboard-meteo.conf
В конфигурации ограничена сборка лишних файлов, кроме образа «sdimg», который необходим для записи на microSD карту.
IMAGE_FSTYPES = "sunxi-sdimg"
cubieboard-meteo.conf
require conf/distro/poky.conf
DISTRO = "cubieboard-meteo"
DISTRO_NAME = "Cubieboard meteo (Yocto Project based Distro)"
DISTRO_VERSION = "1.0.0+snapshot-${METADATA_REVISION}"
SDK_VERSION = "${@d.getVar('DISTRO_VERSION') /
.replace('snapshot-${METADATA_REVISION}', 'snapshot')}"
SDK_VERSION[vardepvalue] = "${SDK_VERSION}"
MAINTAINER = "Roman Eliseev <tepotpoh@gmail.com>"
IMAGE_FSTYPES = "sunxi-sdimg"
DISTRO_FEATURES:remove = " 3g nfc"
Конфигурация дистрибутива с поддержкой USB WIFI RTL8821CU
Дистрибутив основан на cubieboard-meteo.conf. Дополнительно включена фича «rtl8821cu», которая добавляет необходимый для работы RTL8821CU драйвер в образ. Т.к. WiFi модуль также поддерживает Bluetooth, то включена также фича «bluetooth» для установки компонентов для работы с Bluetooth, которые могут потребоваться в будущем для подключения беспроводных датчиков.
# Distro with RTL8821CU USB WIFI/BT driver
require cubieboard-meteo.conf
DISTRO = "cubieboard-meteo-rtl8821cu"
DISTRO_NAME = "Cubieboard meteo (Yocto Project based Distro) with /
RTL8821CU WIFI/BT driver"
DISTRO_FEATURES:append = " rtl8821cu bluetooth"
Рецепт сборки образа core-image-meteo
meta-meteo/recipes-core/images/core-image-meteo.bb
Образ основан на core-image-base – базовом Yocto Linux образе с консолью и поддержкой выбранной платформы.
require recipes-core/images/core-image-base.bb
В образ через IMAGE_INSTALL добавлены основные приложения метеостанции.
IMAGE_INSTALL:append = " co2-sensor-daemon"
IMAGE_INSTALL:append = " htu21d-daemon"
IMAGE_INSTALL:append = " slint-gui liberation-fonts"
IMAGE_INSTALL:append = " weather-provider"
IMAGE_INSTALL:append = " mosquitto"
Некоторые приложения используют для конфигурирования своих параметров environment переменные. Для их переопределения введен конфигурационный bash скрипт, задающий необходимые переменные до старта приложений. Это сделано через переменную ROOTFS_POSTPROCESS_COMMAND, в которую добавляются функции, вносящие необходимые изменения в конфигурационные файлы образа уже после установки всех пакетов.
set_global_env() {
mkdir -p ${IMAGE_ROOTFS}${sysconfdir}/profile.d
ENV_FILE=${IMAGE_ROOTFS}${sysconfdir}/profile.d/set_global_env.sh
# set part of MQTT topic
echo "export MQTT_DEVICE_NAME=${MACHINE}" > $ENV_FILE
# Slint display-rotation
echo "export SLINT_KMS_ROTATION=270" >> $ENV_FILE
}
ROOTFS_POSTPROCESS_COMMAND += "set_global_env;"
Еще в рецепт добавлено конфигурирование поворота сенсорной панели дисплея на нужный угол, для соответствия ориентации изображения.
Также в рецепт добавлена конфигурация WiFi для авто подключения к точке доступа и установка драйвера, для случая, когда включена фича «rtl8821cu».
Не мало времени было потрачено на попытку запуска USB WiFi модуля RTL8821CU — USB устройство определялось в системе как USB накопитель и не переключалось в режим сетевого устройства. Проблема решилась добавлением в сборку следующих пакетов:
usb-modeswitch usb-modeswitch-data rfkill
Для установки только для RTL8821CU пакеты подвязаны на «фичу» rtl8821cu:
IMAGE_INSTALL:append = "${@bb.utils.contains('DISTRO_FEATURES', 'rtl8821cu', ' rtl8821cu usb-modeswitch usb-modeswitch-data rfkill', '', d)}"
К сожалению, в репозитории с драйверами не обозначено, что эти пакеты являются необходимыми для работы драйвера.
Еще одна проблема с USB WiFi была в том, что драйвер при старте инициализировался достаточно долго и уже срабатывала команда поднятия беспроводного интерфейса приводя к неудаче. Поэтому в конфигурационный файл /etc/network/interfaces была добавлена pre-up задержка до поднятия интерфейса в 5 сек.
wifi_config () {
INTERFACES_FILE=${IMAGE_ROOTFS}${sysconfdir}/network/interfaces
if ! grep -q "^pre-up sleep 5" "$INTERFACES_FILE"; then
sed -i "/^iface wlan0/a pre-up sleep 5" $INTERFACES_FILE
fi
}
ROOTFS_POSTPROCESS_COMMAND += "wifi_config;"
Итоговый рецепт сборки образа
DESCRIPTION = "Image based on core-image-base for home meteostation with LCD display"
require recipes-core/images/core-image-base.bb
export IMAGE_BASENAME = "core-image-meteo"
# Добавление приложения для получения данных с датчика CO2
IMAGE_INSTALL:append = " co2-sensor-daemon"
# Добавление приложения для получения данных с датчика температуры и влажности HTU21D
IMAGE_INSTALL:append = " htu21d-daemon"
# Добавление приложения реализующего GUI
IMAGE_INSTALL:append = " slint-gui liberation-fonts"
# Добавление приложения провайдера погоды
IMAGE_INSTALL:append = " weather-provider"
# Добавление MQTT брокера
IMAGE_INSTALL:append = " mosquitto"
# Установка таймзоны, серверов для синхронизации времени через NTP и скрипта для ожидания этой синхронизации
IMAGE_INSTALL:append = " ntp tzdata ntp-waiter"
# Другие компоненты, необходимые для работы приложений
IMAGE_FEATURES += "ssh-server-dropbear hwcodecs"
# Переменная используется для задания альтернативного источника видеопотока для отображения в блоке Outdoor live video
# Если не переопределять в local.conf, то будет использоваться значение по умолчанию из кода приложения
OUTDOOR_VIDEO_URI ?= "using-default-app-video-url"
set_global_env() {
# Создание глобального конфигурационного файла
mkdir -p ${IMAGE_ROOTFS}${sysconfdir}/profile.d
GLOBAL_ENV_FILE=${IMAGE_ROOTFS}${sysconfdir}/profile.d/set_global_env.sh
# Задание одной из частей MQTT topic
echo "export MQTT_DEVICE_NAME=${MACHINE}" > $GLOBAL_ENV_FILE
# Задается угол поворота GUI для Slint бэкэнда linux-kms
# https://releases.slint.dev/1.8.0/docs/slint/src/advanced/backend_linuxkms#display-rotation
echo "export SLINT_KMS_ROTATION=270" >> $GLOBAL_ENV_FILE
# Условное определение environment переменной, см. описание OUTDOOR_VIDEO_URI
if [ "${@bb.utils.contains('OUTDOOR_VIDEO_URI', 'using-default-app-video-url', '0', '1', d)}" = "1" ] ; then
echo "export VIDEO_URL=${OUTDOOR_VIDEO_URI}" >> $GLOBAL_ENV_FILE
fi
}
ROOTFS_POSTPROCESS_COMMAND += "set_global_env;"
# Конфигурация тачскрина для его поворота на нужный угол
# https://releases.slint.dev/1.8.0/docs/slint/src/advanced/backend_linuxkms#display-rotation
touch_rotate () {
echo 'ENV{LIBINPUT_CALIBRATION_MATRIX}="0 -1 1 1 0 0" # 90 degree clockwise' > ${IMAGE_ROOTFS}${sysconfdir}/udev/rules.d/libinput.rules
}
ROOTFS_POSTPROCESS_COMMAND += "touch_rotate;"
# Условная установка драйвера и других необходимых утилит для работы USB WiFi модуля RTL8821CU
IMAGE_INSTALL:append = "${@bb.utils.contains('DISTRO_FEATURES', 'rtl8821cu', ' rtl8821cu usb-modeswitch usb-modeswitch-data rfkill', '', d)}"
# Условная конфигурация автоматического подключения к точке доступа WiFi
# Параметры должны быть заданы в local.conf, при этом предполагается, что WIFI_PASS - это ключ сгенерированный wpa_passphrase, а не сам пароль.
WIFI_SSID ?= ""
WIFI_PASS ?= ""
wifi_config () {
# Конфигурирование параметров точки доступа для подключения
echo "network={" >> ${IMAGE_ROOTFS}${sysconfdir}/wpa_supplicant.conf
echo ' ssid="${WIFI_SSID}"' >> ${IMAGE_ROOTFS}${sysconfdir}/wpa_supplicant.conf
echo ' psk=${WIFI_PASS}' >> ${IMAGE_ROOTFS}${sysconfdir}/wpa_supplicant.conf
echo "} " >> ${IMAGE_ROOTFS}${sysconfdir}/wpa_supplicant.conf
INTERFACES_FILE=${IMAGE_ROOTFS}${sysconfdir}/network/interfaces
# Конфигурирование старта беспроводного интерфейса при запуске системы
if ! grep -q "^auto wlan0" "$INTERFACES_FILE"; then
sed -i "/^iface wlan0/i auto wlan0" $INTERFACES_FILE
fi
# Добавление задержки перед запуском интерфейса для завершения инициализации драйвера
if ! grep -q "^pre-up sleep 5" "$INTERFACES_FILE"; then
sed -i "/^iface wlan0/a pre-up sleep 5" $INTERFACES_FILE
fi
}
ROOTFS_POSTPROCESS_COMMAND += "${@bb.utils.contains('DISTRO_FEATURES', 'rtl8821cu', 'wifi_config;', '', d)}"
Рецепты для датчика CO2
recipes-kernel/linux/linux-mainline_%.bbappend
Рецепт патчит Device Tree для активации UART4 на выводах PG10, PG11 чипа, к которым подключается датчик (смотри схему соединений). Также в конфигурацию ядра добавлено включение драйвера микросхемы управления питанием AXP20x, т.к. по умолчанию в слое meta-sunxi этот драйвер отключен для cubieboard.
linux-mainline_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI:append:cubieboard = " file://0001-uart4.patch"
SRC_URI:append:cubieboard = " file://axp20x.cfg"
meta-meteo/recipes-python/python3-mhz19/python3-mhz19_0.1.bb
Рецепт для установки Python пакета для работы с датчиком mhz19. В рецепте существующий модуль от UedaTakeyuki пропатчен для избавления от зависимости на Raspberry Pi. В зависимости добавляются необходимые python модули, которые зачастую не совпадают с привычными из pip, их список пришлось определять эмпирически. Для управления сборкой и установкой Python пакетов в рецепте наследуется класс setuptools3, который учитывает зависимости из DEPENDS и RDEPENDS.
DESCRIPTION = "Python setuptools MH-Z19 CO2 sensor application"
SECTION = "examples"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "git://github.com/UedaTakeyuki/mh-z19;branch=master;protocol=https"
# Patch for delete Rpi platform dependences
SRC_URI:append = " file://0001-Deleted-Rpi-platform-dependences-and-PWM-mode.patch;patchdir=.."
SRCREV = "fd68b864460fbe1c44a3ac5af3f40d8ca9b64b26"
S = "${WORKDIR}/git/pypi"
RDEPENDS:${PN} += "python3 python3-setuptools python3-core python3-requests python3-pyserial \
python3-configargparse python3-datetime python3-json python3-bitstruct"
inherit setuptools3
meta-meteo/recipes-python/co2-sensor-daemon/co2-sensor-daemon_0.1.bb
Рецепт сервиса отправки данных датчика MQTT брокеру. Автостарт сервиса конфигурируется как для systemd, так и для sysvinit, используя классы systemd и update-rc.d. Шаблон конфигурации авто запуска сервиса для systemd добавлялся с целью использования в будущем, при необходимости более сложной схемы запуска сервисов, но пока на практике запуск через systemd использован не был.
Запись «${BPN}» при работе рецепта будет заменена на имя рецепта. В INITSCRIPT_PARAMS указан запуск сервиса в стандартных runlevels с приоритетом запуска/останова службы 31 — один из самых ранних приоритетов среди других сервисов/приложений метеостанции.
inherit systemd update-rc.d
SYSTEMD_SERVICE:${PN} = "${BPN}.service"
INITSCRIPT_NAME = "${BPN}"
INITSCRIPT_PARAMS = "defaults 31"
В рецепте также добавляется свой конфигурационный файл для задания параметров приложения, в частности задается адрес последовательного порта, на котором сидит датчик — /dev/ttyS4. Также в конфигурацию вынесена возможность отключения автоматической калибровки датчика.
DESCRIPTION = "Python application to read sensors data and send it to MQTT broker"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "git://github.com/R4scal/mhz19-mqtt-daemon;branch=master;protocol=https"
# Stop using unicode module and add setup serial device in config
SRC_URI:append = " file://0001-Stop-using-uniocode-module-add-setup-serial-device-a.patch"
# add custom config
SRC_URI:append = " file://config.ini"
# daemon startup configs for systemd and sysvinit. Tested only config for sysvinit.
SRC_URI:append = " file://${BPN}.service"
SRC_URI:append = " file://${BPN}.init"
SRCREV = "df32605ad81cb41a5df68567d914c1e701620218"
S = "${WORKDIR}/git"
RDEPENDS:${PN} += "python3 python3-setuptools python3-core python3-mhz19 python3-paho-mqtt \
python3-sdnotify python3-colorama"
inherit systemd update-rc.d
DAEMON_WDIR = "/opt/${BPN}"
do_install:append () {
rm -f config.ini.dist
install -d ${D}${DAEMON_WDIR}
install -m 0755 *.py ${D}${DAEMON_WDIR}/
install -m 0755 ../config.ini ${D}${DAEMON_WDIR}/
install -d ${D}${systemd_unitdir}/system/
install -m 0644 ../${BPN}.service ${D}${systemd_unitdir}/system/${BPN}.service
sed -i -e 's,@DMNWORKDIR@,${DAEMON_WDIR},g' \
-e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
${D}${systemd_unitdir}/system/${BPN}.service
install -d ${D}${sysconfdir}/init.d/
install -m 0755 ../${BPN}.init ${D}${sysconfdir}/init.d/${BPN}
sed -i -e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
-e 's,@SYSCONFDIR@,${sysconfdir},g' \
-e 's,@DMNWORKDIR@,${DAEMON_WDIR},g' \
${D}${sysconfdir}/init.d/${BPN}
}
FILES:${PN} += "${DAEMON_WDIR} ${systemd_unitdir}/system/${BPN}.service ${sysconfdir}/init.d"
SYSTEMD_SERVICE:${PN} = "${BPN}.service"
INITSCRIPT_NAME = "${BPN}"
INITSCRIPT_PARAMS = "defaults 31"
Рецепт для датчика температуры и влажности
meta-meteo/recipes-rust/htu21d-daemon/htu21d-daemon_0.1.0.bb
В рецепте для сборки Rust приложения используется класс cargo_bin из слоя meta-rust-bin.
Для подгрузки из сети cargo модулей во время компиляции необходимо дать разрешение использования сети для таска выполняющего компиляцию:
do_compile[network] = "1"
Сервис использует environment конфигурацию — переменная «HTU21D_INTERFACE» задает используемую шину I2C, к которой подключен датчик. Также задаются некоторые части MQTT topic. Переменные задаются из файла set_global_env.sh, который подключается к скрипту авто запуска.
setup_env_cmd = ". ${sysconfdir}/profile.d/set_global_env.sh"
sed -i -e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@SETUPENV@,${setup_env_cmd},g' \
${D}${sysconfdir}/init.d/${BPN}
Это временное решение, планируется сделать глобальный конфигурационный класс, в котором будут собраны все настраиваемые параметры метеостанции. Конфигурация автостарта сервиса реализована аналогично описанному ранее способу.
SUMMARY = "Rust daemon for TE HTU21D relative humidity and temperature sensor to get from and send data to MQTT broker"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
inherit cargo_bin
SRC_URI:append = " \
file://src \
file://Cargo.toml \
"
S = "${WORKDIR}"
do_compile[network] = "1"
# Configure startup
# daemon startup configs for systemd and sysvinit. Tested only config for sysvinit.
SRC_URI:append = " file://${BPN}.service"
SRC_URI:append = " file://${BPN}.init"
inherit systemd update-rc.d
setup_env_cmd = ". ${sysconfdir}/profile.d/set_global_env.sh"
do_install:append () {
install -d ${D}${systemd_unitdir}/system/
install -m 0644 ${S}/${BPN}.service ${D}${systemd_unitdir}/system/${BPN}.service
sed -i -e 's,@DMNWORKDIR@,${USRBINPATH},g' \
-e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
${D}${systemd_unitdir}/system/${BPN}.service
install -d ${D}${sysconfdir}/init.d/
install -m 0755 ${S}/${BPN}.init ${D}${sysconfdir}/init.d/${BPN}
sed -i -e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
-e 's,@SYSCONFDIR@,${sysconfdir},g' \
-e 's,@DMNWORKDIR@,${USRBINPATH},g' \
-e 's,@SETUPENV@,${setup_env_cmd},g' \
${D}${sysconfdir}/init.d/${BPN}
}
FILES:${PN} += "${systemd_unitdir}/system/${BPN}.service ${sysconfdir}/init.d"
SYSTEMD_SERVICE:${PN} = "${BPN}.service"
INITSCRIPT_NAME = "${BPN}"
INITSCRIPT_PARAMS = "defaults 35"
Рецепт для провайдера погоды
meta-meteo/recipes-rust/weather-provider/weather-provider_git.bb
Рецепт сборки сервиса поставщика данных о погоде, в том числе космической, в MQTT брокер. Написан на Rust. Исходный код выложен на GitHub https://github.com/TEPOTPOH/mqtt-weather-provider. Данные о космической погоде берутся из файла сводки с сайта swpc.noaa.gov. Детальный разбор кода приложения выходит за рамки данной статьи.
Аналогично рецепту для HTU21D, для сборки использует класс cargo_bin из слоя meta-rust-bin и требует такую же настройку для подгрузки модулей во время компиляции.
do_compile[network] = "1"
Сервис так же использует environment конфигурацию, в частности для задания периода получения исторических и мгновенных данных о индексе геомагнитной активности Кр.
"KP_RELEASE_INTERVAL_S", default = "600"
"KP_INST_INTERVAL_S", default = "300"
Т.к. приложение ходит в интернет по HTTPS, то потребовалась зависимость от openssl:
DEPENDS += "openssl"
Автостарт реализуется аналогично ранее описанному методу.
SUMMARY = "Fetch current weather and forecast and send it to MQTT broker"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
inherit cargo_bin
DEPENDS += "openssl"
SRCREV = "c9ad353b0ac35203bf38dfa18486fb927da218d0"
SRC_URI:append = " \
git://github.com/TEPOTPOH/mqtt-weather-provider.git;branch=main;protocol=https \
"
# Configure startup
# daemon startup configs for systemd and sysvinit. Tested only config for sysvinit.
SRC_URI:append = " file://${BPN}.service"
SRC_URI:append = " file://${BPN}.init"
S = "${WORKDIR}/git"
do_compile[network] = "1"
inherit systemd update-rc.d
setup_env_cmd = ". ${sysconfdir}/profile.d/set_global_env.sh"
do_install:append () {
install -d ${D}${systemd_unitdir}/system/
install -m 0644 ${WORKDIR}/${BPN}.service ${D}${systemd_unitdir}/system/${BPN}.service
sed -i -e 's,@DMNWORKDIR@,${USRBINPATH},g' \
-e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
${D}${systemd_unitdir}/system/${BPN}.service
install -d ${D}${sysconfdir}/init.d/
install -m 0755 ${WORKDIR}/${BPN}.init ${D}${sysconfdir}/init.d/${BPN}
sed -i -e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
-e 's,@SYSCONFDIR@,${sysconfdir},g' \
-e 's,@DMNWORKDIR@,${USRBINPATH},g' \
-e 's,@SETUPENV@,${setup_env_cmd},g' \
${D}${sysconfdir}/init.d/${BPN}
}
FILES:${PN} += "${systemd_unitdir}/system/${BPN}.service ${sysconfdir}/init.d"
SYSTEMD_SERVICE:${PN} = "${BPN}.service"
INITSCRIPT_NAME = "${BPN}"
INITSCRIPT_PARAMS = "defaults 55"
Рецепт для slint-gui
meta-meteo/recipes-rust/slint-gui/slint-gui_git.bb
Рецепт приложения реализующего графический интерфейс (GUI) на базе Slint на Rust. Детальный разбор данной программы выходит за рамки данной статьи. Исходный код размещен на GitHub https://github.com/TEPOTPOH/slint-meteo-gui.
Slint поддерживает несколько бэкэндов и библиотек рендеринга. По умолчанию включены сразу несколько, что сильно раздувает размер приложения, поэтому все остальные, кроме одного необходимого были отключены. Т.к. предполагалось, что графический интерфейс является только одним приложением (режим киоска), то был выбран бэкэнд на основе KMS, позволяющий отказаться от оконного менеджера. Библиотека рендеринга выбрана совместимой с данным бэкендом – FemtoVG.
Конфигурирование нужных features осуществляется заменой параметров в Cargo.toml до запуска сборки – т.е. после таска configure.
# Backend and renderer features for build
BACKEND_TYPE ?= '"backend-linuxkms-noseat"'
RENDER_TYPE ?= '"renderer-femtovg"'
do_configure:append() {
# replace default backend and renerer
sed -i -e 's,\"backend-winit\",${BACKEND_TYPE},g' \
-e 's,\"renderer-femtovg\",${RENDER_TYPE},g' ${S}/Cargo.toml
}
Для сборки Slint с linuxkms бэкэндом требуется подключение дополнительных библиотек. Т.к. у libgbm может быть несколько провайдеров, использован virtual/libgbm. Также, для работы с фреймворком Gstreamer, в зависимости сборки добавлен gstreamer и его базовые плагины включающие необходимые элементы видео пайплайна.
DEPENDS += "udev libxkbcommon libinput virtual/libgbm gstreamer1.0 gstreamer1.0-plugins-base"
Также для работы Slint и бэкэнда linux-kms добавлены необходимые runtime зависимости.
RDEPENDS:${PN} += "libudev libxkbcommon fontconfig gstreamer1.0-plugins-base"
Для поддержки аппаратного декодирования видео требуется Gstreamer плагин v4l2 из состава gstreamer-plugins-good и v4l2codecs из состава gstreamer-plugins-bad.
RDEPENDS:${PN} += "gstreamer1.0-plugins-good gstreamer1.0-plugins-bad"
Итоговый рецепт slint-gui_git.bb
SUMMARY = "GUI for meteostation based on Slint - Rust UI framework"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
inherit cargo_bin
# Backend and renderer features for build
BACKEND_TYPE ?= '"backend-linuxkms-noseat"'
RENDER_TYPE ?= '"renderer-femtovg"'
DEPENDS += "udev libxkbcommon libinput virtual/libgbm gstreamer1.0 gstreamer1.0-plugins-base"
SRCREV = "0c9506bb22ab50cedf83fa10552f9518f75d27f2"
SRC_URI:append = " \
git://github.com/TEPOTPOH/slint-meteo-gui.git;branch=main;protocol=https \
"
S = "${WORKDIR}/git"
do_compile[network] = "1"
# About dependencies: https://github.com/slint-ui/slint/blob/v1.4.1/docs/building.md
# For Linux a few additional packages beyond the usual build essentials are needed for development and running apps:
# - xcb (libxcb-shape0-dev libxcb-xfixes0-dev on debian based distributions)
# - xkbcommon (libxkbcommon-dev on debian based distributions)
# - fontconfig library (libfontconfig-dev on debian based distributions)
RDEPENDS:${PN} += "libudev libxkbcommon fontconfig gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad"
do_configure:append() {
# replace default backend and renerer
# NOTE: Make recipe cleaning if yocto returns error on Cargo.toml with doubling " symbols
sed -i -e 's,\"backend-winit\",${BACKEND_TYPE},g' \
-e 's,\"renderer-femtovg\",${RENDER_TYPE},g' ${S}/Cargo.toml
}
# Configure startup
# daemon startup configs for systemd and sysvinit. Tested only config for sysvinit.
SRC_URI:append = " file://${BPN}.service"
SRC_URI:append = " file://${BPN}.init"
inherit systemd update-rc.d
setup_env_cmd = ". ${sysconfdir}/profile.d/set_global_env.sh"
do_install:append () {
install -d ${D}${systemd_unitdir}/system/
install -m 0644 ${WORKDIR}/${BPN}.service ${D}${systemd_unitdir}/system/${BPN}.service
sed -i -e 's,@DMNWORKDIR@,${USRBINPATH},g' \
-e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
${D}${systemd_unitdir}/system/${BPN}.service
install -d ${D}${sysconfdir}/init.d/
install -m 0755 ${WORKDIR}/${BPN}.init ${D}${sysconfdir}/init.d/${BPN}
sed -i -e 's,@BINDIR@,${USRBINPATH},g' \
-e 's,@LOCALSTATEDIR@,${localstatedir},g' \
-e 's,@SYSCONFDIR@,${sysconfdir},g' \
-e 's,@DMNWORKDIR@,${USRBINPATH},g' \
-e 's,@SETUPENV@,${setup_env_cmd},g' \
${D}${sysconfdir}/init.d/${BPN}
}
FILES:${PN} += "${systemd_unitdir}/system/${BPN}.service ${sysconfdir}/init.d"
SYSTEMD_SERVICE:${PN} = "${BPN}.service"
INITSCRIPT_NAME = "${BPN}"
INITSCRIPT_PARAMS = "defaults 45"
Рецепты для включения аппаратного декодирования видео
meta-meteo/recipes-multimedia/gstreamer/gstreamer1.0-plugins-bad_%.bbappend
По умолчанию, плагин v4l2codecs из состава gstreamer-plugins-bad, необходимый для возможности аппаратного декодирования видео отключен из сборки, поэтому он включается явно.
PACKAGECONFIG:append = " v4l2codecs"
# for debugging pipline
PACKAGECONFIG:append = " kms gl"
recipes-kernel/linux/linux-mainline_%.bbappend
В конфигурацию ядра добавляется включение драйвера Cedrus для поддержки аппаратного декодирования видео. Также патчится расположение DMA пула видео ускорителя в DTS (подсмотрено в версии ядра с патчами Cedrus).
linux-mainline_%.bbappend
SRC_URI:append:cubieboard = " \
file://0001-Fixed-VE-DMA-memory-pool-range.patch \
file://cedar-support.cfg \
"
Другие рецепты
meta-meteo/recipes-connectivity/mosquitto/mosquitto_%.bbappend
mosquitto – локальный MQTT брокер. Рецепт заменяет дефолтный конфигурационный файл, в который добавляется номер порта, который будет слушать брокер и включение работы с анонимными клиентами.
mosquitto_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += "file://mosquitto.conf"
do_install:append() {
install -d ${D}${sysconfdir}/mosquitto
install -m 0644 ${WORKDIR}/mosquitto.conf \
${D}${sysconfdir}/mosquitto/mosquitto.conf
}
FILES:${PN}:append = " ${sysconfdir}/mosquitto/mosquitto.conf"
meta-meteo/recipes-support/ntp/ntp_%.bbappend
Рецепт добавляет конфигурационный файл с IP адресами NTP серверов для синхронизации.
ntp_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += "file://ntp.conf"
do_install:append() {
install -m 644 ${WORKDIR}/ntp.conf ${D}${sysconfdir}
}
meta-meteo/recipes-extended/timezone/tzdata.bbappend
tzdata — задает часовой пояс по умолчанию.
tzdata.bbappend
INSTALL_TIMEZONE_FILE = "1"
DEFAULT_TIMEZONE = "Europe/Moscow"
meta-meteo/recipes-support/ntp-waiter/ntp-waiter.bb
ntp-waiter – cкрипт для блокировки дальнейшей загрузки приложений при старте системы до выполнения синхронизации времени по NTP и начала работы соединения по HTTPS. Требуется для корректной работы приложений, использующих HTTPS соединение.
meta-meteo/recipes-core/psplash/psplash_%.bbappend
psplash – заменяет стандартное изображение при старте системы на свое с логотипами используемых фреймворков.
psplash_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SPLASH_IMAGES = "file://startup_logo.png;outsuffix=default
meta-meteo/recipes-core/base-files/base-files_%.bbappend
base-files – поворачивает логотип при старте системы на 90 градусов для вертикальной ориентации. Для этого в /etc/ добавляется файл rotation, в котором указан угол, на который нужно поворачивать логотип. Этот файл анализирует программа отображения логотипа. Установку в систему этого файла выполняет основной рецепт, тут же в bbappend только указывается приоритетное место поиска измененного файла rotation.
base-files_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
Сборка и установка
Подготовка проекта к сборке
Загрузка слоев
~/$ mkdir yocto
~/$ cd yocto
~/$ mkdir my_layers
~/$ cd my_layers
~/yocto/my_layers$ git clone -b main https://github.com/TEPOTPOH/yocto-sunxi-meteo meta-meteo
Cloning into 'meta-meteo'...
~/yocto/my_layers$ git clone -b master https://github.com/rust-embedded/meta-rust-bin
Cloning into 'meta-rust-bin'...
~/yocto/my_layers$ git clone -b master https://github.com/EmbeddedAndroid/meta-rtlwifi
Cloning into 'meta-rtlwifi'...
~/$ cd ..
~/yocto$ git clone -b kirkstone git://git.yoctoproject.org/poky
Cloning into 'poky'...
~/yocto$ cd poky/
~/yocto/poky$ git clone -b kirkstone git://git.openembedded.org/meta-openembedded
Cloning into 'meta-openembedded'...
~/yocto/poky$ git clone -b kirkstone https://github.com/linux-sunxi/meta-sunxi
Cloning into 'meta-sunxi'...
~/yocto/poky$ source oe-init-build-env ../meteo-build
You had no conf/local.conf file. This configuration file has therefore been
created for you with some default values. You may wish to edit it to, for
example, select a different MACHINE (target hardware). See conf/local.conf
for more information as common configuration options are commented.
...
~/yocto/meteo-build$
Подключение слоев
Добавляем в meteo-build/conf/bblayers.conf полные пути к слоям.
BBLAYERS ?= " \
/home/user/yocto/poky/meta \
/home/user/yocto/poky/meta-poky \
/home/user/yocto/poky/meta-yocto-bsp \
/home/user/yocto/poky/meta-sunxi \
/home/user/yocto/poky/meta-openembedded/meta-oe \
/home/user/yocto/poky/meta-openembedded/meta-python \
/home/user/yocto/poky/meta-openembedded/meta-networking \
/home/user/yocto/my_layers/meta-rust-bin \
/home/user/yocto/my_layers/meta-meteo \
/home/user/yocto/my_layers/meta-rtlwifi \
"
Настройка локальной конфигурации сборки local.conf
Добавляем в meteo-build/conf/local.conf
MACHINE ??= "cubieboard"
MACHINEOVERRIDES .= ":use-mailine-graphics"
DISTRO = "cubieboard-meteo"
LICENSE_FLAGS_ACCEPTED = "commercial license"
Здесь включается использование open-source mainline графического драйвера «Lima» для ускорителя Mali, т.к. он потенциально стабильней и должен иметь больше возможностей.
When using mainline kernel >5.2 it is now possible to use the mainline graphics drivers lima and panfrost, instead of the mali driver provided by ARM.
MACHINEOVERRIDES .= ":use-mailine-graphics"
Для сборки образа с поддержкой USB WiFi RTL8821CU нужно использовать дистрибутив «cubieboard-meteo-rtl8821cu». Если поддержка такого модуля не нужна, то использовать «cubieboard-meteo».
DISTRO = "cubieboard-meteo"
Задать точку доступа WiFi для подключения можно через переменные WIFI_SSID и WIFI_PASS.
Для задания другого источника outdoor видео можно использовать переменную OUTDOOR_VIDEO_URI.
Итоговый local.conf
MACHINE ??= "cubieboard"
MACHINEOVERRIDES .= ":use-mailine-graphics"
DISTRO = "cubieboard-meteo"
LICENSE_FLAGS_ACCEPTED = "commercial license"
# optional settings, depends on build hardware
BB_NUMBER_THREADS = "4"
BB_NUMBER_PARSE_THREADS = "8"
PARALLEL_MAKE = "-j 2"
PARALLEL_MAKEINST = "-j 2"
# lower disk space
INHERIT += "rm_work"
Сборка
Если окружение уже было настроено командой «source oe-init-build-env», то запускаем сборку.
~/yocto/meteo-build$ bitbake core-image-meteo
Если терминал закрывался, то перед сборкой заново настроить окружение.
cd poky
source oe-init-build-env ../meteo-build
Установка на SD карту
Подключить к компьютеру кардридер с microSD картой объемом не менее 512 Мбайт. Записать образ на карту.
sudo dd bs=4M if=<path to build direcotry>/tmp/deploy/images/cubieboard/core-image-meteo-cubieboard.sunxi-sdimg of=/dev/<SD card device name> status=progress
sudo sync /dev/<SD card device name>
Запуск
Включить питание дисплея, затем включить питание основной платы.
Изображения процесса загрузки и примеры работы метеостанции приведены ниже.
Заключение
В результате работы создана домашняя метеостанция с прошивкой на основе Yocto Linux. Создан слой реализующий метеостанцию и рецепты для приложений. Кастомизированы необходимые существующие рецепты. Ключевые приложения написаны на языке Rust, а графический интерфейс реализован на базе фреймворка Slint. Для воспроизведения видео создан Gstreamer пайплайн поддерживающий аппаратное декодирование видео.
По итогу получен практический опыт работы с Yocto Linux, Rust, Slint, Gstreamer.
Планы
Доработка сборки образа:
- внедрить глобальный конфигурационный класс для возможности конфигурирования всех приложений метеостанции в одном месте;
- перейти на использование менеджера загрузки systemd вместо sysvinit, т.к. systemd позволяет загружать систему быстрее и намного более гибко;
- выделить аппаратные зависимости из слоя meta-meteo в отдельный слой для возможности использования слоя meta-meteo с другими платформами.
Добавить датчики:
- датчик качества воздуха TVOC;
- датчик мелких частиц PM2.5;
- средний уровень шума, Дб;
- удаленный датчик температуры снаружи;
- датчик радиации.
Погода текущая (Windy):
- температура, облачность, осадки.
Прогноз погоды на 7 дней:
- температура, облачность, осадки.
Текущие данные о осадках с сервиса Яндекс.Погода – отрисовка карты осадков.
Видео с веб-камер:
- переключение источников видео по списку;
- увеличение производительности при отображении видео (сейчас FPS меньше 10 кадров в секунду);
- автоматическое переключение на источник, ассоциированный с северным сиянием, при детектировании достаточных погодных условий.
Возможность полного отключения дисплея на ночь.
Меню настроек.
Литература/источники
Yocto Project: www.yoctoproject.org
- Yocto-doc одной страницей: https://docs.yoctoproject.org/singleindex.html;
- Список релизов: https://wiki.yoctoproject.org/wiki/Releaseshttps://wiki.yoctoproject.org/wiki/Releases;
- BitBake-doc одной страницей: https://docs.yoctoproject.org/bitbake/dev/singleindex.html;
- Python modules: https://hub.mender.io/t/how-to-work-with-python-applications-and-modules-in-yocto-project/1135.
meta-rust-bin
- https://github.com/rust-embedded/meta-rust-bin;
- Build Rust:
— https://interrupt.memfault.com/blog/rust-in-yocto;
— https://mender.io/blog/building-the-mender-rust-project-with-yocto.
OpenEmbedded: www.openembedded.org:
- Список слоёв: layers.openembedded.org/layerindex/;
- poky: https://git.yoctoproject.org/poky/;
- meta-openembedded: http://cgit.openembedded.org/meta-openembedded/.
Cubieboard:
- Спецификация Cubieboard: http://docs.cubieboard.org/products/start#cubieboard1;
- Различные сборки: http://docs.cubieboard.org/tutorials/cb1/start;
- Краткая документация на Cubieboard: https://linux-sunxi.org/Cubietech_Cubieboard;
- Yocto слой meta-sunxi: https://github.com/linux-sunxi/meta-sunxi;
- AXP209: https://linux-sunxi.org/AXP209/PMIC_control_Linux;
- Cubieboard pinout (совпадает с Cubieboard2): http://docs.cubieboard.org/cubieboard1_and_cubieboard2_gpio_pin;
- Патч DTS для включения UART4: https://github.com/armbian/sunxi-DT-overlays/blob/master/sun4i-a10/sun4i-a10-uart4.dts;
- Драйвер для аппаратного ускорения видео: https://linux-sunxi.org/Sunxi-Cedrus.
Mesa 3D Graphics Library:
- Lima драйвер для GPU Mali-400: https://docs.mesa3d.org/drivers/lima.html.
MHZ19B:
- Подробное пользовательское описание http://emariete.com/en/sensor-co2-mh-z19b/;
- Как отличить подделку https://emariete.com/en/sensors-co2-mh-z19b-false/;
- Подробное пользовательское описание https://revspace.nl/MH-Z19B;
- MQTT демон: https://github.com/R4scal/mhz19-mqtt-daemon;
- Драйвер: https://github.com/UedaTakeyuki/mh-z19/.
HTU21D:
- Драйвер: https://github.com/jdeeny/htu21d-rs;
- Datasheet: https://cdn-shop.adafruit.com/datasheets/1899_HTU21D.pdf.
Mosquitto MQTT broker:
- Документация: https://mosquitto.org/documentation/;
- GitHub: https://github.com/eclipse/mosquitto;
- Конфигурирование: https://randomnerdtutorials.com/how-to-install-mosquitto-broker-on-raspberry-pi/.
Linux:
- start-stop-daemon: https://www.man7.org/linux/man-pages/man8/start-stop-daemon.8.html.
Slint GUI:
- Релизы: https://releases.slint.dev/;
- Slint Reference: https://docs.slint.dev/latest/docs/slint/;
- Rust API: https://docs.slint.dev/latest/docs/rust/slint/;
- Backends: https://releases.slint.dev/1.7.1/docs/slint/src/advanced/backends_and_renderers.
Gstreamer:
- Tutorial: https://gstreamer.freedesktop.org/documentation/tutorials/index.html?gi-language=c;
- Plugins: https://gstreamer.freedesktop.org/documentation/plugins_doc.html?gi-language=c;
- Пример использования в Slint: https://github.com/slint-ui/slint/tree/master/examples/gstreamer-player.
USB WiFi/bluetooth RTL8821CU:
- Yocto слой с драйверами: https://github.com/EmbeddedAndroid/meta-rtlwifi.
LCD дисплей Wisecoco 7 inch 1024×600:
- Магазин ozon