14.2.2.2. Criar uma imagem ROMFS

Uma imagem ROMFS é um sistema de ficheiros residente em flash, de leitura apenas, que o runtime monta automaticamente em /rom. Resolve o problema dos recursos com que a página anterior terminou: ficheiros de modelos de machine learning, tabelas de etiquetas, configuração JSON, modelos de imagem – qualquer coisa que a aplicação abre e lê mas nunca escreve – entra na compilação sem pagar o custo de ser incorporada como literais Python.

Três aspetos fazem da ROMFS a ferramenta certa para recursos expedidos:

  • O sistema de ficheiros é parte da imagem de firmware. Os utilizadores finais não podem apagar um ficheiro de /rom, editá-lo nem substituí-lo pelo seu.

  • Os ficheiros em /rom são acessíveis no local. Consumidores como o módulo ml a carregar um ficheiro de modelo obtêm uma vista direta para a flash sem cópia em RAM – um modelo de vários megabytes em /rom «carrega» essencialmente de graça, enquanto o mesmo ficheiro em /sdcard é lido para RAM no momento do carregamento e fica lá durante o tempo de vida da referência. O open() comum + read copia a pedido: cada chamada read(n) copia n bytes da flash para RAM no momento da chamada, sendo que um read() simples pede o ficheiro inteiro.

  • /rom e /rom/lib são adicionados a sys.path no arranque. Os pacotes Python colocados na imagem são importáveis pelo nome; nada de especial no local de chamada.

14.2.2.2.1. Criar uma imagem

As imagens ROMFS são criadas, editadas e gravadas através do IDE. Utilize-o como fonte de verdade para o conteúdo de cada partição ROMFS expedida.

O motivo pelo qual isto é importante: os ficheiros de modelo vêm com requisitos de alinhamento que o carregador em runtime impõe. Os ficheiros .tflite têm de ser preenchidos para limites de 16 bytes, e o NPU do N6 exige alinhamento de 32 bytes para modelos compilados. O IDE aplica esse preenchimento automaticamente quando escreve a imagem. Ferramentas que percorrem a árvore de código-fonte sem aplicar o preenchimento – mpremote romfs em particular – produzem uma imagem que monta corretamente mas cujos modelos falham na primeira chamada de inferência.

O editor ROMFS do IDE é uma vista interativa do conteúdo da imagem. Ficheiros e pastas podem ser adicionados, renomeados e eliminados em memória; guardar escreve o resultado como um ficheiro .img pronto a gravar. Uma estrutura típica para uma aplicação que expede um modelo junto com alguns recursos e um pacote Python tem este aspeto:

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

Dica

Tanto o IDE como o mpremote compilam cruzadamente ficheiros .py para bytecode .mpy ao inseri-los numa imagem ROMFS, pelo que a câmara os importa sem pagar o custo de análise no momento do carregamento. Os ficheiros de código-fonte no editor ficam como .py; a imagem contém .mpy.

Depois de a imagem ser gravada, a árvore é visível a partir do MicroPython em /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. A maior parte da aplicação reside em ROMFS

A ROMFS é o lugar certo para quase tudo o que uma aplicação expede: as bibliotecas que importa, os ficheiros de modelo que carrega, a configuração que lê, qualquer recurso cuja saída veio de uma ferramenta de compilação que emite uma árvore de ficheiros (conversores de modelos, pipelines de imagem, empacotadores de recursos) e – importantly – o próprio código da aplicação.

O lado dos módulos congelados deve permanecer pequeno: boot.py para configuração pré-REPL, main.py como ponto de entrada leve, e apenas as bibliotecas que a câmara genuinamente não consegue arrancar sem. Tudo o resto vai para a ROMFS, onde iterar significa guardar um novo .img do IDE e regravar – sem necessidade de recompilar o firmware, sem precisar de ter uma toolchain disponível.

O padrão que emerge é um main.py que não faz nada mais do que delegar para a aplicação residente em ROMFS:

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

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

Uma alteração ao app é uma edição à ROMFS e uma regravação. A compilação do firmware mantém-se para o tempo de vida do produto a menos que algo no lado congelado realmente precise de mudar.