Lavorare con ROMFS

Panoramica

ROMFS (Read-Only Memory Filesystem) è un filesystem leggero e di sola lettura progettato per i dispositivi MicroPython. È ottimizzato per microcontrollori e sistemi embedded in cui il codice e i dati devono essere memorizzati nella memoria flash e accessibili in modo efficiente senza essere copiati nella RAM.

I principali vantaggi di ROMFS sono:

  • Import zero-copy: i file di bytecode .mpy memorizzati in un ROMFS possono essere eseguiti direttamente dalla memoria flash (memory-mapped) anziché essere prima copiati nella RAM. Questo è simile al funzionamento dei moduli congelati, ma non richiede di riprogrammare l’intero firmware.

  • Basso consumo di RAM: gli oggetti costanti (stringhe, byte, ecc.) nei file .mpy caricati da ROMFS sono referenziati direttamente dalla flash, non duplicati nella RAM.

  • Distribuzione flessibile: un’immagine ROMFS può essere costruita su un PC host e distribuita al dispositivo usando mpremote, senza ricompilare il firmware.

  • Interfaccia filesystem standard: un ROMFS viene montato nel VFS ed è accessibile tramite le normali operazioni sui file di Python (open, os.listdir, import, ecc.).

ROMFS è complementare sia ai filesystem FAT/LittleFS in lettura-scrittura (che risiedono in altre partizioni della flash) sia ai moduli congelati (che sono compilati nel firmware stesso).

Supporto delle schede

ROMFS è abilitato nel firmware OpenMV su ogni scheda camera che dispone di una partizione ROMFS riservata nel proprio layout della flash. Su queste schede la partizione ROMFS viene rilevata automaticamente all’avvio e montata su /rom; sia /rom che /rom/lib vengono aggiunti a sys.path in modo che i moduli memorizzati lì possano essere importati direttamente.

Scheda

Supporto ROMFS

OpenMV Cam N6

OpenMV AE3

OpenMV Cam RT1062

OpenMV Cam Pure Thermal

OpenMV Cam M4 / M7 / H7 / H7 Plus

Arduino Giga

Arduino Portenta H7

Arduino Nicla Vision

Arduino Nano 33 BLE Sense

No (nessuna partizione ROMFS)

Arduino Nano RP2040 Connect

No (nessuna partizione ROMFS)

Vedi romfs per un helper specifico di OpenMV che ispeziona il ROMFS montato su /rom.

Flusso di lavoro

Il tipico flusso di lavoro per usare ROMFS è:

  1. Crea una directory sul tuo PC con i file Python (o i file .mpy) che vuoi distribuire.

  2. Usa mpremote romfs deploy <directory> per costruire e distribuire l’immagine ROMFS al dispositivo.

  3. Il ROMFS verrà montato su /rom al successivo avvio (o può essere montato immediatamente se il dispositivo viene riavviato).

  4. Il codice Python sul dispositivo può quindi usare import sui moduli dal ROMFS proprio come da qualsiasi altro filesystem.

Ad esempio:

# On the host PC, with a directory "myapp/" containing app.py:
$ mpremote romfs deploy myapp/

Dopo un soft-reset, il dispositivo avrà /rom/app.py (o /rom/app.mpy se mpy_cross è installato) disponibile per l’import.

Vedi la sezione sotto-comandi mpremote romfs qui sotto per tutti i dettagli sui sotto-comandi di mpremote.

API Python

L’API Python di ROMFS è fornita tramite il modulo vfs.

class vfs.VfsRom(buffer)

Crea un oggetto filesystem ROMFS da buffer, che deve essere un oggetto che supporta il protocollo buffer (ad es. un oggetto bytes, bytearray o memoryview) contenente un’immagine ROMFS valida.

Il costruttore verifica che buffer inizi con i byte magici di ROMFS (b"\xd2\xcd\x31"). Se il buffer è troppo piccolo o non è un ROMFS valido, viene sollevato OSError(ENODEV).

Gli oggetti creati da questo costruttore possono essere montati usando vfs.mount().

Esempio:

import vfs

# Load a ROMFS image from flash into a memoryview.
dev = vfs.rom_ioctl(2, 0)   # get partition 0 as a memoryview
fs = vfs.VfsRom(dev)
vfs.mount(fs, '/rom')

Oppure, per montare un’immagine ROMFS memorizzata in un file:

import vfs

with open('/flash/app.romfs', 'rb') as f:
    romfs_data = f.read()
fs = vfs.VfsRom(romfs_data)
vfs.mount(fs, '/rom2')

I seguenti metodi sono disponibili su un oggetto VfsRom:

VfsRom.open(path, mode)

