14.2.2.1. Înghețarea scripturilor în firmware

Un modul înghețat (frozen) este un fișier .py compilat în bytecode și legat în imaginea firmware-ului la momentul compilării. La execuție, runtime-ul importă un modul înghețat direct din memoria flash, fără a se uita vreodată la sistemul de fișiere de pe disc. Pentru un produs livrat, acesta este locul potrivit pentru codul aplicației: nimic pe care utilizatorul final să poată șterge, niciun fișier .py învechit de pe cardul SD care să îl suprascrie, iar camera rulează același cod la fiecare pornire, indiferent de ce se află (dacă se află ceva) pe unitățile sale.

Această pagină acoperă secvența de pornire pe care camera o urmează, apoi modul în care manifest.py și directiva freeze integrează o aplicație în build.

14.2.2.1.1. Secvența de pornire

Ce rulează și când, pe o cameră care iese din reset:

  • Bootloader-ul. Pornirea intră într-o scurtă fereastră DFU pe care OpenMV IDE o folosește pentru a trimite actualizări de firmware. Fereastra se închide după câteva secunde, iar bootloader-ul predă controlul către MicroPython. Un script în execuție poate reintra în această fereastră la cerere apelând machine.bootloader().

  • Inițializarea sistemului de fișiere înghețat. Înainte ca orice cod al aplicației să ruleze, runtime-ul aduce sistemele de fișiere în stare de funcționare. Memoria flash internă este montată la /flash (și formatată în gol dacă nu se află nimic acolo). Dacă este prezent un card SD și nu există un fișier marcator numit SKIPSD în memoria flash internă, cardul SD este montat la /sdcard. ROMFS, atunci când build-ul îl include, este montat automat la /rom. Directorul de lucru este setat la directorul de pornire (/sdcard dacă s-a montat cardul, /flash în caz contrar), iar sys.path este populat cu /flash, /flash/lib, /sdcard, /sdcard/lib, /rom și /rom/lib. Configurarea rezidentă în flash este gestionată de un modul înghețat numit _boot.py – infrastructură de port și placă, nu un punct de extindere al aplicației. Aplicațiile nu personalizează _boot.py; build-ul o face. Plasarea unui fișier SKIPSD în flash din OpenMV IDE este modalitatea acceptată de a face camera să pornească din memoria flash internă în loc de cardul SD.

  • Configurarea înainte de REPL. boot.py rulează la fiecare soft reset – pornire la rece, Ctrl-D din REPL, scriptul în execuție care se încheie și recuperarea prin watchdog – înainte ca REPL-ul să devină accesibil. Sarcina sa este de a pregăti mediul în care rulează restul sistemului: tipul de configurare de care REPL-ul, aplicația și orice instrument de recuperare au nevoie pentru a funcționa. Nu este locul unde se află aplicația propriu-zisă. main.py este punctul de intrare al aplicației.

  • Bucla principală. main.py este bucla principală a aplicației. Rulează o singură dată la pornirea la rece, imediat după boot.py. Nu este re-rulat la soft reset-urile ulterioare – în schimb, camera revine la REPL. Această asimetrie contează pentru dezvoltare (un Ctrl-D revine la REPL fără a re-rula bucla, astfel încât dezvoltatorul să poată inspecta starea), dar nu și pentru producție: o cameră instalată în teren vede porniri, watchdog și reset-uri hardware, care sunt toate reset-uri hardware ce reintră pe calea de pornire la rece și rulează din nou main.py.

14.2.2.1.2. Înghețarea în firmware

Setul de module înghețate al unei plăci este declarat în boards/<TARGET>/manifest.py în arborele firmware-ului. Manifestul este un mic fișier Python care apelează câteva directive:

  • freeze("$(OMV_LIB_DIR)/", "foo.py") – integrează un singur fișier foo.py în build.

  • package("mylib", base_path="...") – integrează un pachet Python format din mai multe fișiere, păstrându-i structura de directoare sub calea de bază dată.

  • include("...") – include un alt fișier manifest. Manifestele plăcilor folosesc acest lucru pentru a partaja seturi comune de module.

  • require("logging") – include un modul micropython-lib din amonte specificat după nume.

