14.2.2.1. Frysa skript in i den fasta programvaran

En fryst modul är en .py-fil som kompilerats till bytekod och länkats in i avbilden av den fasta programvaran vid byggtillfället. Körningsmiljön importerar en fryst modul direkt från flashminnet, utan att någonsin titta på filsystemet på disk. För en levererad produkt är detta rätt plats för applikationskoden: ingenting för slutanvändaren att ta bort, ingenting hos en föråldrad .py på SD-kortet som kan åsidosätta den, och kameran kör samma kod vid varje uppstart oavsett vad (om något) som finns på dess enheter.

Den här sidan beskriver uppstartssekvensen som kameran följer, och därefter hur manifest.py och direktivet freeze bakar in en applikation i bygget.

14.2.2.1.1. Uppstartssekvensen

Vad som körs, och när, på en kamera som kommer ur återställning:

  • Startladdaren. Vid påslag öppnas ett kort DFU-fönster som IDE:n använder för att skicka ut uppdateringar av den fasta programvaran. Fönstret stängs efter några sekunder och startladdaren lämnar över till MicroPython. Ett körande skript kan på begäran gå in i detta fönster igen genom att anropa machine.bootloader().

  • Initiering av det frysta filsystemet. Innan någon applikationskod körs startar körningsmiljön upp filsystemen. Internt flashminne monteras vid /flash (och formateras tomt om det inte finns något där). Om ett SD-kort finns och en markörfil vid namn SKIPSD inte finns i det interna flashminnet, monteras SD-kortet vid /sdcard. ROMFS monteras automatiskt vid /rom när bygget innehåller det. Arbetskatalogen sätts till uppstartskatalogen (/sdcard om kortet monterades, annars /flash), och sys.path fylls med /flash, /flash/lib, /sdcard, /sdcard/lib, /rom och /rom/lib. Den flashresidenta uppsättningen hanteras av en fryst modul vid namn _boot.py – infrastruktur för port och kort, inte en applikationskrok. Applikationer anpassar inte _boot.py; det gör bygget. Att lägga en SKIPSD-fil på flashminnet från IDE:n är det rekommenderade sättet att få kameran att starta från det interna flashminnet i stället för SD-kortet.

  • Uppsättning före REPL. boot.py körs vid varje mjuk återställning – kallstart, Ctrl-D från REPL:en, när det körande skriptet returnerar, och vid återhämtning efter watchdog – innan REPL:en blir nåbar. Dess uppgift är att förbereda den miljö som resten av systemet körs i: den typ av uppsättning som REPL:en, applikationen och eventuella återhämtningsverktyg alla behöver på plats för att fungera. Det är inte här applikationen själv bor. main.py är applikationens startpunkt.

  • Huvudloop. main.py är applikationens huvudloop. Körs en gång vid kallstart, omedelbart efter boot.py. Körs inte om vid efterföljande mjuka återställningar – kameran faller i stället ner till REPL:en. Den asymmetrin spelar roll för utveckling (ett Ctrl-D faller ner till REPL:en utan att köra om loopen, så att utvecklaren kan inspektera tillståndet) men inte för produktion: en kamera ute i fält ser påslag, watchdog och hårda återställningar, vilka alla är maskinvaruåterställningar som går in i kallstartsvägen igen och kör main.py på nytt.

14.2.2.1.2. Frysa in i den fasta programvaran

Ett korts uppsättning av frysta moduler deklareras i boards/<TARGET>/manifest.py i trädet för den fasta programvaran. Manifestet är en liten Python-fil som anropar en handfull direktiv:

  • freeze("$(OMV_LIB_DIR)/", "foo.py") – bakar in en enstaka foo.py i bygget.

  • package("mylib", base_path="...") – bakar in ett Python-paket med flera filer och bevarar dess katalogstruktur under den angivna basvägen.

  • include("...") – hämtar in en annan manifestfil. Kortmanifesten använder detta för att dela gemensamma moduluppsättningar.

  • require("logging") – hämtar in en namngiven micropython-lib-modul från uppströms genom dess namn.

Ett minimalt applikationsmanifest lägger till en freeze-rad per skript på översta nivån och en package-rad per paket som applikationen är beroende av.

14.2.2.1.2.1. Var källkoden bor

Applikationens källkod bor under scripts/libraries/ i trädet för den fasta programvaran, vid sidan av de moduler som bygget redan fryser. Manifestvariabeln $(OMV_LIB_DIR) expanderar till den vägen, så att manifestposterna förblir korta. Att redigera manifestet är redan en operation i trädet, så att hålla källkoden i trädet undviker att behöva jonglera ett separat projektförråd i sökvägsupplösningen.

En typisk struktur för en applikation som levererar en enda main.py plus ett stödjande paket:

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

Och i kortets boards/<TARGET>/manifest.py, en freeze-rad för skriptet och en package-rad för paketet:

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

Enfilsskript – main.py här, men samma regel gäller boot.py eller vilken fristående hjälpmodul som helst – använder freeze. Paket med flera filer använder package. Att lägga till ytterligare ett skript är ytterligare en freeze-rad; att lägga till ytterligare ett paket är ytterligare en package-rad.

14.2.2.1.2.2. Bygga och flasha

När manifestet är på plats, bygg den fasta programvaran precis som firmware-kapitlet beskriver:

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

Resultatet hamnar i build/<TARGET>/bin/

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

Att flasha .bin och .img via IDE:n ger en kamera vars applikation är en del av bygget.

Uppstartssekvensen ovan är vad som gör inbakningen verkningsfull: körningsmiljön löser upp boot.py och main.py till de frysta kopiorna innan den någonsin kontrollerar filsystemet, så att en levererad kamera kör byggets kod även om SD-kortet innehåller en föråldrad boot.py kvarlämnad från utvecklingen.

14.2.2.1.2.3. Uppslagsordning

Åsidosättningssemantiken är annorlunda för exekveringsvägen boot.py / main.py och för vanliga import-satser. Att veta vilken som är vilken spelar roll för både produktion och utveckling:

  • För boot.py och main.py: körningsmiljön letar efter en fryst kopia först, sedan filsystemet. En fryst boot.py kan inte åsidosättas genom att lägga en på SD-kortet – den som har kameran kan inte ändra startpunkten utan att flasha om.

  • För import foo: körningsmiljön söker i sys.path först – vilket täcker /flash, /sdcard, /rom och deras lib-underkataloger – sedan frysta moduler. En foo.py med samma namn på flashminnet eller SD-kortet åsidosätter en fryst foo. Detta är bekvämligheten för utveckling: lägg en korrigerad modul på kortet, gör en mjuk återställning, se ändringen utan att flasha om.

En levererad produkt som vill undertrycka beteendet där filsystemet åsidosätter frysta moduler vid import kan rensa sys.path tidigt i boot.py

import sys

sys.path.clear()

Med sys.path tom löses alla importer upp enbart från de frysta modulerna; ingenting på flashminnet, SD-kortet eller ROMFS kan skugga dem.

14.2.2.1.2.4. Tillgångsproblemet

Frysning är utmärkt för kod. Det är inte bra för stora binära tillgångar: filer för maskininlärningsmodeller, etiketttabeller, JSON-konfiguration, bildmallar. Att bädda in dessa som Python-literaler sväller upp källkoden, kompilerar om långsamt och slösar bort bytekodbehållaren på data som tolken ändå bara ska läsa rått. Sidan Bygga en ROMFS-avbild beskriver det skrivskyddade flashfilsystemet som fyller detta tomrum.