Dodanie nowego komponentu do tworzonego obrazu systemu wymaga utworzenia dla niego pliku receptury oraz dołączenia wszystkich niezbędnych plików. Pliki receptur (*.bb) zawierają szczegółowe instrukcje na temat lokalizacji źródeł, sposobu ich kompilacji oraz instalacji w finalnym systemie plików. W zaprezentowanym przykładzie użyjemy kodu aplikacji z pierwszej części cyklu, który zostanie skompilowany i zainstalowany zgodnie z instrukcjami zawartymi w recepturze. Dodatkowo skonfigurujemy serwis systemd, uruchamiający naszą aplikację automatycznie przy starcie systemu.
Przygotowanie nowej receptury
Na początku musimy utworzyć odpowiednią strukturę plików w warstwie meta-somlabs, która została pokazana na rysunku 1.
W katalogu recipes-somlabs, w którym domyślnie znajduje się receptura aplikacji demo dodajemy nowy katalog o nazwie somlabs-example. W tym katalogu będą umieszczone wszystkie źródła niezbędne do zbudowania naszej nowej aplikacji.
DESCRIPTION = "Example application"
LICENSE = "BSD-3-Clause"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/BSD-3-Clause;md5=550794465ba0ec5312d6919e203a55f9"
SRC_URI = " \
file://somlabs-example.c \
file://somlabs-example.service \
"
inherit systemd
SYSTEMD_AUTO_ENABLE = "enable"
SYSTEMD_SERVICE_${PN} = "somlabs-example.service"
S = "${WORKDIR}"
do_compile() {
${CC} ${LDFLAGS} somlabs-example.c -o somlabs-example
}
do_install() {
install -d ${D}/usr/bin/
install -m 0755 somlabs-example ${D}/usr/bin/
install -d ${D}/${systemd_unitdir}/system
install -m 0644 ${WORKDIR}/somlabs-example.service ${D}/${systemd_unitdir}/system/
}
FILES_${PN} = " /usr/bin/somlabs-example ${systemd_unitdir}/system/somlabs-example.service"
W katalogu tym tworzymy plik receptury - somlabs-example.bb, zawierający instrukcje dotyczące kompilacji i instalacji programu. Jego zawartość została pokazana na listingu 1.
Za nagłówkiem zawierającym opis i informacje licencyjne, musimy podać listę wszystkich źródeł używanych podczas budowania. Muszą się na niej znaleźć zarówno źródła używane podczas kompilacji (somlabs-example.c), jak i wszystkie skrypty instalowane w docelowym systemie plików. W naszym przypadku, jest to plik serwisu somlabs-example.service odpowiedzialny za start aplikacji podczas uruchamiania systemu. Automatyczne uruchamianie aplikacji jest zadaniem menedżera systemu – systemd, dlatego nasza receptura musi dziedziczyć po klasie systemd.bbclass, dzięki czemu konfiguracja serwisu zostanie wykonana podczas budowania obrazu systemu. Jest ona wykonywana na bazie zmiennych SYSTEMD_AUTO_ENABLE i SYSTEMD_SERVICE_${PN} zawierających odpowiednio informacje o tym czy serwis ma być domyślnie włączony i jaka jest jego nazwa (musi być ona zgodna z instalowanym plikiem serwisu z listy źródeł).
W kolejnych liniach przechodzimy już do procesu kompilacji. Na początku, w zmiennej S definiujemy lokalizację źródeł dla kompilatora. Możemy tutaj użyć zmiennej środowiskowej WORKSPACE, która jest automatycznie ustawiana na podkatalog receptury identyczny z jej nazwą (somlabs-example). Proces kompilacji jest definiowany w funkcji do_compile, w której umieszczamy polecenie analogiczne do tego z pierwszego odcinka cyklu, gdzie używaliśmy kompilatora z SDK.
Po kompilacji wywoływana jest jeszcze funkcja do_install, która umieszcza pliki w obrazie docelowego systemu. Kopiujemy więc plik wyjściowy z kompilacji do katalogu /usr/bin oraz plik serwisu do /etc/systemd/system. Warto zwrócić uwagę na to, że proces budowania definiuje zmienne opisujące używane katalogi systemowe, np. systemd_unitdir, dzięki czemu nie trzeba podawać ich ścieżek bezwzględnych. Dodatkowo, ścieżka do docelowego systemu plików znajduje się w zmiennej D. Na samym końcu receptury powinniśmy podać jeszcze listę zainstalowanych przez nas plików i katalogów, uzupełniając odpowiednią zmienną – FILES_${PN}.
#include <fcntl.h>
#include <unistd.h>
#define LED_PATH "/sys/class/leds/led3/brightness"
#define LED_BLINKS 2
int main(void) {
int fd;
fd = open(LED_PATH, O_WRONLY);
for(int i=0; i<LED_BLINKS; i++) {
write(fd, "1", 1);
sleep(1);
write(fd, "0", 1);
sleep(1);
}
close(fd);
return 0;
}
Kod programu somlabs-example znajduje się na listingu 2. Jego zadaniem jest dwukrotne zaświecenie diodą LED3 podłączoną do wyprowadzenia PG12. Program korzysta z pliku brightness tworzonego przez sterownik LED jądra Linuxa.
[Unit]
Description=Example application
[Service]
ExecStart=/usr/bin/somlabs-example
[Install]
WantedBy=multi-user.target
Na koniec zobaczymy jeszcze w jaki sposób zdefiniowany jest serwis uruchamiający aplikację. Znajduje się on na listingu 3 i został utworzony zgodnie z dokumentacją dostępną na stronie https://bit.ly/2PSrU4E. Znajduje się w nim ogólny opis, program uruchamiany przy starcie serwisu oraz moment uruchomienia serwisu – multi-user.target oznacza, że jest już gotowa niegraficzna powłoka systemowa.
Nasza receptura jest w tym momencie gotowa, ale nie zostanie zbudowana, dopóki nie znajdzie się na liście wymaganych pakietów. Możemy to zrobić modyfikując plik recipes-st/images/st-image-weston.bbappend w warstwie meta-somlabs i dopisując pakiet somlabs-example do listy IMAGE_INSTALL. Po zbudowaniu i uruchomieniu systemu program somlabs-example zostanie uruchomiony automatycznie, o czym będzie świadczyć dwukrotne zaświecenie diody LEDS3.
Instalacja Python Flask i serwera Apache2
W tej części omówimy krótko możliwość zrobienia prostej aplikacji webowej, dzięki której będzie można sterować diodami znajdującymi się na płytce bazowej VisionCB-STD-STM32MP1. Będziemy do tego potrzebować dodatkowych pakietów zainstalowanych w OpenSTLinuxie – frameworka python-flask i jednego z dostępnych serwerów HTTP implementujących interfejs WSGI. W naszym przykładzie użyjemy serwera Apache2 z modułem mod_wsgi, dzięki któremu nasz serwer będzie mógł obsługiwać aplikacje webowe napisane w języku Python.
Receptura budowania pakietu Apache2 znajduje się w warstwie meta-openembedded/meta-webserver, która jest wśród pobranych przez nas źródeł projektu OpenSTLinux, jednak moduł mod_wsgi wymaga od nas pobrania dodatkowych meta-warstw: meta-cloud-services i meta-virtualization. Pobieramy je do katalogu layers za pomocą następujących poleceń, pamiętając o tym, że musimy za każdym razem trzymać się wersji dunfell:
git clone git://git.yoctoproject.org/meta-virtualization -b dunfell
git clone git://git.yoctoproject.org/meta-cloud-services -b dunfell
Po pobraniu dodatkowych plików, nasz katalog layers powinien wyglądać tak, jak na rysunku 2.
Do poprawnej kompilacji potrzebujemy także brakującego pliku python3targetconfig.bbclass, który możemy pobrać z repozytorium openembedded do katalogu classes w warstwie meta-somlabs:
mkdir -p meta-st/meta-somlabs/classes
cd meta-st/meta-somlabs/classes
wget https://raw.githubusercontent.com/openembedded/openembedded-core/dunfell/meta/classes/python3targetconfig.bbclass
IMAGE_INSTALL += " \
qtbase-dev \
qtbase-mkspecs \
qtbase-tools \
qtdeclarative-qmlplugins \
qtquickcontrols2-qmlplugins \
qtwayland \
gstreamer1.0 \
gstreamer1.0-plugins-good \
somlabs-example \
apache2 \
mod-wsgi \
python3-flask \
"
Wszystkie dodatkowe pakiety możemy teraz dopisać do listy w pliku recipes-st/images/st-image-weston.bbappend znajdującym się w warstwie meta-somlabs. Zmodyfikowana lista IMAGE_INSTALL została pokazana na listingu 4. Dodanie wymienionych pakietów wymaga on nas, także zwiększenia rozmiaru partycji. Domyślna wartość 746 MB jest zdefiniowana w pliku conf/machine/include/st-machine-common-stm32mp.inc warstwy meta-st-stm32mp. Możemy tą wartość modyfikować bezpośrednio, lub korzystając z pliku maszyny stm32mp157a-visionsom-rgb-sd-mx.conf. Drugie rozwiązanie jest lepsze, ponieważ wszystkie nasze zmiany będą dotyczyły wyłącznie warstwy meta-somlabs, dlatego dopisujemy linijkę IMAGE_ROOTFS_MAXSIZE = "1048576" na końcu pliku conf/machine/stm32mp157a-visionsom-rgb-sd-mx.conf w warstwie meta-somlabs. W ten sposób ustalimy wielkość partycji systemowej na 1 GB, co umożliwi instalację wszystkich wymaganych pakietów.
Ostatnim krokiem przed rozpoczęciem kompilacji jest dodanie nowych warstw do pliku build-openstlinuxweston-stm32mp157a-visionsom-rgb-sd-mx/conf/bblayers.conf, tak jak na rysunku 3. Możemy teraz zbudować nowy obraz systemu i przetestować działanie serwera Apache2.
Po uruchomieniu nowej wersji systemu musimy podłączyć urządzenie do sieci. Możemy to zrobić za pośrednictwem interfejsów eth0, lub wlan0, dzięki czemu urządzenie będzie widoczne w sieci lokalnej. Alternatywnie możemy również skorzystać z portu USB-OTG, ponieważ w OpenSTLinux jest on domyślnie skonfigurowany jako Ethernet-over-USB. Dodatkowo widoczny w systemie interfejs usb0 ma skonfigurowany serwer DHCP, dzięki czemu komputer, do którego podłączymy urządzenie otrzyma automatycznie adres IP i będzie mógł się komunikować z modułem VisionSOM tak samo jak w sieci lokalnej. Wszystkie dostępne interfejsy sieciowe oraz przydzielone im adresy IP możemy zobaczyć wywołując polecenie ip a w terminalu szeregowym, co widać na rysunku 4.
Kiedy zdecydujemy się na metodę połączenia i poznamy adres IP naszego urządzenia możemy go wpisać w przeglądarkę, aby sprawdzić, czy serwer Apache2 jest uruchomiony. Jeżeli wszystko zostało zainstalowane poprawnie to w oknie przeglądarki zobaczymy komunikat "It works!", tak jak na rysunku 5. Możemy teraz przystąpić do implementacji aplikacji.
Tworzenie aplikacji webowej
Dzięki frameworkowi python-flask implementacja aplikacji sprowadza się do napisania kodu obsługującego dostęp do poszczególnych adresów URL. W odpowiedzi na zapytanie przeglądarki serwer odpowie kodem HTML zwróconym przez aplikację. Jej kod znajduje się na listingu 5.
from flask import Flask
from os import system
app = Flask(__name__)
page = ‘\
<form action="/turnon"><button>Turn On</button></form> \
<form action="/turnoff"><button>Turn Off</button></form> \
‘
@app.route(‘/’)
def main():
return page
@app.route(‘/turnon’)
def turn_on():
system(‘echo 1 > /sys/class/leds/led3/brightness’)
return page
@app.route(‘/turnoff’)
def turn_off():
system(‘echo 0 > /sys/class/leds/led3/brightness’)
return page
Na początku importujemy niezbędne biblioteki i tworzymy obiekt app reprezentujący aplikację. Następnie implementujemy obsługę poszczególnych adresów URL. Bazowym adresem w naszym przykładzie jest "/", który będzie odpowiadał wpisaniu w przeglądarce adresu IP. W odpowiedzi na takie zapytanie zwracamy kod, w którym znajdują się dwa przyciski służące do włączania i wyłączania diody na płytce bazowej. Aby uniknąć przepisywania go wielokrotnie został on umieszczony w zmiennej page.
Wciśnięcie każdego z przycisków prowadzi do innego adresu URL: /turnon i /turnoff. Ich obsługa również została zaimplementowana w aplikacji – w zależności od adresu włączamy, lub wyłączamy diodę oraz zwracamy ten sam kod HTML w każdym przypadku. Wszystkie zapytania od przeglądarki w tym przykładzie są typu GET, dlatego nie odczytujemy z nich żadnych dodatkowych danych – wystarczy nam żądany adres URL.
Po zapisaniu kodu do pliku flask_example.py możemy uruchomić naszą aplikację za pomocą polecenia
FLASK_APP=flask_example.py python3 -m flask run –host=0.0.0.0.
Efekt jego wywołania możemy zobaczyć na rysunku 6. Do aplikacji możemy się dostać wpisując w przeglądarce komputera znajdującego się w sieci lokalnej adres IP urządzenia z portem 5000, tak jak na rysunku 7. Dodatkowo w terminalu, w którym uruchomiliśmy aplikację zobaczymy logi informujące o dostępie do poszczególnych adresów URL.
Konfiguracja serwera Apache2
Polecenie, które wywołaliśmy w poprzednim rozdziale uruchomiło naszą aplikację na prostym serwerze testowym, o czym informuje nas ostrzeżenie wyświetlane w logach na rysunku 6. Z tego względu powinniśmy umieścić nasz serwis na jednym z dostępnych serwerów produkcyjnych – w naszym przykładzie będzie to Apache2, który zdążyliśmy już zainstalować.
Pliki konfiguracyjne serwera znajdują się w katalogu /etc/apache2. Naszą konfigurację dodamy w podkatalogu conf.d, tworząc tam plik flask_example.conf, pokazany na listingu 6.
<VirtualHost *>
WSGIDaemonProcess flask_example user=som group=som threads=5
WSGIScriptAlias / /usr/share/apache2/flask_example/flask_example.wsgi
<Directory /usr/share/apache2/flask_example>
WSGIProcessGroup flask_example
WSGIApplicationGroup %{GLOBAL}
Require all granted
</Directory>
</VirtualHost>
Definiujemy w nim proces odpowiedzialny za interfejs WSGI, dostępny na wszystkich przydzielonych do urządzenia adresach IP. Proces ten zostanie uruchomiony z poziomu użytkownika som i grupy som i będzie umożliwiał dostęp do katalogu /usr/share/apache2/flask_example/. Ponieważ w systemie OpenSTLinux domyślnie znajduje się wyłącznie użytkownik root, musimy utworzyć nowego użytkownika wraz z grupą i przypisać im prawa do wspomnianego katalogu:
useradd -m som
passwd som
mkdir /usr/share/apache2/flask_example
chown som /usr/share/apache2/flask_example
chgrp som /usr/share/apache2/flask_example
W katalogu /usr/share/apache2/flask_example musimy umieścić naszą aplikację z listingu 5. (kod musi być zapisany w pliku flask_example.py) oraz plik flask_example.wsgi wymagany przez proces serwera. Ostatni z plików został pokazany na listingu 7. Dodajemy w nim nową ścieżkę do poszukiwania importowanych modułów Pythona oraz importujemy obiekt aplikacji, który utworzyliśmy w pliku flask_example.py. Możemy także zrezygnować z podawania ścieżki, ale wówczas będziemy musieli umieścić plik flask_example.py razem z innymi modułami Pythona widocznymi globalnie w systemie.
import sys
sys.path.insert(0, ‘/usr/share/apache2/flask_example’)
from flask_example import app as application
Serwer oraz aplikacja są już gotowe do uruchomienia, jednak pozostał nam do rozwiązania ostatni problem. Uruchomienie aplikacji z poziomu użytkownika som uniemożliwi jej dostęp do pliku brightness diody. Rozwiążemy to przez utworzenie nowej grupy – led, która będzie miała dostęp do plików sterownika i dodanie do niej użytkownika som:
groupadd led
usermod -a -G led som
Teraz pozostało już tylko dodanie reguły dla menedżera urządzeń udev, aby po utworzeniu plików urządzeń w /sys/class/leds, zmienił im grupę i dodał jej prawo do zapisu. Możemy to zrobić dodając plik 99-leds.rules do katalogu /etc/udev/rules.d/, skąd zostanie odczytany przez udev podczas rozruchu systemu. Zawartość pliku znajduje się na listingu 8.
ACTION=="add", SUBSYSTEM=="leds", RUN+="/bin/chgrp -R led /sys%p", RUN+="/bin/chmod -R g+w /sys%p"
Po restarcie systemu, serwer Apache2 automatycznie uruchomi naszą aplikację, do której dostaniemy się wpisując adres IP urządzenia, tym razem bez podawania portu. Jeżeli poprawnie dodaliśmy wspomniane reguły udev, to będziemy mogli zapalać i gasić diodę LED3 za pomocą przycisków w przeglądarce.
Krzysztof Chojnowski