14.3.3. Tiedostojärjestelmän hygienia

Toimitetun kameran flash- ja SD-tallennustila täyttyy tiedostoista, joita kukaan käyttäjä ei aio siivota käsin. Kaksi tähän tallennustilaan liittyvää päätöstä seuraavat tuotetta sen koko eliniän ajan: mikä pinta säilyttää minkätyyppistä dataa ja miten hakemistot on rakennettu niin, että tiedosto-operaatiot toimivat edelleen sovelluksen kerätessä tietueita.

14.3.3.1. Mihin asiat menevät

Koodi ja resurssit kulkevat jäädytetyissä moduuleissa ja ROMFS:ssä, jotka käännös sitoo toimitushetkellä. Sovelluksen tilan – kaikki, mitä sovellus kirjoittaa ajon aikana, kaikki, mikä kasvaa, kaikki, mikä muuttuu käynnistysten välillä – on asuttava jossain muualla. Kamera tarjoaa sille kaksi kirjoitettavaa pintaa:

  • Sisäinen flash-muisti osoitteessa /flash: pieni kirjoitettava tiedostojärjestelmä, joka liitetään ennen kuin yhtään sovelluskoodia ajetaan. Oikea paikka pienille kiinteäkokoisille tietueille, jotka säilyvät uudelleenkäynnistysten yli: konfiguraatiolle, jota sovellus päivittää ajon aikana, viimeksi tunnetulle kalibroinnille, juoksevalle laskurille, yksiriviselle merkkitiedostolle, joka kertoo ”tämä kamera on alustettu”. Rajalliset kirjoitussyklit – moderni sisäinen flash-muisti kestää tuhansista kymmeniin tuhansiin kirjoituksiin sektoria kohti, ei miljoonia, joten kirjoitusten on oltava harvoja, ei kehyskohtaisia.

  • SD-kortti osoitteessa /sdcard: suurempi kirjoitettava tiedostojärjestelmä, joka liitetään, kun kortti on paikallaan. Oikea paikka suurikokoisille vaihteleville tiedostoille: kuva- ja videokaappauksille, lokitiedostoille, mallin hienosäätödatalle, kaikelle, mikä saattaa kasvaa megatavuihin tai gigatavuihin. Suurempi kirjoituskapasiteetti kuin sisäisellä flash-muistilla, mutta silti rajallinen; irrotettava, vaihdettava ja se pinta, joka todennäköisimmin katoaa sovelluksen ollessa kesken kirjoituksen.

Oikea vastaus siihen, mihin jokin kirjoitetaan, on lähes aina ”flash pienille kiinteille tietueille, SD kaikelle muulle”. Nämä kaksi eivät ole vaihdettavissa keskenään: sovellus, joka raapustaa juoksevan lokitiedostonsa hakemistoon /flash, kuluttaa flash-muistin kirjoituskestävyyden loppuun käyttöönotossa, joka olisi ollut aivan kunnossa SD:llä.

14.3.3.2. Käsittele molempia mahdollisina virhelähteinä

Sekä /flash että /sdcard voivat molemmat pettää. SD-kortti voidaan irrottaa, flash-muisti voi korruptoitua kesken kirjoituksen tapahtuvasta sähkökatkosta, kummasta tahansa voi loppua tila, ja mikä tahansa operaatio kummalla tahansa voi nostaa OSError-poikkeuksen syistä, joita sovellus ei pääse diagnosoimaan kentällä.

Kaksi mallia saa sovelluksen selviämään tästä:

  • Kääri liitokset ja operaatiot try-lohkoihin. Jokainen open(), os.listdir() ja os.rename() käyttäjädatan polkuja vastaan voi mahdollisesti epäonnistua. Nappaa OSError, kirjaa se lokiin ja palaudu määriteltyyn vaihtoehtoon – kirjoita hakemistoon /flash, jos /sdcard on poissa, tai ohita operaatio, jos kumpikaan ei ole käytettävissä.

  • Atomiset kirjoitukset tiedostoille, joiden on selvittävä sähkökatkoksesta. Kirjoita väliaikaiseen polkuun, sulje kahva ja sitten os.rename() elävän nimen päälle. Joko uudelleennimeäminen onnistui ja tiedosto on uusi versio, tai se ei onnistunut ja tiedosto on vanha versio. Ei ole kolmatta tilaa, jossa tiedosto olisi puoliksi kirjoitettu:

    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)
    

    Malli toimii sekä flashilla että SD:llä. Se ei toimi tiedostoille, jotka ovat niin suuria, että tmp-tiedosto käyttää tiedostojärjestelmän vapaan tilan loppuun; varaa se pienille tietueille.

