14.3.3. Higiene del sistema de archivos¶
El almacenamiento en memoria flash y en tarjeta SD de una cámara puesta en producción se llena de archivos que ningún operador va a limpiar a mano. Dos decisiones sobre ese almacenamiento acompañan al producto durante toda su vida útil: qué superficie guarda qué tipo de datos y cómo se estructuran los directorios para que las operaciones de archivos sigan funcionando a medida que la aplicación acumula registros.
14.3.3.1. Dónde va cada cosa¶
El código y los recursos viajan en los módulos congelados y en el ROMFS que la compilación fija en el momento del envío. El estado de la aplicación – todo lo que la aplicación escribe en tiempo de ejecución, todo lo que crece, todo lo que cambia entre arranques – tiene que vivir en otro lugar. La cámara expone dos superficies de escritura para ello:
Memoria flash interna en
/flash: un pequeño sistema de archivos de escritura que se monta antes de que se ejecute cualquier código de la aplicación. El lugar adecuado para registros pequeños de tamaño fijo que sobreviven a los reinicios: la configuración que la aplicación actualiza en tiempo de ejecución, la última calibración conocida, un contador acumulativo, un archivo marcador de una sola línea que diga «esta cámara fue aprovisionada». Ciclos de escritura limitados – la memoria flash interna moderna tolera de miles a decenas de miles de escrituras por sector, no millones, así que las escrituras deben ser poco frecuentes, no por fotograma.Tarjeta SD en
/sdcard: un sistema de archivos de escritura más grande que se monta cuando hay una tarjeta presente. El lugar adecuado para archivos voluminosos y de tamaño variable: capturas de imagen y vídeo, archivos de registro, datos de ajuste fino de modelos, cualquier cosa que pueda crecer hasta megabytes o gigabytes. Mayor capacidad de escritura que la memoria flash interna, pero aún finita; extraíble, reemplazable y la superficie con más probabilidades de desaparecer mientras la aplicación está escribiendo.
La respuesta correcta a dónde escribir algo es casi siempre «flash para registros pequeños de tamaño fijo, SD para todo lo demás.» Las dos no son intercambiables: una aplicación que vuelca su archivo de registro acumulativo en /flash agotará la resistencia de escritura de la flash en una instalación que habría estado bien en la SD.
14.3.3.2. Trata ambas como susceptibles de fallar¶
Tanto /flash como /sdcard pueden fallar. La tarjeta SD se puede extraer, la flash se puede corromper por un corte de energía a mitad de una escritura, cualquiera de las dos se puede quedar sin espacio, y cualquier operación sobre cualquiera de ellas puede lanzar OSError por razones que la aplicación no tendrá ocasión de diagnosticar sobre el terreno.
Dos patrones hacen que la aplicación sobreviva a eso:
Envuelve los montajes y las operaciones en bloques try. Cada
open(),os.listdir(),os.rename()contra rutas de datos de usuario puede fallar. CapturaOSError, regístralo y recurre a una alternativa definida – escribe en/flashsi/sdcardya no está, omite la operación si ninguna está disponible.Escrituras atómicas para archivos que deben sobrevivir a un corte de energía. Escribe en una ruta temporal, cierra el manejador y luego usa
os.rename()sobre el nombre activo. O bien el renombrado tuvo éxito y el archivo es la nueva versión, o no lo tuvo y el archivo es la versión antigua. No hay un tercer estado en el que el archivo quede escrito a medias:import os def write_config_atomic(path, contents): tmp = path + '.tmp' with open(tmp, 'w') as f: f.write(contents) f.flush() os.rename(tmp, path)
El patrón funciona tanto en flash como en SD. No funciona para archivos lo suficientemente grandes como para que el archivo tmp consuma el espacio libre del sistema de archivos; resérvalo para registros pequeños.
14.3.3.3. La trampa del directorio lento¶
El VFS de MicroPython no indexa el contenido de los directorios como lo hace el sistema de archivos de un ordenador de escritorio. os.listdir() y os.stat() recorren la tabla de archivos subyacente de forma lineal. Un directorio con cien archivos está bien; un directorio con diez mil archivos es inutilizablemente lento, con cada os.listdir() tardando segundos y cada open() comprobando contra la tabla a su paso.
Las aplicaciones que escriben registros o capturas en disco son las que más rápido topan con esto. Un esquema ingenuo /sdcard/logs/<timestamp>.log que abre un archivo nuevo por minuto llena el directorio logs/ con medio millón de archivos en un año de despliegue. Mucho antes de eso, la aplicación empieza a no alcanzar su tasa de fotogramas porque cada apertura de archivo tarda más que un intervalo entre fotogramas.
El patrón correcto es repartir los archivos por un árbol de subdirectorios fechados, de modo que ningún directorio contenga jamás más de unos pocos cientos de entradas:
import os
import time
LOG_ROOT = '/sdcard/logs'
def log_path(now=None):
if now is None:
now = time.localtime()
year, month, day, hour = now[0], now[1], now[2], now[3]
directory = '{}/{:04d}/{:02d}/{:02d}'.format(
LOG_ROOT, year, month, day)
_makedirs(directory)
return '{}/{:02d}.log'.format(directory, hour)
def _makedirs(path):
# os.makedirs equivalent -- create each level if missing
parts = path.split('/')
for i in range(2, len(parts) + 1):
sub = '/'.join(parts[:i])
try:
os.mkdir(sub)
except OSError:
pass
Un año de registro de un archivo por hora queda ahora repartido en 365 directorios diarios, cada uno con como mucho 24 archivos; os.listdir() sobre cualquiera de esos directorios sigue siendo barato, y el bucle de fotogramas de la aplicación no se atasca en las operaciones de archivos a medida que el despliegue envejece.
El mismo principio se aplica a las capturas de imagen, las trazas del sensor o cualquier otra cosa para la que la aplicación escriba un archivo por evento. Si la tasa de eventos es alta, el árbol conviene que sea más profundo (año/mes/día/hora, o año/mes/día/hora/minuto) para que cada directorio hoja se mantenga pequeño. Si la tasa de eventos es baja, basta con un árbol año/mes.
14.3.3.4. Rutas por dispositivo¶
En una flota de más de una cámara, los archivos de registro necesitan identificar de qué unidad física provienen. machine.unique_id() devuelve un identificador de hardware grabado en la cámara en fábrica; es el mismo valor entre reinicios, entre actualizaciones de firmware y entre cambios de tarjeta SD. Incrústalo en la ruta del registro o en los propios registros y un operador que mire un montón de tarjetas SD o un registro centralizado podrá distinguir cuál es cuál:
import binascii
import machine
UNIT_ID = binascii.hexlify(machine.unique_id()).decode()
LOG_ROOT = '/sdcard/logs/' + UNIT_ID
Combinado con el patrón de subdirectorios fechados, la estructura queda como /sdcard/logs/<unit-id>/2026/06/09/14.log – la hora de registros de una unidad, en un directorio lo bastante superficial como para recorrerlo, sobre una ruta que nombra la unidad en el propio sistema de archivos.
14.3.3.5. Juntándolo todo¶
El almacenamiento de escritura de una cámara en producción tiene más o menos este aspecto:
/flash– configuración, calibración, un marcador de aprovisionamiento. Se escribe rara vez, se lee a menudo. Patrón de renombrado atómico para cualquier archivo cuya pérdida rompería el siguiente arranque./sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log– el registro operativo. Se escribe de forma continua, se rota mediante la ruta, nunca se escribe a través de un directorio con miles de hermanos./sdcard/captures/<unit-id>/<year>/<month>/<day>/– las capturas de imagen o vídeo que hace la aplicación. La misma forma de árbol, la misma razón.
Esa estructura le cuesta a la aplicación unas veinte líneas de código y la salva de los modos de fallo que tumban la cámara meses después de iniciado un despliegue.