14.2.2.2. Crear una imagen ROMFS

Una imagen ROMFS es un sistema de archivos de solo lectura residente en la memoria flash que el entorno de ejecución monta automáticamente en /rom. Resuelve el problema de los recursos con el que terminaba la página anterior: archivos de modelos de aprendizaje automático, tablas de etiquetas, configuración JSON, plantillas de imagen – cualquier cosa que la aplicación abra y lea pero nunca escriba – viaja a la compilación sin pagar el coste de estar incrustada como literales de Python.

Tres cosas hacen de ROMFS la herramienta adecuada para los recursos comercializados:

  • El sistema de archivos es parte de la imagen del firmware. Los usuarios finales no pueden eliminar un archivo de /rom, editarlo ni reemplazarlo por uno propio.

  • Los archivos en /rom son accesibles en su sitio. Consumidores como el módulo ml que carga un archivo de modelo obtienen una vista directa de la memoria flash sin copia en RAM – un modelo de varios megabytes en /rom se «carga» esencialmente gratis, mientras que el mismo archivo en /sdcard se lee a la RAM en el momento de la carga y permanece allí durante toda la vida de la referencia. Un open() + read ordinario copia bajo demanda: cada llamada read(n) copia n bytes de la memoria flash a la RAM en el momento de la llamada, y un read() sin argumentos pide el archivo completo.

  • /rom y /rom/lib se añaden a sys.path en el arranque. Los paquetes de Python colocados en la imagen son importables por nombre; nada especial en el lugar de la llamada.

14.2.2.2.1. Crear una imagen

Las imágenes ROMFS se crean, editan y flashean a través del IDE. Úsalo como la fuente de verdad para el contenido de cada partición ROMFS comercializada.

El motivo por el que esto importa: los archivos de modelos vienen con requisitos de alineación que el cargador hace cumplir en tiempo de ejecución. Los archivos .tflite deben rellenarse hasta límites de 16 bytes, y la NPU de la N6 requiere una alineación de 32 bytes para los modelos compilados. El IDE aplica ese relleno automáticamente cuando escribe la imagen. Las herramientas que recorren el árbol de fuentes sin aplicar el relleno – mpremote romfs en particular – producen una imagen que se monta sin problemas pero cuyos modelos fallan en la primera llamada de inferencia.

El editor de ROMFS del IDE es una vista interactiva del contenido de la imagen. Se pueden añadir, renombrar y eliminar archivos y carpetas en memoria; al guardar se escribe el resultado como un archivo .img listo para flashear. Una estructura típica para una aplicación que distribuye un modelo junto a algunos recursos y un paquete de Python tiene este aspecto:

model.tflite
labels.txt
config.json
templates/
    calibration.jpg
lib/
    mylib/
        __init__.py
        helpers.py

Truco

Tanto el IDE como mpremote compilan de forma cruzada los archivos .py a bytecode .mpy al introducirlos en una imagen ROMFS, de modo que la cámara los importa sin pagar el coste de análisis en el momento de la carga. Los archivos de origen en el editor permanecen como .py; la imagen contiene .mpy.

Una vez flasheada la imagen, el árbol es visible desde MicroPython en /rom/:

>>> import os
>>> os.listdir('/rom')
['model.tflite', 'labels.txt', 'config.json', 'templates', 'lib']
>>> import mylib
>>> mylib.helpers
<module 'mylib.helpers' from '/rom/lib/mylib/helpers.mpy'>

14.2.2.2.2. La mayor parte de la aplicación reside en ROMFS

ROMFS es el hogar adecuado para casi todo lo que distribuye una aplicación: las bibliotecas que importa, los archivos de modelos que carga, la configuración que lee, cualquier recurso cuya salida provenga de una herramienta de compilación que emita un árbol de archivos (convertidores de modelos, canalizaciones de imagen, empaquetadores de recursos) y – algo importante – el propio código de la aplicación.

El lado de los módulos congelados debería mantenerse pequeño: boot.py para la configuración previa al REPL, main.py como un punto de entrada delgado, y solo las bibliotecas sin las que la cámara genuinamente no puede arrancar. Todo lo demás va en ROMFS, donde iterar sobre ello es un .img nuevo guardado desde el IDE y reflasheado – sin necesidad de recompilar el firmware, sin tener una cadena de herramientas a mano para hacerlo.

El patrón que surge es un main.py que no hace más que delegar en la aplicación residente en ROMFS:

# main.py (frozen)
import app
app.run()

# /rom/app/__init__.py (in ROMFS)
def run():
    ...

Un cambio en app es una edición de ROMFS y un reflasheo. La compilación del firmware permanece igual durante toda la vida del producto, a menos que algo del lado congelado tenga que cambiar realmente.