14.2.2.1. Szkriptek befagyasztása a firmware-be

A befagyasztott (frozen) modul olyan .py fájl, amelyet bájtkóddá fordítanak, és build-időben a firmware-képbe linkelnek. A futtatókörnyezet a befagyasztott modult közvetlenül a flash memóriából importálja, anélkül hogy egyáltalán megnézné a lemezen lévő fájlrendszert. Egy szállított termék esetén ez a megfelelő hely az alkalmazás kódjának: a végfelhasználónak nincs mit törölnie, nincs olyan elavult .py az SD-kártyán, amely felülírhatná, és a kamera minden indításkor ugyanazt a kódot futtatja, függetlenül attól, hogy mi van (ha van egyáltalán) a meghajtóin.

Ez az oldal bemutatja a kamera által követett indítási sorrendet, majd azt, hogy a manifest.py és a freeze direktíva hogyan süti bele az alkalmazást a buildbe.

14.2.2.1.1. Az indítási sorrend

Mi fut, és mikor, egy reset után induló kamerán:

  • A rendszerbetöltő. A bekapcsoláskor egy rövid DFU-ablakba lép, amelyet az IDE a firmware-frissítések feltöltésére használ. Az ablak néhány másodperc után bezárul, és a rendszerbetöltő átadja a vezérlést a MicroPythonnak. Egy futó szkript igény szerint újra beléphet ebbe az ablakba a machine.bootloader() hívásával.

  • Befagyasztott fájlrendszer inicializálása. Mielőtt bármilyen alkalmazáskód futna, a futtatókörnyezet elindítja a fájlrendszereket. A belső flash memória a /flash ponton csatolódik (és üresre formázódik, ha nincs ott semmi). Ha SD-kártya van jelen, és a belső flash memórián nem létezik SKIPSD nevű jelölőfájl, akkor az SD-kártya a /sdcard ponton csatolódik. A ROMFS, ha a build tartalmazza, automatikusan a /rom ponton csatolódik. A munkakönyvtár az indítási könyvtárra áll be (/sdcard, ha a kártya csatolódott, egyébként /flash), a sys.path pedig a /flash, /flash/lib, /sdcard, /sdcard/lib, /rom és /rom/lib elemekkel töltődik fel. A flash memóriában lévő beállítást egy _boot.py nevű befagyasztott modul kezeli – ez port- és kártyainfrastruktúra, nem alkalmazás-horog. Az alkalmazások nem testreszabják a _boot.py fájlt; ezt a build teszi. A SKIPSD fájl flash memóriára ejtése az IDE-ből a támogatott módja annak, hogy a kamera az SD-kártya helyett a belső flash memóriáról induljon.

  • REPL előtti beállítás. A boot.py minden szoftveres resetkor lefut – hidegindítás, Ctrl-D a REPL-ből, a futó szkript visszatérése és watchdog-helyreállítás esetén – mielőtt a REPL elérhetővé válik. Feladata, hogy előkészítse azt a környezetet, amelyben a rendszer többi része fut: az a fajta beállítás, amelyre a REPL-nek, az alkalmazásnak és bármely helyreállító eszköznek egyaránt szüksége van a működéshez. Nem itt él maga az alkalmazás. A main.py az alkalmazás belépési pontja.

  • Fő ciklus. A main.py az alkalmazás fő ciklusa. Hidegindításkor egyszer fut le, közvetlenül a boot.py után. A későbbi szoftveres reseteknél nem fut le újra – a kamera helyette a REPL-be kerül. Ez az aszimmetria a fejlesztés szempontjából számít (egy Ctrl-D a REPL-be visz a ciklus újrafuttatása nélkül, így a fejlesztő megvizsgálhatja az állapotot), de a produkció szempontjából nem: egy terepen lévő kamera bekapcsolást, watchdogot és kemény reseteket lát, amelyek mind hardveres resetek, és ezek újra belépnek a hidegindítási útvonalba, és ismét lefuttatják a main.py fájlt.

14.2.2.1.2. Befagyasztás a firmware-be

Egy kártya befagyasztott modulkészletét a firmware-fában lévő boards/<TARGET>/manifest.py fájl deklarálja. A manifest egy kis Python-fájl, amely néhány direktívát hív meg:

  • freeze("$(OMV_LIB_DIR)/", "foo.py") – egyetlen foo.py fájlt süt bele a buildbe.

  • package("mylib", base_path="...") – egy több fájlból álló Python-csomagot süt bele, megőrizve annak könyvtárszerkezetét a megadott alapútvonal alatt.

  • include("...") – behúz egy másik manifest fájlt. A kártya-manifestek ezt használják a közös modulkészletek megosztására.

  • require("logging") – név szerint behúz egy megnevezett upstream micropython-lib modult.

