14.2.2.1. Geler des scripts dans le micrologiciel

Un module gelé est un fichier .py compilé en bytecode et lié à l’image du micrologiciel au moment de la compilation. L’environnement d’exécution importe un module gelé directement depuis la mémoire flash, sans jamais consulter le système de fichiers sur disque. Pour un produit livré, c’est l’endroit idéal pour le code de l’application : rien que l’utilisateur final puisse supprimer, rien qu’un .py obsolète sur la carte SD puisse remplacer, et la caméra exécute le même code à chaque démarrage, quel que soit le contenu (le cas échéant) de ses lecteurs.

Cette page décrit la séquence de démarrage suivie par la caméra, puis explique comment manifest.py et la directive freeze intègrent une application dans la compilation.

14.2.2.1.1. La séquence de démarrage

Ce qui s’exécute, et à quel moment, sur une caméra sortant d’une réinitialisation :

  • Le programme d’amorçage. La mise sous tension ouvre une courte fenêtre DFU utilisée par l’IDE pour envoyer les mises à jour du micrologiciel. La fenêtre se ferme au bout de quelques secondes et le programme d’amorçage passe le relais à MicroPython. Un script en cours d’exécution peut rouvrir cette fenêtre à la demande en appelant machine.bootloader().

  • Initialisation du système de fichiers gelé. Avant l’exécution de tout code applicatif, l’environnement d’exécution monte les systèmes de fichiers. La mémoire flash interne est montée sur /flash (et formatée à vide s’il n’y a rien dessus). Si une carte SD est présente et qu’un fichier marqueur nommé SKIPSD n’existe pas sur la mémoire flash interne, la carte SD est montée sur /sdcard. ROMFS, lorsque la compilation l’inclut, est monté automatiquement sur /rom. Le répertoire de travail est défini sur le répertoire de démarrage (/sdcard si la carte est montée, /flash sinon), et sys.path est renseigné avec /flash, /flash/lib, /sdcard, /sdcard/lib, /rom et /rom/lib. La configuration résidant en mémoire flash est gérée par un module gelé nommé _boot.py – une infrastructure de port et de carte, pas un point d’accroche applicatif. Les applications ne personnalisent pas _boot.py ; c’est la compilation qui le fait. Déposer un fichier SKIPSD sur la mémoire flash depuis l’IDE est la manière prise en charge de faire démarrer la caméra depuis la mémoire flash interne plutôt que depuis la carte SD.

  • Configuration avant REPL. boot.py s’exécute à chaque réinitialisation logicielle – démarrage à froid, Ctrl-D depuis le REPL, retour du script en cours d’exécution et récupération du chien de garde – avant que le REPL ne devienne accessible. Son rôle est de préparer l’environnement dans lequel s’exécute le reste du système : le type de configuration dont le REPL, l’application et tout outil de récupération ont besoin pour fonctionner. Ce n’est pas là que réside l’application elle-même. main.py est le point d’entrée de l’application.

  • Boucle principale. main.py est la boucle principale de l’application. Elle s’exécute une fois au démarrage à froid, immédiatement après boot.py. Elle n’est pas réexécutée lors des réinitialisations logicielles ultérieures – la caméra passe alors au REPL. Cette asymétrie compte pour le développement (un Ctrl-D passe au REPL sans réexécuter la boucle, ce qui permet au développeur d’inspecter l’état) mais pas pour la production : une caméra déployée subit des réinitialisations à la mise sous tension, par le chien de garde et matérielles, qui sont toutes des réinitialisations matérielles réempruntant le chemin de démarrage à froid et réexécutant main.py.

14.2.2.1.2. Geler dans le micrologiciel

L’ensemble des modules gelés d’une carte est déclaré dans boards/<TARGET>/manifest.py au sein de l’arborescence du micrologiciel. Le manifeste est un petit fichier Python qui appelle une poignée de directives :

  • freeze("$(OMV_LIB_DIR)/", "foo.py") – intègre un unique foo.py dans la compilation.

  • package("mylib", base_path="...") – intègre un paquet Python composé de plusieurs fichiers, en préservant l’agencement de ses répertoires sous le chemin de base indiqué.

  • include("...") – importe un autre fichier manifeste. Les manifestes de carte s’en servent pour partager des ensembles de modules communs.

  • require("logging") – importe un module amont micropython-lib nommé par son nom.