Apre un file dal ROMFS. Sono supportate solo le modalità di lettura ('', 'r', 'rt', 'rb'). Tentare di aprire un file in scrittura solleverà OSError(EROFS).

L’oggetto file restituito supporta read(), seek(), tell() e close(). Per i file binari aperti in modalità di lettura, l’oggetto restituito supporta anche il protocollo buffer in modo da poter ottenere una memoryview dei dati del file, che fa riferimento direttamente alla memoria del ROMFS (zero-copy).

VfsRom.ilistdir(path)

Restituisce un iteratore sulle voci nella directory path. Ogni voce è una tupla (name, type, inode, size) dove type è 0x8000 per un file o 0x4000 per una directory.

VfsRom.stat(path)

Restituisce una 10-tupla simile a os.stat per path. Solleva OSError(ENOENT) se il percorso non esiste.

VfsRom.statvfs(path)

Restituisce le statistiche del filesystem. La dimensione del blocco è riportata come 1 e il numero di blocchi rappresenta la dimensione totale dell’immagine ROMFS in byte. I blocchi liberi e i file liberi sono sempre 0 (filesystem di sola lettura).

VfsRom.chdir(path)

Cambia directory all’interno del ROMFS. È supportata solo la radice ('/'); il passaggio a qualsiasi sottodirectory solleva OSError(EOPNOTSUPP).

VfsRom.getcwd()

Restituisce la directory di lavoro corrente all’interno del ROMFS. Restituisce sempre '/'.

vfs.rom_ioctl(op, ...)

Interfaccia di basso livello per accedere alle partizioni di memoria di sola lettura (ROM) del dispositivo.

Le operazioni supportate sono:

  • vfs.rom_ioctl(1) – Restituisce il numero di partizioni ROM disponibili.

  • vfs.rom_ioctl(2, id) – Restituisce la partizione ROM con indice id come oggetto memoryview. La memoria può essere letta ma non scritta direttamente.

  • vfs.rom_ioctl(3, id, length) – Prepara una partizione ROM per la scrittura. Cancella i primi length byte della partizione con indice id. Restituisce la dimensione minima di scrittura in byte (l’allineamento richiesto per le scritture successive).

  • vfs.rom_ioctl(4, id, offset, buf) – Scrive buf (un oggetto bytes-like) nella partizione ROM con indice id all’offset in byte offset.

  • vfs.rom_ioctl(5, id) – Completa una sequenza di scrittura nella partizione id (esegue qualsiasi finalizzazione necessaria dopo la scrittura, come lo svuotamento della cache).

Queste operazioni sono usate internamente da mpremote per distribuire le immagini ROMFS. La maggior parte degli utenti non ha bisogno di chiamare direttamente vfs.rom_ioctl().

Esempio (interrogazione delle partizioni disponibili):

import vfs

n = vfs.rom_ioctl(1)
print("Number of ROM partitions:", n)
for i in range(n):
    dev = vfs.rom_ioctl(2, i)
    print(f"  Partition {i}: {len(dev)} bytes")

Montaggio automatico all’avvio

Quando il supporto ROMFS è abilitato nel firmware, MicroPython tenterà automaticamente di montare la prima partizione ROM su /rom durante l’inizializzazione. Se la partizione contiene un’immagine ROMFS valida, viene montata e sia /rom che /rom/lib vengono aggiunti automaticamente a sys.path.

Questo significa che dopo aver distribuito un’immagine ROMFS con mpremote, un soft-reset è sufficiente per rendere importabili i nuovi moduli.

Se nella partizione non viene trovata alcuna immagine ROMFS valida (ad es. su una scheda appena programmata), il montaggio viene saltato silenziosamente.

Usare mpremote per gestire ROMFS

Lo strumento mpremote fornisce tre sotto-comandi per gestire le immagini ROMFS su un dispositivo connesso.

romfs query

$ mpremote romfs query

Elenca tutte le partizioni ROMFS disponibili sul dispositivo e le loro dimensioni. Mostra anche i primi 12 byte di ogni partizione in esadecimale e indica se è presente un’immagine ROMFS valida.

Output di esempio:

ROMFS0 partition has size 131072 bytes (32 blocks of 4096 bytes each)
  Raw contents: d2:cd:31:XX:XX:XX:XX:XX:XX:XX:XX:XX ...
  ROMFS image size: 1234

romfs build

$ mpremote romfs [-o <output>] build <source>

Costruisce un’immagine ROMFS dalla directory source sul PC host. L’immagine viene scritta in output (predefinito: <source>.romfs).

Opzioni:

  • -o <output>, --output <output>: specifica il percorso del file di output.

  • -m, --mpy (predefinito): compila automaticamente i file .py in .mpy usando mpy_cross prima di aggiungerli all’immagine. Richiede il pacchetto Python mpy_cross (pip install mpy_cross).

  • --no-mpy: disabilita la compilazione automatica dei file .py.