Un manifest minimal de aplicație adaugă o linie freeze per script de nivel superior și o linie package per pachet de care depinde aplicația.

14.2.2.1.2.1. Unde se află sursa

Sursa aplicației se află sub scripts/libraries/ în arborele firmware-ului, alături de modulele pe care build-ul le îngheață deja. Variabila de manifest $(OMV_LIB_DIR) se extinde la acea cale, astfel încât intrările din manifest rămân scurte. Editarea manifestului este deja o operațiune în interiorul arborelui, așa că păstrarea sursei în interiorul arborelui evită jonglarea cu un depozit de proiect separat la rezolvarea căilor.

O structură tipică pentru o aplicație care livrează un singur main.py plus un pachet de suport:

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

Iar în fișierul boards/<TARGET>/manifest.py al plăcii, o linie freeze pentru script și o linie package pentru pachet:

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

Scripturile dintr-un singur fișier – main.py aici, dar aceeași regulă se aplică pentru boot.py sau orice modul ajutător independent – folosesc freeze. Pachetele cu mai multe fișiere folosesc package. Adăugarea unui alt script înseamnă încă o linie freeze; adăugarea unui alt pachet înseamnă încă o linie package.

14.2.2.1.2.2. Compilarea și programarea

Odată ce manifestul este la locul lui, compilați firmware-ul exact așa cum descrie capitolul despre firmware

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

Rezultatul ajunge în build/<TARGET>/bin/

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

Programarea fișierelor .bin și .img prin OpenMV IDE produce o cameră a cărei aplicație face parte din build.

Secvența de pornire de mai sus este ceea ce face integrarea eficientă: runtime-ul rezolvă boot.py și main.py la copiile înghețate înainte de a verifica vreodată sistemul de fișiere, astfel încât o cameră livrată rulează codul build-ului chiar dacă pe cardul SD se află un fișier boot.py învechit lăsat din timpul dezvoltării.

14.2.2.1.2.3. Ordinea de căutare

Semantica de suprascriere este diferită pentru calea de execuție boot.py / main.py față de instrucțiunile import obișnuite. A ști care este care contează atât pentru producție, cât și pentru dezvoltare:

  • Pentru boot.py și main.py: runtime-ul caută mai întâi o copie înghețată, apoi sistemul de fișiere. Un boot.py înghețat nu poate fi suprascris prin plasarea unuia pe cardul SD – cine deține camera nu poate schimba punctul de intrare fără a reprograma firmware-ul.

  • Pentru import foo: runtime-ul caută mai întâi în sys.path – care acoperă /flash, /sdcard, /rom și subdirectoarele lor lib – apoi modulele înghețate. Un fișier foo.py cu același nume pe flash sau SD suprascrie într-adevăr un foo înghețat. Aceasta este facilitatea pentru dezvoltare: plasați un modul corectat pe card, faceți un soft reset, vedeți modificarea fără a reprograma firmware-ul.

Un produs livrat care dorește să suprime comportamentul de suprascriere a modulelor înghețate de către sistemul de fișiere la importuri poate goli sys.path devreme în boot.py

import sys

sys.path.clear()

Cu sys.path gol, toate importurile se rezolvă numai din modulele înghețate; nimic de pe flash, SD sau ROMFS nu le poate eclipsa.

14.2.2.1.2.4. Problema activelor

Înghețarea este excelentă pentru cod. Nu este la fel de bună pentru active binare mari: fișiere de model de învățare automată, tabele de etichete, configurare JSON, șabloane de imagini. Încorporarea acestora ca literali Python umflă sursa, recompilează lent și irosește containerul de bytecode pe date pe care interpretorul oricum doar le va citi brut. Pagina Construirea unei imagini ROMFS acoperă sistemul de fișiere flash de tip read-only care umple acest gol.