Ficheiros de manifesto MicroPython¶
Resumo¶
O MicroPython tem uma funcionalidade que permite «congelar» código Python no firmware, como alternativa ao carregamento de código a partir do sistema de ficheiros.
Isto tem as seguintes vantagens:
o código é pré-compilado para bytecode, evitando a necessidade de compilar o código-fonte Python no momento do carregamento.
o bytecode pode ser executado diretamente a partir de ROM (ou seja, memória flash) em vez de ser copiado para RAM. Da mesma forma, quaisquer objetos constantes (strings, tuplos, etc.) também são carregados a partir de ROM. Isto pode resultar em significativamente mais memória disponível para a sua aplicação.
em dispositivos que não têm sistema de ficheiros, esta é a única forma de carregar código Python.
Durante o desenvolvimento, congelar código geralmente não é recomendado, pois irá abrandar significativamente o seu ciclo de desenvolvimento, uma vez que cada atualização exigirá re-flashar o firmware completo. No entanto, pode ainda ser útil congelar seletivamente algumas dependências que raramente mudam (como bibliotecas de terceiros).
A forma de listar os ficheiros Python a serem congelados no firmware é através de um «manifesto», que é um ficheiro Python que será interpretado pelo processo de compilação. Normalmente, escreveria um ficheiro de manifesto como parte de uma definição de placa, mas também pode escrever um ficheiro de manifesto autónomo e utilizá-lo com uma definição de placa existente.
Os ficheiros de manifesto podem definir dependências em bibliotecas de micropython-lib, bem como em ficheiros Python no sistema de ficheiros, e também noutros ficheiros de manifesto.
Escrita de ficheiros de manifesto¶
Um ficheiro de manifesto é um ficheiro Python que contém uma série de chamadas de função. Veja as funções disponíveis definidas abaixo.
Qualquer caminho utilizado nos ficheiros de manifesto pode incluir as seguintes variáveis. Todas resolvem para caminhos absolutos.
$(MPY_DIR)– caminho para o repositório micropython.$(MPY_LIB_DIR)– caminho para o submódulo micropython-lib. Prefira usarrequire().$(PORT_DIR)– caminho para a porta atual (ex:ports/stm32)$(BOARD_DIR)– caminho para a placa atual (ex:ports/stm32/boards/OPENMV4)
Os ficheiros de manifesto personalizados não devem residir no repositório principal do MicroPython. Deve mantê-los em controlo de versões juntamente com o resto do seu projeto.
Normalmente, um manifesto utilizado para compilar firmware precisará de incluir o manifesto da porta, que poderá incluir módulos congelados necessários para o funcionamento da placa. Se apenas pretende adicionar módulos adicionais a uma placa existente, inclua então o manifesto da placa (que por sua vez incluirá o manifesto da porta).
Compilação com um manifesto personalizado¶
O seu manifesto pode ser especificado na linha de comandos do make com:
$ make BOARD=MYBOARD FROZEN_MANIFEST=/path/to/my/project/manifest.py
Isto aplica-se a todas as portas, incluindo as baseadas em CMake (ex: rp2), pois o envólucro Makefile irá passá-lo para a compilação CMake.
Adicionar um manifesto a uma definição de placa¶
Se tiver uma definição de placa personalizada, pode fazer com que inclua automaticamente o seu manifesto personalizado. Em portas baseadas em make (a maioria das portas), no seu mpconfigboard.mk defina a variável FROZEN_MANIFEST.
FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py
Em portas baseadas em CMake (ex: rp2), utilize mpconfigboard.cmake em alternativa
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
Funções de alto nível¶
Estas são as funções que normalmente utilizará. Adicionam código ao conjunto que é pré-compilado para bytecode e congelado na imagem do firmware:
moduleepackagecongelam o seu próprio código-fonte local — um único ficheiro ou um diretório de pacote completo, respetivamente.requirecongela um pacote publicado (e as suas dependências) de micropython-lib, pelo nome.includeincorpora outro manifesto, de modo a que os seus módulos congelados sejam também adicionados.add_libraryemetadatasão funções de suporte (para registar caminhos de pesquisa adicionais pararequire, e declarar metadados de pacote).
Um manifesto de firmware típico começa por include o manifesto da porta ou da placa (para que os módulos de que a placa necessita permaneçam congelados), e depois acrescenta as suas próprias linhas module/package/require.
Nota: O argumento de palavra-chave opt pode ser definido nas várias funções; controla o nível de otimização utilizado pelo compilador cruzado. Consulte micropython.opt_level().
- add_library(library, library_path, prepend=False)¶
Regista o caminho para uma biblioteca externa com nome.
Utilize isto quando pretender que
requireresolva pacotes a partir de um diretório diferente de micropython-lib — por exemplo, a sua própria coleção de drivers, ou um checkout de uma biblioteca de terceiros.O caminho library_path será automaticamente pesquisado ao utilizar
require. Por predefinição, a biblioteca adicionada é inserida no final da lista de bibliotecas a pesquisar. PasseTruepara prepend para adicioná-la ao início da lista.Adicionalmente, a biblioteca adicionada pode ser explicitamente solicitada utilizando
require("name", library="library").
- package(package_path, files=None, base_path='.', opt=None)¶
Congela um pacote completo — um diretório de ficheiros
.py(opcionalmente com sub-pacotes) — para que possa ser importado comoimport <package>. Utilizemodulepara um único ficheiro autónomo.Isto é equivalente a copiar o diretório «package_path» para o dispositivo (exceto como código congelado).
No caso mais simples, para congelar um pacote «foo» no diretório atual:
package("foo")irá incluir recursivamente todos os ficheiros .py em foo, e serão congelados como
foo/**/*.py.Se o pacote não estiver no mesmo diretório que o ficheiro de manifesto, utilize
base_path:package("foo", base_path="path/to/libraries")Pode utilizar as variáveis acima, como
$(PORT_DIR)embase_path.Para restringir a determinados ficheiros no pacote, utilize
files(nota: os caminhos devem ser relativos ao pacote):package("foo", files=["bar/baz.py"]).
- module(module_path, base_path='.', opt=None)¶
Congela um único ficheiro
.pyautónomo para que possa ser importado pelo seu nome (module("foo.py")faz funcionarimport foo). Utilizepackagepara um diretório/pacote.Se o ficheiro estiver no diretório atual:
module("foo.py")Caso contrário, utilize base_path para localizar o ficheiro:
module("foo.py", base_path="src/drivers")Pode utilizar as variáveis acima, como
$(PORT_DIR)embase_path.
- require(name, library=None)¶
Requer um pacote pelo nome (e as suas dependências) de micropython-lib.
É assim que as extensões de biblioteca padrão e os drivers da comunidade são congelados: o pacote com o nome indicado é obtido do submódulo micropython-lib e congelado juntamente com tudo aquilo de que depende. Utilize
moduleoupackagepara congelar o seu próprio código-fonte em vez de um pacote publicado.Opcionalmente, especifique library (uma string) para referenciar um pacote a partir de uma biblioteca previamente registada com
add_library. Caso contrário, será utilizada a lista de caminhos de biblioteca.
- include(manifest_path)¶
Inclui outro manifesto. É assim que os manifestos são compostos: um manifesto de firmware personalizado deve
includeo manifesto da porta (ou da placa) para que os módulos de que a placa necessita permaneçam congelados, e depois adicionar as suas próprias entradas.Normalmente, um manifesto utilizado para compilar firmware precisará de incluir o manifesto da porta, que poderá incluir módulos congelados necessários para o funcionamento da placa.
O argumento manifest pode ser uma string (nome de ficheiro) ou um iterável de strings.
Os caminhos relativos são resolvidos em relação ao ficheiro de manifesto atual.
Se o caminho for um diretório, inclui implicitamente o ficheiro manifest.py dentro desse diretório.
Pode utilizar as variáveis acima, como
$(PORT_DIR)emmanifest_path.
- metadata(description=None, version=None, license=None, author=None)¶
Define metadados para este ficheiro de manifesto. Isto é útil para manifestos de pacotes micropython-lib.
Estes campos são consumidos quando um pacote é publicado em / instalado a partir de micropython-lib via mip; não são necessários num manifesto de firmware de placa.
Funções de baixo nível¶
Estas funções estão documentadas por razões de completude, mas com exceção de freeze_as_str, toda a funcionalidade pode ser acedida através das funções de alto nível.
As funções freeze* diferem apenas na forma como o código é armazenado:
freeze_as_mpy/freeze_mpyarmazenam bytecode pré-compilado (.mpy) em flash. O código é executado diretamente a partir de flash, utiliza RAM mínima e importa rapidamente. É o quemodule,packageerequireutilizam internamente.freeze_as_strcongela o código-fonte Python, que é compilado para bytecode no momento da importação (utilizando RAM e exigindo o compilador no dispositivo). Esta é a única capacidade não exposta pelas funções de alto nível, razão pela qual é a exceção mencionada acima.
- freeze(path, script=None, opt=0)¶
A primitiva subjacente em que as funções de alto nível se baseiam; prefira essas. Congela a entrada especificada por path, determinando automaticamente o seu tipo. Um script
.pyserá primeiro compilado para.mpye depois congelado, e um ficheiro.mpyserá congelado diretamente.path deve ser um diretório, que é o diretório base a partir do qual se começa a pesquisar ficheiros. Ao importar os módulos congelados resultantes, o nome do módulo começará após path, ou seja, path é excluído do nome do módulo.
Se path for relativo, é resolvido em relação ao
manifest.pyatual.Se script for None, todos os ficheiros em path serão congelados.
Se script for um iterável, então
freeze()é chamado em todos os itens do iterável (com o mesmo path e opt passados).Se script for uma string, especifica o ficheiro ou diretório a congelar, podendo incluir diretórios extra antes do ficheiro ou do último diretório. O ficheiro ou diretório será pesquisado em path. Se script for um diretório, todos os ficheiros nesse diretório serão congelados.
opt é o nível de otimização a passar ao mpy-cross ao compilar
.pypara.mpy. Estes níveis são descritos emmicropython.opt_level().
- freeze_as_str(path)¶
Congela o path indicado e todos os scripts
.pydentro dele como uma string, que será compilada aquando da importação. Utilize isto apenas quando o código congelado deve permanecer em código-fonte Python; tem um custo de RAM no momento da importação em comparação com as variantes.mpy.
- freeze_as_mpy(path, script=None, opt=0)¶
Congela a entrada compilando primeiro os scripts
.pypara ficheiros.mpy, e depois congelando os ficheiros.mpyresultantes. É o quemoduleepackagefazem internamente. Consultefreeze()para mais detalhes sobre os argumentos.
- freeze_mpy(path, script=None, opt=0)¶
Congela a entrada, que deve ser ficheiros
.mpyque são congelados diretamente (sem etapa de compilação). Consultefreeze()para mais detalhes sobre os argumentos.
Exemplos¶
Para congelar um único ficheiro do diretório atual que estará disponível como import mydriver, utilize:
module("mydriver.py")
Para congelar um diretório de ficheiros numa subdiretório «mydriver» do diretório atual que estará disponível como import mydriver, utilize:
package("mydriver")
Para congelar a biblioteca «hmac» de micropython-lib, utilize:
require("hmac")
Um exemplo mais completo de um ficheiro manifest.py personalizado (para uma placa que tem o seu próprio manifesto predefinido) é:
# 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")
Depois a placa pode ser compilada com
$ cd ports/stm32
$ make BOARD=MYBOARD FROZEN_MANIFEST=~/src/myproject/manifest.py
Note que a maioria das placas não tem o seu próprio manifest.py, mas sim utiliza diretamente o da porta; nesse caso, o seu manifesto deve apenas utilizar include("$(PORT_DIR)/boards/manifest.py").