14.3.3. Fájlrendszer-higiénia

Egy kiszállított kamera flash- és SD-tárolója megtelik olyan fájlokkal, amelyeket egyetlen kezelő sem fog kézzel kitakarítani. Két döntés ezzel a tárolóval kapcsolatban a termék egész élettartamára megmarad: melyik felület tárolja az adatok melyik fajtáját, és hogyan épülnek fel a könyvtárak, hogy a fájlműveletek továbbra is működjenek, ahogy az alkalmazás egyre több rekordot halmoz fel.

14.3.3.1. Hová kerülnek a dolgok

A kód és az erőforrások a fagyasztott modulokban és a ROMFS-ben utaznak, amelyeket a build a kiszállítás idején rögzít. Az alkalmazás állapotának – mindennek, amit az alkalmazás futás közben ír, mindennek, ami növekszik, mindennek, ami az indítások között változik – máshol kell laknia. A kamera két írható felületet kínál erre:

  • Belső flash memória a /flash útvonalon: egy kis írható fájlrendszer, amely az alkalmazás kódjának futása előtt csatolódik. A megfelelő hely kis, fix méretű, újraindítást túlélő rekordok számára: az alkalmazás által futás közben frissített konfiguráció, a legutóbbi ismert kalibráció, egy gördülő számláló, egy egysoros jelzőfájl, amely azt mondja: „ezt a kamerát provisionálták”. Korlátozott írási ciklusok – a modern belső flash memória szektoronként több ezertől több tízezerig terjedő írást visel el, nem milliókat, így az írásoknak ritkáknak kell lenniük, nem képkockánkéntinek.

  • SD-kártya a /sdcard útvonalon: egy nagyobb írható fájlrendszer, amely akkor csatolódik, amikor kártya van jelen. A megfelelő hely terjedelmes, változó méretű fájlok számára: kép- és videófelvételek, naplófájlok, modell-finomhangolási adatok, bármi, ami megabájtokra vagy gigabájtokra nőhet. Nagyobb írási kapacitás, mint a belső flash memóriáé, de még mindig véges; eltávolítható, cserélhető, és az a felület, amely a legvalószínűbben eltűnik, amikor az alkalmazás éppen írás közben van.

A megfelelő válasz arra, hogy hová írjunk valamit, szinte mindig az, hogy „flash a kis, fix rekordoknak, SD minden másnak”. A kettő nem cserélhető fel: egy olyan alkalmazás, amely a gördülő naplófájlját a /flash felületre firkálja, fel fogja emészteni a flash írási élettartamát egy olyan telepítésben, amely SD-n teljesen rendben lett volna.

14.3.3.2. Mindkettőt kezelje hibázhatóként

A /flash és a /sdcard egyaránt hibázhat. Az SD-kártyát ki lehet venni, a flash megsérülhet írás közbeni áramkimaradás miatt, mindkettő kifogyhat a helyből, és bármelyiken végzett bármely művelet OSError hibát válthat ki olyan okokból, amelyeket az alkalmazás a terepen nem fog tudni diagnosztizálni.

Két minta segít az alkalmazásnak túlélni ezt:

  • A csatolásokat és műveleteket try blokkokba csomagolja. Minden open(), os.listdir(), os.rename() a felhasználói adatok útvonalain potenciálisan hibázhat. Kapja el az OSError hibát, naplózza, és térjen vissza egy meghatározott alternatívához – írjon a /flash felületre, ha a /sdcard eltűnt, hagyja ki a műveletet, ha egyik sem elérhető.

  • Atomi írások az áramkimaradást túlélni köteles fájlokhoz. Írjon egy ideiglenes útvonalra, zárja le a leírót, majd os.rename() művelettel írja felül az élő nevet. Vagy az átnevezés sikerült és a fájl az új verzió, vagy nem sikerült és a fájl a régi verzió. Nincs harmadik állapot, amelyben a fájl félig van megírva:

    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)
    

    A minta flash-en és SD-n egyaránt működik. Nem működik olyan fájlokra, amelyek elég nagyok ahhoz, hogy az ideiglenes fájl felhasználja a fájlrendszer szabad helyét; tartsa fenn kis rekordokhoz.

