Archivos de manifiesto de MicroPython¶
Resumen¶
MicroPython tiene una característica que permite «congelar» código Python dentro del firmware, como alternativa a cargar el código desde el sistema de archivos.
Esto tiene los siguientes beneficios:
el código se precompila a bytecode, evitando la necesidad de compilar el código fuente Python en el momento de la carga.
el bytecode puede ejecutarse directamente desde la ROM (es decir, la memoria flash) en lugar de copiarse en la RAM. De manera similar, cualquier objeto constante (cadenas, tuplas, etc.) también se carga desde la ROM. Esto puede dejar disponible significativamente más memoria para tu aplicación.
en dispositivos que no tienen un sistema de archivos, esta es la única forma de cargar código Python.
Durante el desarrollo, congelar generalmente no es recomendable, ya que ralentizará considerablemente tu ciclo de desarrollo, pues cada actualización requerirá volver a programar todo el firmware. Sin embargo, todavía puede ser útil congelar de forma selectiva algunas dependencias que rara vez cambian (como bibliotecas de terceros).
La forma de listar los archivos Python que se van a congelar en el firmware es mediante un «manifiesto», que es un archivo Python que será interpretado por el proceso de compilación. Normalmente escribirías un archivo de manifiesto como parte de una definición de placa, pero también puedes escribir un archivo de manifiesto independiente y usarlo con una definición de placa existente.
Los archivos de manifiesto pueden definir dependencias de bibliotecas de micropython-lib, así como de archivos Python en el sistema de archivos, y también de otros archivos de manifiesto.
Escribir archivos de manifiesto¶
Un archivo de manifiesto es un archivo Python que contiene una serie de llamadas a funciones. Consulta las funciones disponibles definidas a continuación.
Cualquier ruta utilizada en los archivos de manifiesto puede incluir las siguientes variables. Todas estas se resuelven a rutas absolutas.
$(MPY_DIR)– ruta al repositorio de micropython.$(MPY_LIB_DIR)– ruta al submódulo micropython-lib. Es preferible usarrequire().$(PORT_DIR)– ruta al puerto actual (p. ej.ports/stm32)$(BOARD_DIR)– ruta a la placa actual (p. ej.ports/stm32/boards/OPENMV4)
Los archivos de manifiesto personalizados no deben residir en el repositorio principal de MicroPython. Debes mantenerlos bajo control de versiones junto con el resto de tu proyecto.
Normalmente, un manifiesto utilizado para compilar firmware necesitará incluir el manifiesto del puerto, que puede incluir módulos congelados que son necesarios para que la placa funcione. Si solo quieres añadir módulos adicionales a una placa existente, entonces incluye el manifiesto de la placa (que a su vez incluirá el manifiesto del puerto).
Compilar con un manifiesto personalizado¶
Tu manifiesto puede especificarse en la línea de comandos de make con:
$ make BOARD=MYBOARD FROZEN_MANIFEST=/path/to/my/project/manifest.py
Esto aplica a todos los puertos, incluidos los basados en CMake (p. ej. rp2), ya que el envoltorio del Makefile lo pasará a la compilación de CMake.
Añadir un manifiesto a una definición de placa¶
Si tienes una definición de placa personalizada, puedes hacer que incluya tu manifiesto personalizado automáticamente. En los puertos basados en make (la mayoría de los puertos), establece la variable FROZEN_MANIFEST en tu mpconfigboard.mk.
FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py
En los puertos basados en CMake (p. ej. rp2), usa en su lugar mpconfigboard.cmake
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
Funciones de alto nivel¶
Estas son las funciones que normalmente usarás. Añaden código al conjunto que se precompila a bytecode y se congela en la imagen del firmware:
moduleypackagecongelan tu propio código fuente local: un único archivo o un directorio de paquete completo, respectivamente.requirecongela un paquete publicado (y sus dependencias) de micropython-lib, por nombre.includeincorpora otro manifiesto para que sus módulos congelados también se añadan.add_libraryymetadatason funciones de apoyo (registran rutas de búsqueda adicionales pararequirey declaran metadatos del paquete).
Un manifiesto de firmware típico primero hace include del manifiesto del puerto o de la placa (para que los módulos que la placa necesita permanezcan congelados) y luego añade sus propias líneas de module/package/require.
Nota: El argumento de palabra clave opt puede establecerse en las distintas funciones; esto controla el nivel de optimización utilizado por el compilador cruzado. Consulta micropython.opt_level().
- add_library(library, library_path, prepend=False)¶
Registra la ruta a una biblioteca externa con nombre.
Usa esto cuando quieras que
requireresuelva paquetes desde un directorio distinto de micropython-lib, por ejemplo tu propia colección de controladores, o una copia de trabajo de una biblioteca de terceros.La ruta library_path se buscará automáticamente al usar
require. De forma predeterminada, la biblioteca añadida se agrega al final de la lista de bibliotecas en las que buscar. PasaTruepara anteponerla y añadirla al inicio de la lista.Además, la biblioteca añadida puede solicitarse explícitamente usando
require("name", library="library").
- package(package_path, files=None, base_path='.', opt=None)¶
Congela un paquete completo: un directorio de archivos
.py(opcionalmente con subpaquetes), de modo que pueda importarse comoimport <package>. Usamoduleen su lugar para un único archivo independiente.Esto equivale a copiar el directorio «package_path» al dispositivo (salvo que como código congelado).
En el caso más sencillo, para congelar un paquete «foo» en el directorio actual:
package("foo")incluirá de forma recursiva todos los archivos .py en foo, y se congelarán como
foo/**/*.py.Si el paquete no está en el mismo directorio que el archivo de manifiesto, usa
base_path:package("foo", base_path="path/to/libraries")Puedes usar las variables anteriores, como
$(PORT_DIR)enbase_path.Para restringir a ciertos archivos del paquete usa
files(nota: las rutas deben ser relativas al paquete):package("foo", files=["bar/baz.py"]).
- module(module_path, base_path='.', opt=None)¶
Congela un único archivo
.pyindependiente para que pueda importarse por su nombre (module("foo.py")hace que funcioneimport foo). Usapackagepara un directorio/paquete.Si el archivo está en el directorio actual:
module("foo.py")De lo contrario, usa base_path para localizar el archivo:
module("foo.py", base_path="src/drivers")Puedes usar las variables anteriores, como
$(PORT_DIR)enbase_path.
- require(name, library=None)¶
Requiere un paquete por nombre (y sus dependencias) de micropython-lib.
Así es como se congelan las extensiones de la biblioteca estándar y los controladores de la comunidad: el paquete nombrado se obtiene del submódulo micropython-lib y se congela junto con todo aquello de lo que depende. Usa
moduleopackageen su lugar para congelar tu propio código fuente en vez de un paquete publicado.Opcionalmente, especifica library (una cadena) para referenciar un paquete de una biblioteca que se haya registrado previamente con
add_library. De lo contrario, se usará la lista de rutas de bibliotecas.
- include(manifest_path)¶
Incluye otro manifiesto. Así es como se componen los manifiestos: un manifiesto de firmware personalizado debe hacer
includedel manifiesto del puerto (o de la placa) para que los módulos que la placa necesita permanezcan congelados, y luego añadir sus propias entradas.Normalmente, un manifiesto utilizado para compilar firmware necesitará incluir el manifiesto del puerto, que puede incluir módulos congelados que son necesarios para que la placa funcione.
El argumento manifest puede ser una cadena (nombre de archivo) o un iterable de cadenas.
Las rutas relativas se resuelven con respecto al archivo de manifiesto actual.
Si la ruta apunta a un directorio, entonces incluye implícitamente el archivo manifest.py que hay dentro de ese directorio.
Puedes usar las variables anteriores, como
$(PORT_DIR)enmanifest_path.
- metadata(description=None, version=None, license=None, author=None)¶
Define metadatos para este archivo de manifiesto. Esto es útil para los manifiestos de paquetes de micropython-lib.
Estos campos se utilizan cuando un paquete se publica en / instala desde micropython-lib mediante mip; no son necesarios en un manifiesto de firmware de placa.
Funciones de bajo nivel¶
Estas funciones se documentan para que la referencia esté completa, pero con la excepción de freeze_as_str toda la funcionalidad puede accederse mediante las funciones de alto nivel.
Las funciones freeze* difieren únicamente en cómo se almacena el código:
freeze_as_mpy/freeze_mpyalmacenan bytecode precompilado (.mpy) en la memoria flash. El código se ejecuta directamente desde la flash, usa una cantidad mínima de RAM y se importa rápidamente. Esto es lo que usan internamentemodule,packageyrequire.freeze_as_stren cambio congela el código fuente Python, que se compila a bytecode en el momento de la importación (usando RAM y requiriendo el compilador del dispositivo). Esta es la única capacidad no expuesta por las funciones de alto nivel, motivo por el cual es la excepción señalada anteriormente.
- freeze(path, script=None, opt=0)¶
La primitiva subyacente sobre la que se construyen las funciones de alto nivel; es preferible usar aquellas. Congela la entrada especificada por path, determinando automáticamente su tipo. Un script
.pyse compilará primero a.mpyy luego se congelará, y un archivo.mpyse congelará directamente.path debe ser un directorio, que es el directorio base desde el que empezar a buscar archivos. Al importar los módulos congelados resultantes, el nombre del módulo empezará después de path, es decir, path se excluye del nombre del módulo.
Si path es relativa, se resuelve con respecto al
manifest.pyactual.Si script es None, se congelarán todos los archivos de path.
Si script es un iterable, entonces se llama a
freeze()sobre todos los elementos del iterable (pasando el mismo path y opt).Si script es una cadena, entonces especifica el archivo o directorio que se va a congelar, y puede incluir directorios adicionales antes del archivo o del último directorio. El archivo o directorio se buscará en path. Si script es un directorio, entonces se congelarán todos los archivos de ese directorio.
opt es el nivel de optimización que se pasa a mpy-cross al compilar
.pya.mpy. Estos niveles se describen enmicropython.opt_level().
- freeze_as_str(path)¶
Congela el path dado y todos los scripts
.pyque contiene como cadena, que se compilará al importarse. Usa esto solo cuando el código congelado deba seguir siendo código fuente Python; conlleva un coste de RAM en el momento de la importación en comparación con las variantes.mpy.
- freeze_as_mpy(path, script=None, opt=0)¶
Congela la entrada compilando primero los scripts
.pya archivos.mpyy luego congelando los archivos.mpyresultantes. Esto es lo que hacenmoduleypackageinternamente. Consultafreeze()para más detalles sobre los argumentos.
- freeze_mpy(path, script=None, opt=0)¶
Congela la entrada, que debe ser archivos
.mpyque se congelan directamente (sin paso de compilación). Consultafreeze()para más detalles sobre los argumentos.
Ejemplos¶
Para congelar un único archivo del directorio actual que estará disponible como import mydriver, usa:
module("mydriver.py")
Para congelar un directorio de archivos en un subdirectorio «mydriver» del directorio actual que estará disponible como import mydriver, usa:
package("mydriver")
Para congelar la biblioteca «hmac» de micropython-lib, usa:
require("hmac")
Un ejemplo más completo de un archivo manifest.py personalizado (para una placa que tiene su propio manifiesto predeterminado) es:
# Include the board's default manifest.
include("$(BOARD_DIR)/manifest.py")
# Add a custom driver
module("mydriver.py")
# Add aiorepl from micropython-lib
require("aiorepl")
Entonces la placa puede compilarse con
$ cd ports/stm32
$ make BOARD=MYBOARD FROZEN_MANIFEST=~/src/myproject/manifest.py
Ten en cuenta que la mayoría de las placas no tienen su propio manifest.py, sino que usan el del puerto directamente, en cuyo caso tu manifiesto simplemente debería hacer include("$(PORT_DIR)/boards/manifest.py").