Esempio:

$ mpremote romfs build myapp/
Building romfs filesystem, source directory: myapp/
/
|-- main.py -> .mpy
\-- lib/
    \-- helper.py -> .mpy
Writing 2048 bytes to output file myapp.romfs

romfs deploy

$ mpremote romfs [-p <partition>] deploy <source>

Distribuisce un’immagine ROMFS al dispositivo. source può essere:

  • Una directory sull’host: l’immagine ROMFS viene costruita in memoria e distribuita direttamente.

  • Un file .romfs o .img: l’immagine viene letta dal disco e distribuita.

Opzioni:

  • -p <partition>, --partition <partition>: specifica l’indice della partizione di destinazione (predefinito: 0).

  • -m, --mpy (predefinito): compila .py in .mpy quando source è una directory.

  • --no-mpy: disabilita la compilazione automatica dei file .py.

Dopo la distribuzione, il dispositivo deve essere sottoposto a soft-reset affinché il nuovo ROMFS venga montato su /rom.

Esempio:

$ mpremote romfs deploy myapp/
Building romfs filesystem, source directory: myapp/
/
|-- main.py -> .mpy
\-- lib/
    \-- helper.py -> .mpy
Image size is 2048 bytes
ROMFS0 partition has size 131072 bytes (32 blocks of 4096 bytes each)
Preparing ROMFS0 partition for writing
Deploying ROMFS to ROMFS0 partition
ROMFS image deployed

$ mpremote soft-reset

Esempi

Distribuire una semplice applicazione

Supponiamo di avere una directory di progetto myapp/ con la seguente struttura:

myapp/
    main.py
    utils.py
    lib/
        helper.py

Per distribuirla nel ROMFS del dispositivo:

$ mpremote romfs deploy myapp/

Dopo un soft-reset, i moduli sono importabili dal ROMFS:

import main
import utils
from lib import helper

Elencare il contenuto di ROMFS da Python

Dopo il montaggio, il contenuto del ROMFS può essere esplorato come qualsiasi altro filesystem:

import os

for entry in os.ilistdir('/rom'):
    print(entry)

# Or simply:
print(os.listdir('/rom'))

OpenMV include anche un piccolo helper romfs che stampa un elenco formattato comprendente l’indirizzo memory-mapped e l’allineamento di ogni file:

from omv import romfs
romfs.ls_romfs()

Annidare un ROMFS all’interno di un ROMFS

Un’immagine ROMFS memorizzata come file all’interno di un ROMFS esterno può essere montata come filesystem annidato. Ad esempio, se /rom/inner.romfs esiste. Poiché /rom è un ROMFS, gli oggetti file aperti da esso supportano il protocollo buffer, quindi è possibile ottenere direttamente una memoryview zero-copy:

import vfs

with open('/rom/inner.romfs', 'rb') as f:
    inner = vfs.VfsRom(memoryview(f))
vfs.mount(inner, '/inner')

print(os.listdir('/inner'))

Formato dell’immagine ROMFS

Il formato dell’immagine ROMFS è un formato binario compatto progettato per l’accesso memory-mapped sui microcontrollori. Una breve panoramica:

  • L’immagine inizia con i byte magici 0xd2 0xcd 0x31 (codificati come "RM1" con i bit alti dei primi due byte impostati).

  • Il resto dell’immagine è composto da record, ciascuno con un tag di tipo (varuint), una lunghezza (varuint) e un payload.

  • I tipi di record includono: padding, dati verbatim, puntatore a dati indiretto, directory, file.

  • I nomi di directory e file sono memorizzati come stringhe di byte con prefisso di lunghezza.

  • I dati dei file possono essere memorizzati verbatim (inline) o tramite un puntatore indiretto a un’altra posizione nell’immagine, il che consente l’allineamento per l’accesso memory-mapped.

  • I tipi di record sconosciuti vengono saltati silenziosamente, garantendo la compatibilità in avanti.

Questo formato è definito in extmod/vfs_rom.c nel codice sorgente di MicroPython. L’implementazione Python usata da mpremote per costruire le immagini si trova in tools/mpremote/mpremote/romfs.py.

Vedi anche

Lavorare con i filesystem – Panoramica del VFS di MicroPython e dei tipi di filesystem disponibili.

File manifest di MicroPython – Come congelare i moduli Python nel firmware.

File .mpy di MicroPython – Formato dei file binari .mpy di MicroPython.

Controllo remoto di MicroPython: mpremote – Il riferimento completo dei comandi di mpremote.

romfs – Helper OpenMV per ispezionare il filesystem /rom montato.