14.3.3.3. A lassú könyvtár csapdája

A MicroPython VFS nem indexeli a könyvtárak tartalmát úgy, ahogy egy asztali fájlrendszer teszi. Az os.listdir() és az os.stat() lineárisan járja végig a mögöttes fájltáblát. Egy száz fájlt tartalmazó könyvtár rendben van; egy tízezer fájlt tartalmazó könyvtár használhatatlanul lassú, ahol minden os.listdir() másodperceket vesz igénybe, és minden open() áthaladás közben ellenőrzi a táblát.

Azok az alkalmazások, amelyek naplókat vagy felvételeket írnak lemezre, érik el ezt a leggyorsabban. Egy naiv /sdcard/logs/<timestamp>.log séma, amely percenként nyit egy új fájlt, egy év telepítés alatt félmillió fájllal tölti meg a logs/ könyvtárat. Jóval ez előtt az alkalmazás elkezdi elveszíteni a képkockasebességét, mert minden fájlmegnyitás tovább tart, mint egy képkocka-intervallum.

A megfelelő minta az, hogy a fájlokat dátumozott alkönyvtárak fája között osztja szét, így egyetlen könyvtár sem tartalmaz soha néhány száznál több bejegyzést:

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

Egy év óránkénti egy fájlt jelentő naplózás immár 365 napi könyvtárra van elosztva, amelyek mindegyike legfeljebb 24 fájlt tartalmaz; az os.listdir() bármely egyetlen könyvtáron olcsó marad, és az alkalmazás képkocka-ciklusa nem akad el a fájlműveleteken, ahogy a telepítés öregszik.

Ugyanez az elv vonatkozik a képfelvételekre, az érzékelő nyomvonalaira, vagy bármi másra, amihez az alkalmazás eseményenként egy fájlt ír. Ha az eseményráta magas, a fának mélyebbnek kell lennie (év/hónap/nap/óra, vagy év/hónap/nap/óra/perc), hogy minden levélkönyvtár kicsi maradjon. Ha az eseményráta alacsony, egy év/hónap fa elég.

14.3.3.4. Eszközönkénti útvonalak

Egy egynél több kamerából álló flottában a naplófájloknak azonosítaniuk kell, melyik fizikai egységből származnak. A machine.unique_id() egy hardveres azonosítót ad vissza, amelyet a gyárban sütöttek a kamerába; ez ugyanaz az érték az újraindítások során, a firmware-frissítések során és az SD-kártyacserék során. Ágyazza be a naplóútvonalba vagy a naplórekordokba, és egy kezelő, aki egy halom SD-kártyát vagy egy központosított naplót néz, meg tudja mondani, melyik melyik:

import binascii
import machine

UNIT_ID = binascii.hexlify(machine.unique_id()).decode()

LOG_ROOT = '/sdcard/logs/' + UNIT_ID

A dátumozott alkönyvtár-mintával kombinálva az elrendezés a következővé válik: /sdcard/logs/<unit-id>/2026/06/09/14.log – egy egység egyórányi rekordja, egy elég sekély könyvtárban a bejáráshoz, egy olyan útvonalon, amely magán a fájlrendszeren megnevezi az egységet.

14.3.3.5. Mindezt összerakva

Egy kiszállított kamera írható tárolója nagyjából így néz ki:

  • /flash – konfiguráció, kalibráció, egy provisioning-jelző. Ritkán írt, gyakran olvasott. Atomi-átnevezés minta minden olyan fájlhoz, amelynek elvesztése megakadályozná a következő indítást.

  • /sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log – a működési napló. Folyamatosan írt, az útvonal által forgatott, soha nem több ezer testvérrel rendelkező könyvtáron keresztül írt.

  • /sdcard/captures/<unit-id>/<year>/<month>/<day>/ – az alkalmazás által készített kép- vagy videófelvételek. Ugyanaz a faalak, ugyanaz az ok.

Ez az elrendezés körülbelül húsz sornyi kódba kerül az alkalmazásnak, és megmenti azoktól a hibamódoktól, amelyek a telepítés kezdete után hónapokkal leveszik a kamerát a lábáról.