14.2.2.2. Construindo uma imagem ROMFS

Uma imagem ROMFS é um sistema de arquivos somente leitura, residente na flash, que o runtime monta automaticamente em /rom. Ela resolve o problema dos ativos com que a página anterior terminou: arquivos de modelo de aprendizado de máquina, tabelas de rótulos, configuração JSON, modelos de imagem – qualquer coisa que a aplicação abre e lê, mas nunca grava – entram na compilação sem pagar o custo de serem embutidos como literais Python.

Três coisas tornam o ROMFS a ferramenta certa para ativos entregues:

  • O sistema de arquivos é parte da imagem do firmware. Os usuários finais não podem apagar um arquivo de /rom, editá-lo ou substituí-lo por um próprio.

  • Os arquivos em /rom são acessíveis no local. Consumidores como o módulo ml carregando um arquivo de modelo obtêm uma visão direta na flash, sem cópia para a RAM – um modelo de vários megabytes em /rom é “carregado” essencialmente de graça, ao passo que o mesmo arquivo em /sdcard é lido para a RAM no momento do carregamento e permanece lá durante toda a vida útil da referência. O open() + read comum copia sob demanda: cada chamada read(n) copia n bytes da flash para a RAM no momento da chamada, com um read() simples pedindo o arquivo inteiro.

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

14.2.2.2.1. Construindo uma imagem

As imagens ROMFS são criadas, editadas e gravadas através da IDE. Use-a como a fonte da verdade para o conteúdo de toda partição ROMFS entregue.

O motivo pelo qual isso importa: os arquivos de modelo vêm com requisitos de alinhamento que o carregador impõe em tempo de execução. Arquivos .tflite precisam ser preenchidos até limites de 16 bytes, e a NPU do N6 exige alinhamento de 32 bytes para modelos compilados. A IDE aplica esse preenchimento automaticamente ao gravar a imagem. Ferramentas que percorrem a árvore de código-fonte sem aplicar o preenchimento – o mpremote romfs em particular – produzem uma imagem que monta corretamente, mas cujos modelos falham na primeira chamada de inferência.

O editor ROMFS da IDE é uma visão interativa do conteúdo da imagem. Arquivos e pastas podem ser adicionados, renomeados e excluídos em memória; ao salvar, o resultado é gravado como um arquivo .img pronto para gravação. Uma estrutura típica para uma aplicação que entrega um modelo junto a alguns ativos e a um pacote Python se parece com:

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

Dica

Tanto a IDE quanto o mpremote compilam de forma cruzada os arquivos .py para bytecode .mpy no caminho de entrada para uma imagem ROMFS, de modo que a câmera os importa sem pagar o custo de parsing no carregamento. Os arquivos de origem no editor permanecem .py; a imagem contém .mpy.

Uma vez gravada a imagem, a árvore fica 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 vive no ROMFS

O ROMFS é o lar certo para quase tudo que uma aplicação entrega: as bibliotecas que ela importa, os arquivos de modelo que ela carrega, a configuração que ela lê, qualquer ativo cuja saída veio de uma ferramenta de compilação que emite uma árvore de arquivos (conversores de modelo, pipelines de imagem, empacotadores de ativos) e – importante – o próprio código da aplicação.

O lado dos módulos congelados deve permanecer pequeno: boot.py para a configuração pré-REPL, main.py como um ponto de entrada enxuto e apenas as bibliotecas sem as quais a câmera genuinamente não consegue dar boot. Todo o resto vai para o ROMFS, onde iterar sobre ele é um novo .img salvo a partir da IDE e regravado – sem necessidade de recompilar o firmware, sem necessidade de ter um toolchain à mão para isso.

O padrão que emerge é um main.py que não faz nada além de delegar para a aplicação residente no ROMFS:

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

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

Uma alteração em app é uma edição do ROMFS e uma regravação. A compilação do firmware permanece estável durante toda a vida útil do produto, a menos que algo no lado congelado realmente precise mudar.