Un manifeste d’application minimal ajoute une ligne freeze par script de premier niveau et une ligne package par paquet dont l’application dépend.

14.2.2.1.2.1. Où réside le code source

Le code source de l’application réside sous scripts/libraries/ dans l’arborescence du micrologiciel, aux côtés des modules que la compilation gèle déjà. La variable de manifeste $(OMV_LIB_DIR) se développe en ce chemin, ce qui permet de garder les entrées de manifeste courtes. L’édition du manifeste est déjà une opération au sein de l’arborescence, donc conserver le code source dans l’arborescence évite de jongler avec un dépôt de projet séparé lors de la résolution des chemins.

Un agencement typique pour une application qui livre un unique main.py plus un paquet d’appui

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

Et dans le boards/<TARGET>/manifest.py de la carte, une ligne freeze pour le script et une ligne package pour le paquet

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

Les scripts mono-fichier – main.py ici, mais la même règle s’applique à boot.py ou à tout assistant autonome – utilisent freeze. Les paquets multi-fichiers utilisent package. Ajouter un autre script revient à une ligne freeze de plus ; ajouter un autre paquet revient à une ligne package de plus.

14.2.2.1.2.2. Compilation et flashage

Une fois le manifeste en place, compilez le micrologiciel exactement comme le décrit le chapitre sur le micrologiciel

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

La sortie se retrouve dans build/<TARGET>/bin/

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

Flasher le .bin et le .img via l’IDE produit une caméra dont l’application fait partie de la compilation.

La séquence de démarrage ci-dessus est ce qui rend l’intégration efficace : l’environnement d’exécution résout boot.py et main.py vers les copies gelées avant même de consulter le système de fichiers, de sorte qu’une caméra livrée exécute le code de la compilation même si la carte SD contient un boot.py obsolète laissé lors du développement.

14.2.2.1.2.3. Ordre de recherche

La sémantique de remplacement est différente pour le chemin d’exécution de boot.py / main.py et pour les instructions import ordinaires. Savoir laquelle est laquelle compte aussi bien pour la production que pour le développement :

  • Pour boot.py et main.py : l’environnement d’exécution recherche d’abord une copie gelée, puis le système de fichiers. Un boot.py gelé ne peut pas être remplacé en en déposant un sur la carte SD – quiconque détient la caméra ne peut pas changer le point d’entrée sans reflasher.

  • Pour import foo : l’environnement d’exécution parcourt d’abord sys.path – qui couvre /flash, /sdcard, /rom et leurs sous-répertoires lib – puis les modules gelés. Un foo.py de même nom sur la mémoire flash ou la carte SD remplace bien un foo gelé. C’est la commodité de développement : déposez un module corrigé sur la carte, faites une réinitialisation logicielle, constatez le changement sans reflasher.

Un produit livré qui souhaite supprimer le comportement « le système de fichiers remplace le gelé » pour les importations peut vider sys.path tôt dans boot.py

import sys

sys.path.clear()

Avec sys.path vide, toutes les importations se résolvent uniquement à partir des modules gelés ; rien sur la mémoire flash, la carte SD ou ROMFS ne peut les masquer.

14.2.2.1.2.4. Le problème des ressources

Le gel est excellent pour le code. Il n’est pas idéal pour les grandes ressources binaires : fichiers de modèles d’apprentissage automatique, tables d’étiquettes, configuration JSON, modèles d’image. Les intégrer en tant que littéraux Python gonfle le code source, ralentit la recompilation et gaspille le conteneur de bytecode pour des données que l’interpréteur va de toute façon simplement lire brutes. La page Construire une image ROMFS couvre le système de fichiers en lecture seule résidant en mémoire flash qui comble cette lacune.