14.2.2.1. Zamrażanie skryptów w oprogramowaniu układowym¶
Moduł zamrożony (frozen) to plik .py skompilowany do kodu bajtowego i wkompilowany w obraz oprogramowania układowego na etapie budowania. Środowisko uruchomieniowe importuje zamrożony moduł bezpośrednio z pamięci flash, w ogóle nie sięgając do systemu plików na dysku. W przypadku gotowego produktu jest to właściwe miejsce dla kodu aplikacji: użytkownik końcowy nie ma czego usuwać, żaden nieaktualny plik .py na karcie SD niczego nie nadpisze, a kamera przy każdym uruchomieniu wykonuje ten sam kod, niezależnie od tego, co (jeśli cokolwiek) znajduje się na jej dyskach.
Ta strona omawia sekwencję rozruchu, którą wykonuje kamera, a następnie sposób, w jaki manifest.py oraz dyrektywa freeze wkompilowują aplikację w build.
14.2.2.1.1. Sekwencja rozruchu¶
Co i kiedy uruchamia się w kamerze wychodzącej z resetu:
Bootloader. Po włączeniu zasilania następuje krótkie okno DFU, którego IDE używa do wysyłania aktualizacji oprogramowania układowego. Okno zamyka się po kilku sekundach, a bootloader przekazuje sterowanie do MicroPython. Działający skrypt może na żądanie ponownie wejść w to okno, wywołując
machine.bootloader().Inicjalizacja zamrożonego systemu plików. Zanim uruchomi się jakikolwiek kod aplikacji, środowisko uruchomieniowe uruchamia systemy plików. Wewnętrzna pamięć flash jest montowana w
/flash(i formatowana na pusto, jeśli nic tam nie ma). Jeśli obecna jest karta SD oraz na wewnętrznej pamięci flash nie istnieje plik znacznikowy o nazwieSKIPSD, karta SD jest montowana w/sdcard. ROMFS, gdy build go zawiera, jest montowany automatycznie w/rom. Katalog roboczy jest ustawiany na katalog rozruchowy (/sdcard, jeśli karta została zamontowana, w przeciwnym razie/flash), asys.pathjest wypełniana wpisami/flash,/flash/lib,/sdcard,/sdcard/lib,/romoraz/rom/lib. Konfiguracją rezydującą w pamięci flash zajmuje się zamrożony moduł o nazwie_boot.py– infrastruktura portu i płytki, a nie punkt zaczepienia dla aplikacji. Aplikacje nie modyfikują_boot.py; robi to build. Umieszczenie plikuSKIPSDw pamięci flash z poziomu IDE to wspierany sposób, aby kamera uruchamiała się z wewnętrznej pamięci flash zamiast z karty SD.Konfiguracja przed REPL.
boot.pyuruchamia się przy każdym miękkim resecie – zimnym rozruchu,Ctrl-Dz REPL, zakończeniu działania skryptu oraz odzyskiwaniu po zadziałaniu watchdoga – zanim REPL stanie się osiągalny. Jego zadaniem jest przygotowanie środowiska, w którym działa reszta systemu: rodzaj konfiguracji, którą REPL, aplikacja oraz wszelkie narzędzia odzyskiwania muszą mieć przygotowaną, aby działać. Nie jest to miejsce, w którym żyje sama aplikacja.main.pyjest punktem wejścia aplikacji.Pętla główna.
main.pyto pętla główna aplikacji. Uruchamia się raz przy zimnym rozruchu, bezpośrednio poboot.py. Nie jest uruchamiana ponownie przy kolejnych miękkich resetach – kamera przechodzi wtedy do REPL. Ta asymetria ma znaczenie podczas tworzenia oprogramowania (Ctrl-D przechodzi do REPL bez ponownego uruchamiania pętli, dzięki czemu programista może zbadać stan), ale nie w produkcji: kamera w terenie doświadcza włączenia zasilania, zadziałania watchdoga oraz twardych resetów, które wszystkie są resetami sprzętowymi ponownie wchodzącymi na ścieżkę zimnego rozruchu i ponownie uruchamiającymimain.py.
14.2.2.1.2. Zamrażanie w oprogramowaniu układowym¶
Zestaw zamrożonych modułów płytki jest deklarowany w pliku boards/<TARGET>/manifest.py w drzewie oprogramowania układowego. Manifest to niewielki plik Python, który wywołuje kilka dyrektyw:
freeze("$(OMV_LIB_DIR)/", "foo.py")– wkompilowuje pojedynczyfoo.pyw build.package("mylib", base_path="...")– wkompilowuje wieloplikowy pakiet Python, zachowując jego układ katalogów pod podaną ścieżką bazową.include("...")– dołącza inny plik manifestu. Manifesty płytek używają tego do współdzielenia wspólnych zestawów modułów.require("logging")– dołącza nazwany modułmicropython-libz repozytorium nadrzędnego wg nazwy.
Minimalny manifest aplikacji dodaje jedną linię freeze na każdy skrypt najwyższego poziomu oraz jedną linię package na każdy pakiet, od którego zależy aplikacja.
14.2.2.1.2.1. Gdzie znajduje się kod źródłowy¶
Kod źródłowy aplikacji znajduje się w scripts/libraries/ w drzewie oprogramowania układowego, obok modułów, które build już zamraża. Zmienna manifestu $(OMV_LIB_DIR) rozwija się do tej ścieżki, dzięki czemu wpisy w manifeście pozostają krótkie. Edycja manifestu jest i tak operacją wewnątrz drzewa, więc trzymanie kodu źródłowego w drzewie pozwala uniknąć żonglowania osobnym repozytorium projektu przy rozwiązywaniu ścieżek.
Typowy układ dla aplikacji dostarczającej pojedynczy main.py wraz z towarzyszącym pakietem:
scripts/libraries/
main.py
my_lib/
__init__.py
helpers.py
Natomiast w pliku boards/<TARGET>/manifest.py płytki: jedna linia freeze dla skryptu i jedna linia package dla pakietu:
freeze("$(OMV_LIB_DIR)/", "main.py")
package("my_lib", base_path="$(OMV_LIB_DIR)/my_lib")
Skrypty jednoplikowe – tutaj main.py, ale ta sama zasada dotyczy boot.py lub dowolnego samodzielnego pomocnika – używają freeze. Wieloplikowe pakiety używają package. Dodanie kolejnego skryptu to jedna dodatkowa linia freeze; dodanie kolejnego pakietu to jedna dodatkowa linia package.
14.2.2.1.2.2. Budowanie i wgrywanie¶
Gdy manifest jest na miejscu, zbuduj oprogramowanie układowe dokładnie tak, jak opisuje rozdział o oprogramowaniu układowym
make -j$(nproc) -C lib/micropython/mpy-cross # once, builds the cross-compiler
make -j$(nproc) TARGET=<TARGET> # builds the firmware
Wynik trafia do build/<TARGET>/bin/
build/<TARGET>/bin/
firmware.bin # flash through the IDE
romfs0.img # flash through the IDE in a separate step
Wgranie plików .bin i .img przez IDE daje kamerę, której aplikacja jest częścią builda.
Powyższa sekwencja rozruchu sprawia, że wkompilowanie jest skuteczne: środowisko uruchomieniowe rozwiązuje boot.py i main.py do zamrożonych kopii, zanim w ogóle sprawdzi system plików, więc dostarczona kamera wykonuje kod z builda nawet wtedy, gdy karta SD zawiera nieaktualny boot.py pozostawiony z czasów tworzenia oprogramowania.
14.2.2.1.2.3. Kolejność wyszukiwania¶
Semantyka nadpisywania jest różna dla ścieżki wykonania boot.py / main.py oraz dla zwykłych instrukcji import. Wiedza o tym, co jest czym, ma znaczenie zarówno dla produkcji, jak i dla tworzenia oprogramowania:
Dla
boot.pyimain.py: środowisko uruchomieniowe najpierw szuka kopii zamrożonej, a dopiero potem w systemie plików. Zamrożonegoboot.pynie da się nadpisać, umieszczając go na karcie SD – ktokolwiek trzyma kamerę, nie może zmienić punktu wejścia bez ponownego wgrania oprogramowania.Dla
import foo: środowisko uruchomieniowe najpierw przeszukujesys.path– co obejmuje/flash,/sdcard,/romoraz ich podkatalogilib– a potem moduły zamrożone. Plikfoo.pyo tej samej nazwie na pamięci flash lub karcie SD faktycznie nadpisuje zamrożonyfoo. To jest udogodnienie przy tworzeniu oprogramowania: umieść poprawiony moduł na karcie, wykonaj miękki reset i zobacz zmianę bez ponownego wgrywania oprogramowania.
Gotowy produkt, który chce wyłączyć zachowanie nadpisywania zamrożonych modułów przez system plików dla importów, może wyczyścić sys.path na wczesnym etapie boot.py
import sys
sys.path.clear()
Przy pustym sys.path wszystkie importy są rozwiązywane wyłącznie z modułów zamrożonych; nic na pamięci flash, karcie SD ani w ROMFS nie może ich przesłonić.
14.2.2.1.2.4. Problem zasobów¶
Zamrażanie świetnie sprawdza się w przypadku kodu. Nie sprawdza się natomiast w przypadku dużych zasobów binarnych: plików modeli uczenia maszynowego, tablic etykiet, konfiguracji JSON, szablonów obrazów. Osadzanie ich jako literałów Python rozdyma kod źródłowy, wydłuża rekompilację i marnuje kontener kodu bajtowego na dane, które interpreter i tak zamierza odczytać w surowej postaci. Strona Budowanie obrazu ROMFS omawia tylko-do-odczytu system plików w pamięci flash, który wypełnia tę lukę.