Egy minimális alkalmazás-manifest top-szintű szkriptenként egy freeze sort, az alkalmazás által használt csomagonként pedig egy package sort ad hozzá.

14.2.2.1.2.1. Hol található a forrás

Az alkalmazás forrása a firmware-fában lévő scripts/libraries/ alatt található, azon modulok mellett, amelyeket a build már befagyaszt. A $(OMV_LIB_DIR) manifest-változó erre az útvonalra bővül ki, így a manifest bejegyzései rövidek maradnak. A manifest szerkesztése amúgy is fában belüli művelet, így a forrás fában tartása megspórolja egy külön projekt-repó zsonglőrködését az útvonalfeloldásnál.

Egy tipikus elrendezés olyan alkalmazáshoz, amely egyetlen main.py fájlt és egy kiegészítő csomagot szállít:

scripts/libraries/
    main.py
    my_lib/
        __init__.py
        helpers.py

A kártya boards/<TARGET>/manifest.py fájljában pedig egy freeze sor a szkripthez és egy package sor a csomaghoz:

freeze("$(OMV_LIB_DIR)/", "main.py")
package("my_lib", base_path="$(OMV_LIB_DIR)/my_lib")

Egyfájlos szkriptek – itt a main.py, de ugyanez a szabály vonatkozik a boot.py fájlra vagy bármely önálló segédfájlra – a freeze direktívát használják. A több fájlból álló csomagok a package direktívát használják. Egy újabb szkript hozzáadása egy újabb freeze sor; egy újabb csomag hozzáadása egy újabb package sor.

14.2.2.1.2.2. Buildelés és flashelés

Miután a manifest a helyén van, buildeld a firmware-t pontosan úgy, ahogyan a firmware fejezet leírja:

make -j$(nproc) -C lib/micropython/mpy-cross   # once, builds the cross-compiler
make -j$(nproc) TARGET=<TARGET>                # builds the firmware

A kimenet a build/<TARGET>/bin/ könyvtárba kerül:

build/<TARGET>/bin/
    firmware.bin     # flash through the IDE
    romfs0.img       # flash through the IDE in a separate step

A .bin és .img fájlok IDE-n keresztüli flashelése olyan kamerát eredményez, amelynek alkalmazása a build része.

A fenti indítási sorrend az, ami a belesütést hatékonnyá teszi: a futtatókörnyezet a boot.py és main.py fájlokat a befagyasztott példányokra oldja fel, mielőtt egyáltalán megnézné a fájlrendszert, így egy szállított kamera akkor is a build kódját futtatja, ha az SD-kártyán egy fejlesztésből ottmaradt, elavult boot.py található.

14.2.2.1.2.3. Keresési sorrend

A felülírási szemantika eltérő a boot.py / main.py végrehajtási útvonal és a közönséges import utasítások esetén. Annak ismerete, hogy melyik melyik, a produkció és a fejlesztés szempontjából is számít:

  • A boot.py és main.py esetén: a futtatókörnyezet először a befagyasztott példányt keresi, majd a fájlrendszert. Egy befagyasztott boot.py fájlt nem lehet felülírni egy SD-kártyára ejtett másikkal – aki a kamerát birtokolja, nem tudja megváltoztatni a belépési pontot újraflashelés nélkül.

  • Az import foo esetén: a futtatókörnyezet először a sys.path útvonalat keresi – amely lefedi a /flash, /sdcard, /rom könyvtárakat és azok lib alkönyvtárait –, majd a befagyasztott modulokat. Egy flash memórián vagy SD-kártyán lévő, azonos nevű foo.py valóban felülír egy befagyasztott foo modult. Ez a fejlesztési könnyítés: ejts egy javított modult a kártyára, végezz szoftveres resetet, és lásd a változást újraflashelés nélkül.

Egy szállított termék, amely el akarja nyomni a fájlrendszer-felülírja-a-befagyasztottat viselkedést az importoknál, korán a boot.py fájlban kiürítheti a sys.path útvonalat:

import sys

sys.path.clear()

Ha a sys.path üres, minden import kizárólag a befagyasztott modulokból oldódik fel; semmi a flash memórián, az SD-kártyán vagy a ROMFS-en nem árnyékolhatja be őket.

14.2.2.1.2.4. Az eszköz-probléma

A befagyasztás kiváló a kódhoz. Nem kiváló viszont a nagy bináris eszközökhöz: gépi tanulási modellfájlokhoz, címketáblázatokhoz, JSON-konfigurációhoz, képsablonokhoz. Ezek Python-literálként való beágyazása felfújja a forrást, lassan fordul újra, és az adatokra pazarolja a bájtkód-tárolót, amelyeket az értelmező amúgy is csak nyersen fog beolvasni. A ROMFS-kép buildelése oldal bemutatja az ezt a hiányt betöltő, csak olvasható flash-fájlrendszert.