OpenSTLinux dla procesorów z rodziny STM32MP1 (5)

OpenSTLinux dla procesorów z rodziny STM32MP1 (5)

Do tej pory omawialiśmy budowanie aplikacji przy użyciu pakietu SDK. Tym razem zapoznamy się z możliwością włączenia kompilacji naszej aplikacji do procesu budowania obrazu systemu. W kolejnej części artykułu omówimy sposób utworzenia prostego serwisu webowego, dzięki któremu będziemy mogli skomunikować się z modułem VisionSOM-STM32MP1 za pomocą przeglądarki internetowej.

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.

Rysunek 1. Struktura plików nowej receptury

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.

Listing 1. Zawartość pliku receptury – somlabs-example.bb

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}.

Listing 2. Kod programu somlabs-example.c

#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.

Listing 3. Kod serwisu somlabs-example.service

[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

Rysunek 2. Katalog layers po pobraniu dodatkowych meta-warstw

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

Listing 4. Lista pakietów w pliku st-image-weston.bbappend

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.

Rysunek 3. Lista nowych warstw w pliku bblayers.conf

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.

Rysunek 4. Dostępne interfejsy sieciowe modułu VisionSOM-STM32MP1

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.

Rysunek 5. Domyślna strona serwera Apache2

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.

Listing 5. Kod aplikacji używającej python-flask

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.

Rysunek 6. Uruchomienie aplikacji na serwerze testowym

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.

Rysunek 7. Interfejs aplikacji widoczny w przeglądarce

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.

Listing 6. Plik konfiguracyjny dla serwera Apache2

<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.

Listing 7. Plik flask_example.wsgi wymagany przez proces WSGI

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.

Listing 8. Zawartość pliku 99-leds.rules

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

Artykuł ukazał się w
Elektronika Praktyczna
kwiecień 2021

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik maj 2021

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio czerwiec 2021

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka Podzespoły Aplikacje maj 2021

Automatyka Podzespoły Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna maj 2021

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich czerwiec 2021

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów