14.3.3. Dateisystem-Hygiene¶
Flash- und SD-Speicher einer ausgelieferten Kamera füllen sich mit Dateien, die kein Bediener von Hand aufräumen wird. Zwei Entscheidungen über diesen Speicher begleiten das Produkt für seine gesamte Lebensdauer: welcher Datenträger welche Art von Daten aufnimmt und wie die Verzeichnisse strukturiert sind, damit Dateioperationen weiterhin funktionieren, während die Anwendung Datensätze ansammelt.
14.3.3.1. Wo die Dinge hingehören¶
Code und Assets reisen in den eingefrorenen Modulen und in ROMFS mit, die der Build zur Auslieferungszeit festschreibt. Der Zustand der Anwendung – alles, was die Anwendung zur Laufzeit schreibt, alles, was wächst, alles, was sich zwischen den Bootvorgängen ändert – muss irgendwo anders untergebracht werden. Die Kamera stellt dafür zwei beschreibbare Datenträger bereit:
Interner Flash unter
/flash: ein kleines beschreibbares Dateisystem, das eingehängt wird, bevor irgendein Anwendungscode läuft. Der richtige Ort für kleine Datensätze mit fester Größe, die Neustarts überdauern: Konfiguration, welche die Anwendung zur Laufzeit aktualisiert, die zuletzt bekannte Kalibrierung, ein fortlaufender Zähler, eine einzeilige Markierungsdatei mit der Aussage „diese Kamera wurde bereitgestellt“. Begrenzte Schreibzyklen – moderner interner Flash verträgt Tausende bis Zehntausende von Schreibvorgängen pro Sektor, nicht Millionen, daher müssen Schreibvorgänge selten erfolgen, nicht pro Einzelbild.SD-Karte unter
/sdcard: ein größeres beschreibbares Dateisystem, das eingehängt wird, wenn eine Karte vorhanden ist. Der richtige Ort für große, variable Dateien: Bild- und Videoaufnahmen, Logdateien, Daten für das Feintuning von Modellen, alles, was auf Megabyte oder Gigabyte anwachsen kann. Höhere Schreibkapazität als der interne Flash, aber dennoch endlich; entfernbar, austauschbar und der Datenträger, der am ehesten verschwindet, während die Anwendung mitten in einem Schreibvorgang steckt.
Die richtige Antwort darauf, wohin etwas geschrieben werden soll, lautet fast immer „Flash für kleine feste Datensätze, SD für alles andere“. Die beiden sind nicht austauschbar: Eine Anwendung, die ihre rotierende Logdatei nach /flash kritzelt, verbraucht in einem Einsatz, der auf SD problemlos gewesen wäre, die Schreibfestigkeit des Flash.
14.3.3.2. Beide als ausfallgefährdet behandeln¶
/flash und /sdcard können beide ausfallen. Die SD-Karte kann ausgeworfen werden, der Flash kann durch einen Stromausfall mitten im Schreibvorgang beschädigt werden, beiden kann der Speicherplatz ausgehen, und jede Operation auf beiden kann aus Gründen, die die Anwendung im Feld nicht diagnostizieren kann, einen OSError auslösen.
Zwei Muster sorgen dafür, dass die Anwendung das übersteht:
Mounts und Operationen in try-Blöcke einpacken. Jedes
open(),os.listdir(),os.rename()auf Pfaden mit Benutzerdaten kann potenziell fehlschlagen. Fangen SieOSErrorab, protokollieren Sie ihn und greifen Sie auf eine definierte Alternative zurück – nach/flashschreiben, falls/sdcardnicht mehr da ist, die Operation überspringen, falls keines von beiden verfügbar ist.Atomare Schreibvorgänge für Dateien, die einen Stromausfall überstehen müssen. Schreiben Sie auf einen temporären Pfad, schließen Sie das Handle und führen Sie dann
os.rename()über den aktiven Namen aus. Entweder ist die Umbenennung gelungen und die Datei ist die neue Version, oder sie ist es nicht und die Datei ist die alte Version. Es gibt keinen dritten Zustand, in dem die Datei halb geschrieben ist: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)
Das Muster funktioniert sowohl auf Flash als auch auf SD. Es funktioniert nicht für Dateien, die groß genug sind, dass die tmp-Datei den freien Speicherplatz des Dateisystems aufbraucht; reservieren Sie es für kleine Datensätze.
14.3.3.3. Die Falle des langsamen Verzeichnisses¶
Das MicroPython-VFS indiziert Verzeichnisinhalte nicht so, wie es ein Desktop-Dateisystem tut. os.listdir() und os.stat() durchlaufen die zugrunde liegende Dateitabelle linear. Ein Verzeichnis mit hundert Dateien ist in Ordnung; ein Verzeichnis mit zehntausend Dateien ist unbrauchbar langsam, wobei jedes os.listdir() Sekunden dauert und jedes open() auf seinem Weg die Tabelle durchprüft.
Anwendungen, die Logs oder Aufnahmen auf die Festplatte schreiben, treffen darauf am schnellsten. Ein naives /sdcard/logs/<timestamp>.log-Schema, das jede Minute eine neue Datei öffnet, füllt das Verzeichnis logs/ in einem Jahr Einsatz mit einer halben Million Dateien. Lange davor beginnt die Anwendung, ihre Bildrate zu verfehlen, weil jedes Öffnen einer Datei länger dauert als ein Einzelbildintervall.
Das richtige Muster besteht darin, Dateien über einen Baum von datierten Unterverzeichnissen aufzuteilen, sodass kein einzelnes Verzeichnis jemals mehr als ein paar hundert Einträge enthält:
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
Ein Jahr Protokollierung mit einer Datei pro Stunde verteilt sich nun über 365 Tagesverzeichnisse, die jeweils höchstens 24 Dateien enthalten; os.listdir() auf ein beliebiges einzelnes Verzeichnis bleibt günstig, und die Einzelbildschleife der Anwendung gerät bei Dateioperationen nicht ins Stocken, während der Einsatz altert.
Dasselbe Prinzip gilt für Bildaufnahmen, Sensor-Traces oder alles andere, wofür die Anwendung pro Ereignis eine Datei schreibt. Ist die Ereignisrate hoch, sollte der Baum tiefer sein (Jahr/Monat/Tag/Stunde oder Jahr/Monat/Tag/Stunde/Minute), damit jedes Blattverzeichnis klein bleibt. Ist die Ereignisrate niedrig, genügt ein Jahr/Monat-Baum.
14.3.3.4. Gerätespezifische Pfade¶
In einer Flotte von mehr als einer Kamera müssen Logdateien angeben, von welcher physischen Einheit sie stammen. machine.unique_id() gibt eine Hardwarekennung zurück, die der Kamera im Werk fest einprogrammiert wurde; sie ist über Neustarts, über Firmware-Updates und über SD-Kartenwechsel hinweg derselbe Wert. Betten Sie sie in den Logpfad oder in die Logdatensätze ein, und ein Bediener, der auf einen Stapel SD-Karten oder ein zentrales Log schaut, kann erkennen, welche welche ist:
import binascii
import machine
UNIT_ID = binascii.hexlify(machine.unique_id()).decode()
LOG_ROOT = '/sdcard/logs/' + UNIT_ID
Kombiniert mit dem Muster der datierten Unterverzeichnisse wird das Layout zu /sdcard/logs/<unit-id>/2026/06/09/14.log – eine Stunde Datensätze einer Einheit, in einem Verzeichnis, das flach genug zum Durchlaufen ist, auf einem Pfad, der die Einheit im Dateisystem selbst benennt.
14.3.3.5. Alles zusammenführen¶
Der beschreibbare Speicher einer ausgelieferten Kamera sieht in etwa so aus:
/flash– Konfiguration, Kalibrierung, eine Bereitstellungsmarkierung. Selten geschrieben, oft gelesen. Atomares Umbenennungsmuster für jede Datei, deren Verlust den nächsten Bootvorgang stören würde./sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log– das Betriebslog. Kontinuierlich geschrieben, über den Pfad rotiert, niemals durch ein Verzeichnis mit Tausenden von Geschwistern geschrieben./sdcard/captures/<unit-id>/<year>/<month>/<day>/– Bild- oder Videoaufnahmen, die die Anwendung erstellt. Gleiche Baumstruktur, gleicher Grund.
Dieses Layout kostet die Anwendung etwa zwanzig Zeilen Code und bewahrt sie vor den Fehlermodi, die die Kamera Monate nach Einsatzbeginn lahmlegen.