14.3.3.3. Hitaan hakemiston ansa

MicroPythonin VFS ei indeksoi hakemistojen sisältöä samalla tavalla kuin työpöytätiedostojärjestelmä. os.listdir() ja os.stat() käyvät läpi taustalla olevan tiedostotaulun lineaarisesti. Sadan tiedoston hakemisto on kunnossa; kymmenentuhannen tiedoston hakemisto on käyttökelvottoman hidas, jolloin jokainen os.listdir() kestää sekunteja ja jokainen open() tarkistaa taulua sen läpi kulkiessaan.

Sovellukset, jotka kirjoittavat lokeja tai kaappauksia levylle, törmäävät tähän nopeimmin. Naiivi /sdcard/logs/<timestamp>.log -malli, joka avaa yhden uuden tiedoston minuutissa, täyttää logs/-hakemiston puolella miljoonalla tiedostolla vuoden käyttöönotossa. Jo kauan ennen sitä sovellus alkaa jäädä kehysnopeudestaan jälkeen, koska jokainen tiedoston avaus kestää kauemmin kuin kehysväli.

Oikea malli on jakaa tiedostot päivättyjen alihakemistojen puuhun niin, ettei yksikään hakemisto koskaan pidä sisällään enempää kuin muutamia satoja merkintöjä:

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

Vuoden verran yksi-tiedosto-per-tunti -lokitusta on nyt levitetty 365 päivähakemistoon, joista kukin sisältää enintään 24 tiedostoa; os.listdir() mitä tahansa yksittäistä hakemistoa vastaan pysyy edullisena, eikä sovelluksen kehyssilmukka jumiudu tiedosto-operaatioihin käyttöönoton vanhetessa.

Sama periaate pätee kuvakaappauksiin, sensorijälkiin tai mihin tahansa muuhun, mistä sovellus kirjoittaa tiedoston tapahtumaa kohti. Jos tapahtumataajuus on korkea, puun kannattaa olla syvempi (vuosi/kuukausi/päivä/tunti tai vuosi/kuukausi/päivä/tunti/minuutti), jotta kukin lehtihakemisto pysyy pienenä. Jos tapahtumataajuus on matala, vuosi/kuukausi-puu riittää.

14.3.3.4. Laitekohtaiset polut

Useamman kuin yhden kameran laitekannassa lokitiedostojen on tunnistettava, mistä fyysisestä yksiköstä ne ovat peräisin. machine.unique_id() palauttaa laitteistotunnisteen, joka on poltettu kameraan tehtaalla; se on sama arvo uudelleenkäynnistysten, laiteohjelmistopäivitysten ja SD-korttien vaihtojen yli. Upota se lokipolkuun tai lokitietueisiin, niin SD-korttien pinoa tai keskitettyä lokia tarkasteleva käyttäjä voi kertoa, mikä on mikä:

import binascii
import machine

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

LOG_ROOT = '/sdcard/logs/' + UNIT_ID

Yhdistettynä päivättyjen alihakemistojen malliin asettelusta tulee /sdcard/logs/<unit-id>/2026/06/09/14.log – yhden yksikön tunnin verran tietueita, hakemistossa, joka on tarpeeksi matala läpikäytäväksi, polulla, joka nimeää yksikön itse tiedostojärjestelmässä.

14.3.3.5. Kootaan yhteen

Toimitetun kameran kirjoitettava tallennustila näyttää suunnilleen tältä:

  • /flash – konfiguraatio, kalibrointi, alustusmerkki. Kirjoitetaan harvoin, luetaan usein. Atomisen uudelleennimeämisen malli mille tahansa tiedostolle, jonka menetys rikkoisi seuraavan käynnistyksen.

  • /sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log – toiminnallinen loki. Kirjoitetaan jatkuvasti, kierrätetään polun mukaan, ei koskaan kirjoiteta hakemiston läpi, jolla on tuhansia sisaria.

  • /sdcard/captures/<unit-id>/<year>/<month>/<day>/ – kuva- tai videokaappaukset, joita sovellus tekee. Sama puurakenne, sama syy.

Tämä asettelu maksaa sovellukselle noin kaksikymmentä riviä koodia ja säästää sen vikatiloilta, jotka kaatavat kameran kuukausia käyttöönoton jälkeen.