14.2.2.2. Budowanie obrazu ROMFS

Obraz ROMFS to rezydujący w pamięci flash, tylko-do-odczytu system plików, który środowisko uruchomieniowe automatycznie montuje w /rom. Rozwiązuje on problem zasobów, którym zakończyła się poprzednia strona: pliki modeli uczenia maszynowego, tablice etykiet, konfiguracja JSON, szablony obrazów – wszystko, co aplikacja otwiera i odczytuje, ale nigdy nie zapisuje – trafia do builda bez ponoszenia kosztu osadzania jako literały Python.

Trzy rzeczy sprawiają, że ROMFS jest właściwym narzędziem dla dostarczanych zasobów:

  • System plików jest częścią obrazu oprogramowania układowego. Użytkownicy końcowi nie mogą usunąć pliku z /rom, edytować go ani zastąpić własnym.

  • Pliki w /rom są dostępne w miejscu. Konsumenci tacy jak moduł ml ładujący plik modelu otrzymują bezpośredni wgląd w pamięć flash bez kopii w RAM – wielomegabajtowy model na /rom „ładuje się” zasadniczo za darmo, podczas gdy ten sam plik na /sdcard jest wczytywany do RAM w momencie ładowania i tam pozostaje przez cały czas życia referencji. Zwykłe open() + read kopiuje na żądanie: każde wywołanie read(n) kopiuje n bajtów z pamięci flash do RAM w momencie wywołania, a samo read() żąda całego pliku.

  • /rom oraz /rom/lib są dodawane do sys.path podczas rozruchu. Pakiety Python umieszczone w obrazie można importować po nazwie; nic szczególnego w miejscu wywołania.

14.2.2.2.1. Budowanie obrazu

Obrazy ROMFS są tworzone, edytowane i wgrywane za pomocą IDE. Używaj go jako źródła prawdy dla zawartości każdej dostarczanej partycji ROMFS.

Powód, dla którego to ma znaczenie: pliki modeli mają wymagania dotyczące wyrównania, które loader wymusza w czasie wykonywania. Pliki .tflite muszą być dopełnione do granic 16-bajtowych, a NPU układu N6 wymaga wyrównania 32-bajtowego dla skompilowanych modeli. IDE stosuje to dopełnienie automatycznie podczas zapisywania obrazu. Narzędzia, które przechodzą drzewo źródłowe bez zastosowania dopełnienia – w szczególności mpremote romfs – tworzą obraz, który montuje się poprawnie, ale którego modele zawodzą przy pierwszym wywołaniu wnioskowania.

Edytor ROMFS w IDE to interaktywny widok zawartości obrazu. Pliki i foldery można dodawać, zmieniać im nazwy i usuwać w pamięci; zapis wypisuje wynik jako plik .img gotowy do wgrania. Typowa struktura dla aplikacji dostarczającej model wraz z pewnymi zasobami i pakietem Python wygląda następująco:

model.tflite
labels.txt
config.json
templates/
    calibration.jpg
lib/
    mylib/
        __init__.py
        helpers.py

Wskazówka

Zarówno IDE, jak i mpremote kompilują krzyżowo pliki .py do kodu bajtowego .mpy w trakcie umieszczania ich w obrazie ROMFS, dzięki czemu kamera importuje je bez ponoszenia kosztu parsowania podczas ładowania. Pliki źródłowe w edytorze pozostają .py; obraz zawiera .mpy.

Gdy obraz zostanie wgrany, drzewo jest widoczne z poziomu MicroPython w /rom/

>>> import os
>>> os.listdir('/rom')
['model.tflite', 'labels.txt', 'config.json', 'templates', 'lib']
>>> import mylib
>>> mylib.helpers
<module 'mylib.helpers' from '/rom/lib/mylib/helpers.mpy'>

14.2.2.2.2. Większość aplikacji żyje w ROMFS

ROMFS jest właściwym miejscem dla niemal wszystkiego, co dostarcza aplikacja: bibliotek, które importuje, plików modeli, które ładuje, konfiguracji, którą odczytuje, dowolnego zasobu, którego wynik pochodzi z narzędzia budującego emitującego drzewo plików (konwertery modeli, potoki obrazów, pakowarki zasobów) oraz – co istotne – samego kodu aplikacji.

Strona zamrożonych modułów powinna pozostać niewielka: boot.py do konfiguracji przed REPL, main.py jako cienki punkt wejścia oraz tylko te biblioteki, bez których kamera naprawdę nie może się uruchomić. Wszystko inne trafia do ROMFS, gdzie iterowanie nad tym to świeży plik .img zapisany z IDE i ponownie wgrany – bez konieczności przebudowywania oprogramowania układowego, bez potrzeby posiadania pod ręką narzędzi do tego.

Wzorzec, który z tego wynika, to main.py, które nie robi nic poza delegowaniem do aplikacji rezydującej w ROMFS:

# main.py (frozen)
import app
app.run()

# /rom/app/__init__.py (in ROMFS)
def run():
    ...

Zmiana w app to edycja ROMFS i ponowne wgranie. Build oprogramowania układowego pozostaje niezmieniony przez cały czas życia produktu, chyba że coś po stronie zamrożonej faktycznie musi się zmienić.