14.3.3. Hygiène du système de fichiers¶
Sur une caméra livrée, la mémoire flash et le stockage SD se remplissent de fichiers qu’aucun opérateur ne viendra nettoyer à la main. Deux décisions concernant ce stockage accompagnent le produit tout au long de sa durée de vie : quelle surface contient quel type de données, et comment les répertoires sont structurés pour que les opérations sur les fichiers continuent de fonctionner à mesure que l’application accumule des enregistrements.
14.3.3.1. Où vont les choses¶
Le code et les ressources sont embarqués dans les modules figés et le ROMFS que la compilation fige au moment de la livraison. L”état de l’application – tout ce que l’application écrit à l’exécution, tout ce qui grossit, tout ce qui change d’un démarrage à l’autre – doit résider ailleurs. La caméra expose deux surfaces inscriptibles à cet effet :
Mémoire flash interne dans
/flash: un petit système de fichiers inscriptible monté avant l’exécution de tout code applicatif. C’est l’endroit idéal pour les petits enregistrements de taille fixe qui survivent aux redémarrages : la configuration que l’application met à jour à l’exécution, le dernier étalonnage connu, un compteur cumulatif, un fichier marqueur d’une ligne indiquant « cette caméra a été provisionnée ». Cycles d’écriture limités – la mémoire flash interne moderne tolère des milliers à des dizaines de milliers d’écritures par secteur, pas des millions, de sorte que les écritures doivent être peu fréquentes, et non par trame.Carte SD dans
/sdcard: un système de fichiers inscriptible plus grand, monté lorsqu’une carte est présente. C’est l’endroit idéal pour les fichiers volumineux et variables : captures d’images et de vidéos, fichiers journaux, données d’ajustement fin de modèle, tout ce qui peut atteindre des mégaoctets ou des gigaoctets. Capacité d’écriture supérieure à la mémoire flash interne, mais tout de même finie ; amovible, remplaçable, et la surface la plus susceptible de disparaître alors que l’application est en pleine écriture.
La bonne réponse à la question de où écrire quelque chose est presque toujours « la flash pour les petits enregistrements de taille fixe, la SD pour tout le reste ». Les deux ne sont pas interchangeables : une application qui griffonne son fichier journal cumulatif dans /flash épuisera l’endurance en écriture de la flash dans un déploiement qui se serait très bien passé sur SD.
14.3.3.2. Considérez les deux comme pouvant échouer¶
/flash et /sdcard peuvent tous deux échouer. La carte SD peut être éjectée, la flash peut être corrompue par une coupure de courant en pleine écriture, l’un comme l’autre peut tomber à court d’espace, et toute opération sur l’un ou l’autre peut lever une OSError pour des raisons que l’application n’aura pas l’occasion de diagnostiquer sur le terrain.
Deux modèles permettent à l’application de survivre à cela :
Enveloppez les montages et les opérations dans des blocs try. Chaque
open(),os.listdir(),os.rename()sur des chemins de données utilisateur est potentiellement défaillant. InterceptezOSError, journalisez-la, et repliez-vous sur une alternative définie – écrivez dans/flashsi/sdcarda disparu, ignorez l’opération si aucun des deux n’est disponible.Écritures atomiques pour les fichiers qui doivent survivre à une coupure de courant. Écrivez dans un chemin temporaire, fermez le descripteur, puis utilisez
os.rename()par-dessus le nom actif. Soit le renommage a réussi et le fichier est la nouvelle version, soit il a échoué et le fichier est l’ancienne version. Il n’existe pas de troisième état où le fichier serait écrit à moitié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)
Ce modèle fonctionne aussi bien sur la flash que sur la SD. Il ne fonctionne pas pour des fichiers suffisamment volumineux pour que le fichier tmp épuise l’espace libre du système de fichiers ; réservez-le aux petits enregistrements.
14.3.3.3. Le piège du répertoire lent¶
Le VFS de MicroPython n’indexe pas le contenu des répertoires comme le fait un système de fichiers de bureau. os.listdir() et os.stat() parcourent linéairement la table de fichiers sous-jacente. Un répertoire contenant une centaine de fichiers convient ; un répertoire contenant dix mille fichiers est d’une lenteur inutilisable, chaque os.listdir() prenant des secondes et chaque open() devant vérifier la table au passage.
Les applications qui écrivent des journaux ou des captures sur le disque rencontrent ce problème le plus rapidement. Un schéma naïf /sdcard/logs/<timestamp>.log qui ouvre un nouveau fichier par minute remplit le répertoire logs/ d’un demi-million de fichiers en une année de déploiement. Bien avant cela, l’application commence à manquer sa cadence d’images parce que chaque ouverture de fichier prend plus de temps qu’un intervalle entre trames.
Le bon modèle consiste à répartir les fichiers dans une arborescence de sous-répertoires datés afin qu’aucun répertoire ne contienne jamais plus de quelques centaines d’entrées
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
Une année de journalisation à raison d’un fichier par heure est désormais répartie sur 365 répertoires de jours, chacun contenant au maximum 24 fichiers ; os.listdir() sur n’importe quel répertoire reste peu coûteux, et la boucle de trames de l’application ne se bloque pas sur les opérations de fichiers à mesure que le déploiement vieillit.
Le même principe s’applique aux captures d’images, aux traces de capteurs, ou à tout autre élément pour lequel l’application écrit un fichier par événement. Si le taux d’événements est élevé, l’arborescence devra être plus profonde (année/mois/jour/heure, ou année/mois/jour/heure/minute) afin que chaque répertoire feuille reste petit. Si le taux d’événements est faible, une arborescence année/mois suffit.
14.3.3.4. Chemins par appareil¶
Dans un parc de plus d’une caméra, les fichiers journaux doivent identifier de quelle unité physique ils proviennent. machine.unique_id() renvoie un identifiant matériel inscrit dans la caméra à l’usine ; c’est la même valeur d’un redémarrage à l’autre, d’une mise à jour de micrologiciel à l’autre, et d’un échange de carte SD à l’autre. Intégrez-le dans le chemin du journal ou dans les enregistrements du journal, et un opérateur examinant une pile de cartes SD ou un journal centralisé pourra dire laquelle est laquelle
import binascii
import machine
UNIT_ID = binascii.hexlify(machine.unique_id()).decode()
LOG_ROOT = '/sdcard/logs/' + UNIT_ID
Combiné au modèle des sous-répertoires datés, l’agencement devient /sdcard/logs/<unit-id>/2026/06/09/14.log – une heure d’enregistrements d’une unité, dans un répertoire assez peu profond pour être parcouru, sur un chemin qui nomme l’unité dans le système de fichiers lui-même.
14.3.3.5. Mise en commun¶
Le stockage inscriptible d’une caméra livrée ressemble à peu près à ceci :
/flash– configuration, étalonnage, un marqueur de provisionnement. Écrit rarement, lu souvent. Modèle de renommage atomique pour tout fichier dont la perte casserait le prochain démarrage./sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log– le journal opérationnel. Écrit en continu, rotaté par le chemin, jamais écrit à travers un répertoire comptant des milliers de voisins./sdcard/captures/<unit-id>/<year>/<month>/<day>/– les captures d’images ou de vidéos réalisées par l’application. Même forme d’arborescence, même raison.
Cet agencement coûte à l’application une vingtaine de lignes de code et lui épargne les modes de défaillance qui mettent la caméra hors service des mois après le début d’un déploiement.