14.2.2.1. Skripte in die Firmware einfrieren¶
Ein eingefrorenes (frozen) Modul ist eine .py-Datei, die zu Bytecode kompiliert und zur Build-Zeit in das Firmware-Image eingebunden wird. Die Laufzeitumgebung importiert ein eingefrorenes Modul direkt aus dem Flash, ohne jemals auf das Dateisystem zuzugreifen. Für ein ausgeliefertes Produkt ist dies der richtige Ort für den Anwendungscode: Der Endnutzer kann nichts löschen, eine veraltete .py auf der SD-Karte kann nichts überschreiben, und die Kamera führt bei jedem Start denselben Code aus, unabhängig davon, was (wenn überhaupt) sich auf ihren Laufwerken befindet.
Diese Seite behandelt die Startsequenz, die die Kamera durchläuft, und anschließend, wie manifest.py und die freeze-Direktive eine Anwendung in den Build einbacken.
14.2.2.1.1. Die Startsequenz¶
Was läuft auf einer Kamera nach einem Reset, und wann:
Der Bootloader. Beim Einschalten wird ein kurzes DFU-Fenster geöffnet, das die IDE zum Aufspielen von Firmware-Updates nutzt. Das Fenster schließt sich nach einigen Sekunden, und der Bootloader übergibt an MicroPython. Ein laufendes Skript kann dieses Fenster bei Bedarf erneut betreten, indem es
machine.bootloader()aufruft.Initialisierung des eingefrorenen Dateisystems. Bevor irgendein Anwendungscode läuft, bringt die Laufzeitumgebung die Dateisysteme hoch. Der interne Flash wird unter
/flasheingehängt (und leer formatiert, falls dort nichts vorhanden ist). Ist eine SD-Karte vorhanden und existiert eine Markierungsdatei namensSKIPSDnicht im internen Flash, so wird die SD-Karte unter/sdcardeingehängt. ROMFS wird, wenn der Build es enthält, automatisch unter/romeingehängt. Das Arbeitsverzeichnis wird auf das Boot-Verzeichnis gesetzt (/sdcard, falls die Karte eingehängt wurde, andernfalls/flash), undsys.pathwird mit/flash,/flash/lib,/sdcard,/sdcard/lib,/romund/rom/libbefüllt. Die im Flash residente Einrichtung wird von einem eingefrorenen Modul namens_boot.pyübernommen – Port- und Board-Infrastruktur, kein Anwendungs-Hook. Anwendungen passen_boot.pynicht an; das tut der Build. EineSKIPSD-Datei über die IDE in den Flash zu legen ist der unterstützte Weg, die Kamera vom internen Flash statt von der SD-Karte booten zu lassen.Einrichtung vor der REPL.
boot.pyläuft bei jedem Soft-Reset – Kaltstart,Ctrl-Daus der REPL, Beenden des laufenden Skripts und Watchdog-Wiederherstellung – bevor die REPL erreichbar wird. Seine Aufgabe ist es, die Umgebung vorzubereiten, in der der Rest des Systems läuft: jene Art von Einrichtung, die die REPL, die Anwendung und jegliches Wiederherstellungswerkzeug zum Funktionieren benötigen. Hier liegt nicht die Anwendung selbst.main.pyist der Einstiegspunkt der Anwendung.Hauptschleife.
main.pyist die Hauptschleife der Anwendung. Sie läuft beim Kaltstart einmal, unmittelbar nachboot.py. Bei nachfolgenden Soft-Resets wird sie nicht erneut ausgeführt – die Kamera fällt stattdessen in die REPL zurück. Diese Asymmetrie ist für die Entwicklung wichtig (ein Ctrl-D fällt in die REPL zurück, ohne die Schleife erneut zu starten, sodass die Entwicklerin den Zustand untersuchen kann), aber nicht für die Produktion: Eine im Feld installierte Kamera erlebt Einschalt-, Watchdog- und Hard-Resets, die allesamt Hardware-Resets sind, die den Kaltstart-Pfad erneut betreten undmain.pywieder ausführen.
14.2.2.1.2. In die Firmware einfrieren¶
Der Satz eingefrorener Module eines Boards wird in boards/<TARGET>/manifest.py im Firmware-Baum deklariert. Das Manifest ist eine kleine Python-Datei, die eine Handvoll Direktiven aufruft:
freeze("$(OMV_LIB_DIR)/", "foo.py")– bäckt eine einzelnefoo.pyin den Build ein.package("mylib", base_path="...")– bäckt ein aus mehreren Dateien bestehendes Python-Paket ein und behält dessen Verzeichnisstruktur unter dem angegebenen Basispfad bei.include("...")– zieht eine weitere Manifest-Datei hinein. Die Board-Manifeste nutzen dies, um gemeinsame Modulsätze zu teilen.require("logging")– zieht ein benanntes vorgelagertesmicropython-lib-Modul über seinen Namen hinein.
Ein minimales Anwendungsmanifest fügt eine freeze-Zeile pro Top-Level-Skript und eine package-Zeile pro Paket hinzu, von dem die Anwendung abhängt.
14.2.2.1.2.1. Wo der Quellcode liegt¶
Der Anwendungsquellcode liegt unter scripts/libraries/ im Firmware-Baum, neben den Modulen, die der Build bereits einfriert. Die Manifest-Variable $(OMV_LIB_DIR) expandiert zu diesem Pfad, sodass Manifest-Einträge kurz bleiben. Das Bearbeiten des Manifests ist ohnehin eine Operation innerhalb des Baums, sodass das Halten des Quellcodes im Baum es erspart, ein separates Projekt-Repository im Pfad zu jonglieren.
Ein typisches Layout für eine Anwendung, die eine einzelne main.py plus ein unterstützendes Paket ausliefert:
scripts/libraries/
main.py
my_lib/
__init__.py
helpers.py
Und im boards/<TARGET>/manifest.py des Boards eine freeze-Zeile für das Skript und eine package-Zeile für das Paket:
freeze("$(OMV_LIB_DIR)/", "main.py")
package("my_lib", base_path="$(OMV_LIB_DIR)/my_lib")
Einzeldatei-Skripte – hier main.py, aber dieselbe Regel gilt für boot.py oder jeden eigenständigen Helfer – verwenden freeze. Pakete aus mehreren Dateien verwenden package. Ein weiteres Skript hinzuzufügen ist eine weitere freeze-Zeile; ein weiteres Paket hinzuzufügen ist eine weitere package-Zeile.
14.2.2.1.2.2. Bauen und Flashen¶
Sobald das Manifest vorhanden ist, bauen Sie die Firmware genau so, wie es das Firmware-Kapitel beschreibt:
make -j$(nproc) -C lib/micropython/mpy-cross # once, builds the cross-compiler
make -j$(nproc) TARGET=<TARGET> # builds the firmware
Die Ausgabe landet in build/<TARGET>/bin/:
build/<TARGET>/bin/
firmware.bin # flash through the IDE
romfs0.img # flash through the IDE in a separate step
Das Flashen der .bin und .img über die IDE ergibt eine Kamera, deren Anwendung Teil des Builds ist.
Die obige Startsequenz ist es, die das Einbacken wirksam macht: Die Laufzeitumgebung löst boot.py und main.py zu den eingefrorenen Kopien auf, bevor sie überhaupt das Dateisystem prüft, sodass eine ausgelieferte Kamera den Code des Builds ausführt, selbst wenn die SD-Karte eine veraltete boot.py aus der Entwicklung enthält.
14.2.2.1.2.3. Reihenfolge der Suche¶
Die Override-Semantik ist unterschiedlich für den Ausführungspfad von boot.py / main.py und für gewöhnliche import-Anweisungen. Zu wissen, was was ist, ist sowohl für die Produktion als auch für die Entwicklung wichtig:
Für
boot.pyundmain.py: Die Laufzeitumgebung sucht zuerst nach einer eingefrorenen Kopie, dann im Dateisystem. Eine eingefroreneboot.pylässt sich nicht überschreiben, indem man eine auf die SD-Karte legt – wer die Kamera in Händen hält, kann den Einstiegspunkt nicht ändern, ohne neu zu flashen.Für
import foo: Die Laufzeitumgebung durchsucht zuerstsys.path– der/flash,/sdcard,/romund derenlib-Unterverzeichnisse abdeckt – dann die eingefrorenen Module. Eine gleichnamigefoo.pyim Flash oder auf der SD-Karte überschreibt durchaus ein eingefrorenesfoo. Dies ist die Entwicklungs-Annehmlichkeit: ein korrigiertes Modul auf die Karte legen, soft-reseten, die Änderung sehen, ohne neu zu flashen.
Ein ausgeliefertes Produkt, das das Verhalten „Dateisystem überschreibt eingefroren“ für Importe unterdrücken möchte, kann sys.path früh in boot.py leeren:
import sys
sys.path.clear()
Ist sys.path leer, werden alle Importe ausschließlich aus den eingefrorenen Modulen aufgelöst; nichts im Flash, auf der SD-Karte oder in ROMFS kann sie verdecken.
14.2.2.1.2.4. Das Asset-Problem¶
Einfrieren ist großartig für Code. Es ist nicht großartig für große Binär-Assets: Modelldateien für maschinelles Lernen, Label-Tabellen, JSON-Konfiguration, Bildvorlagen. Diese als Python-Literale einzubetten bläht den Quellcode auf, kompiliert langsam neu und verschwendet den Bytecode-Container für Daten, die der Interpreter ohnehin nur roh lesen wird. Die Seite Ein ROMFS-Image erstellen behandelt das schreibgeschützte Flash-Dateisystem, das diese Lücke füllt.