14.3.3. Higijena datotečnog sustava

Flash i SD pohrana na isporučenoj kameri pune se datotekama koje nijedan operater neće ručno čistiti. Dvije odluke o toj pohrani prate proizvod tijekom cijelog njegovog životnog vijeka: koja površina drži koju vrstu podataka i kako su strukturirani direktoriji tako da operacije s datotekama nastave raditi dok aplikacija gomila zapise.

14.3.3.1. Gdje stvari idu

Kod i resursi nalaze se u zamrznutim modulima i ROMFS-u koje build učvršćuje u trenutku isporuke. Stanje aplikacije – sve što aplikacija zapisuje tijekom rada, sve što raste, sve što se mijenja između pokretanja – mora živjeti negdje drugdje. Kamera za to izlaže dvije zapisive površine:

  • Interna flash memorija na /flash: mali zapisivi datotečni sustav montiran prije nego što se pokrene bilo kakav aplikacijski kod. Pravo mjesto za male zapise fiksne veličine koji preživljavaju ponovna pokretanja: konfiguraciju koju aplikacija ažurira tijekom rada, posljednju poznatu kalibraciju, brojač koji se rotira, jednoredni datotečni marker koji kaže „ova kamera je provisionirana”. Ograničen broj ciklusa zapisa – moderna interna flash memorija podnosi tisuće do desetke tisuća zapisa po sektoru, ne milijune, pa zapisi trebaju biti rijetki, a ne po svakoj sličici.

  • SD kartica na /sdcard: veći zapisivi datotečni sustav montiran kada je kartica prisutna. Pravo mjesto za glomazne promjenjive datoteke: snimke slika i videa, datoteke zapisnika, podatke za fino podešavanje modela, sve što bi moglo narasti na megabajte ili gigabajte. Veći kapacitet zapisa od interne flash memorije, ali i dalje konačan; uklonjiva, zamjenjiva i površina koja će najvjerojatnije nestati dok je aplikacija usred zapisivanja.

Pravi odgovor na pitanje kamo nešto zapisati gotovo je uvijek „flash za male fiksne zapise, SD za sve ostalo”. Te dvije površine nisu zamjenjive: aplikacija koja žvrlja svoju rotirajuću datoteku zapisnika u /flash potrošit će izdržljivost zapisa flash memorije u implementaciji koja bi na SD-u prošla dobro.

14.3.3.2. Tretirajte obje kao podložne kvaru

/flash i /sdcard mogu oboje zakazati. SD kartica može biti izbačena, flash memorija može biti oštećena gubitkom napajanja usred zapisivanja, bilo kojoj može ponestati prostora, a bilo koja operacija nad bilo kojom može podići OSError iz razloga koje aplikacija na terenu neće imati priliku dijagnosticirati.

Dva obrasca omogućuju aplikaciji da to preživi:

  • Omotajte montiranja i operacije u try blokove. Svaki open(), os.listdir(), os.rename() nad putanjama korisničkih podataka potencijalno može zakazati. Uhvatite OSError, zabilježite ga i vratite se na definiranu alternativu – zapišite u /flash ako je /sdcard nestao, preskočite operaciju ako nijedna nije dostupna.

  • Atomarni zapisi za datoteke koje moraju preživjeti gubitak napajanja. Zapišite u privremenu putanju, zatvorite handle, a zatim os.rename() preko aktivnog naziva. Ili je preimenovanje uspjelo i datoteka je nova verzija, ili nije i datoteka je stara verzija. Ne postoji treće stanje u kojem je datoteka napola zapisana:

    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)
    

    Obrazac radi i na flash memoriji i na SD-u. Ne radi za datoteke dovoljno velike da privremena datoteka iskoristi slobodan prostor datotečnog sustava; rezervirajte ga za male zapise.

14.3.3.3. Zamka sporog direktorija

MicroPython VFS ne indeksira sadržaj direktorija onako kako to radi stolni datotečni sustav. os.listdir() i os.stat() prolaze kroz osnovnu tablicu datoteka linearno. Direktorij sa stotinu datoteka je u redu; direktorij s deset tisuća datoteka je neupotrebljivo spor, gdje svaki os.listdir() traje sekunde i svaki open() provjerava tablicu na svom putu.

Aplikacije koje zapisuju zapisnike ili snimke na disk najbrže nailaze na ovo. Naivna shema /sdcard/logs/<timestamp>.log koja otvara jednu novu datoteku svake minute napuni direktorij logs/ s pola milijuna datoteka u godini implementacije. Mnogo prije toga aplikacija počinje propuštati svoju brzinu sličica jer svako otvaranje datoteke traje dulje od intervala između sličica.

Pravi obrazac je raspodijeliti datoteke kroz stablo datiranih poddirektorija tako da nijedan pojedini direktorij nikad ne drži više od nekoliko stotina unosa:

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

Godina dana zapisivanja jedne datoteke po satu sada je raspoređena kroz 365 direktorija po danu, od kojih svaki sadrži najviše 24 datoteke; os.listdir() nad bilo kojim pojedinim direktorijem ostaje jeftin, a petlja sličica aplikacije ne zastaje na operacijama s datotekama kako implementacija stari.

Isto načelo vrijedi za snimke slika, tragove senzora ili bilo što drugo za što aplikacija zapisuje datoteku po događaju. Ako je stopa događaja visoka, stablo treba biti dublje (godina/mjesec/dan/sat, ili godina/mjesec/dan/sat/minuta) tako da svaki krajnji direktorij ostane malen. Ako je stopa događaja niska, stablo godina/mjesec je dovoljno.

14.3.3.4. Putanje po uređaju

U floti od više od jedne kamere, datoteke zapisnika moraju identificirati iz koje su fizičke jedinice došle. machine.unique_id() vraća hardverski identifikator ugrađen u kameru u tvornici; to je ista vrijednost kroz ponovna pokretanja, kroz ažuriranja ugrađenog programa (firmware) i kroz zamjene SD kartica. Ugradite ga u putanju zapisnika ili u zapise zapisnika i operater koji gleda hrpu SD kartica ili centralizirani zapisnik može reći koja je koja:

import binascii
import machine

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

LOG_ROOT = '/sdcard/logs/' + UNIT_ID

U kombinaciji s obrascem datiranih poddirektorija, raspored postaje /sdcard/logs/<unit-id>/2026/06/09/14.log – sat zapisa jedne jedinice, u direktoriju dovoljno plitkom za prolazak, na putanji koja imenuje jedinicu na samom datotečnom sustavu.

14.3.3.5. Spajanje u cjelinu

Zapisiva pohrana isporučene kamere izgleda otprilike ovako:

  • /flash – konfiguracija, kalibracija, marker provisioniranja. Rijetko zapisivano, često čitano. Obrazac atomarnog preimenovanja za svaku datoteku čiji bi gubitak prekinuo sljedeće pokretanje.

  • /sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log – operativni zapisnik. Zapisivan kontinuirano, rotiran putanjom, nikad zapisivan kroz direktorij s tisućama srodnih unosa.

  • /sdcard/captures/<unit-id>/<year>/<month>/<day>/ – snimke slika ili videa koje aplikacija pravi. Isti oblik stabla, isti razlog.

Taj raspored košta aplikaciju oko dvadeset redaka koda i spašava je od načina kvarova koji obore kameru mjesecima nakon početka implementacije.