14.3.3. Higiene do sistema de ficheiros¶
O armazenamento flash e em cartão SD de uma câmara enviada para produção enche-se de ficheiros que nenhum operador vai limpar manualmente. Duas decisões sobre esse armazenamento acompanham o produto durante toda a sua vida útil: que superfície alberga que tipo de dados e como as pastas estão estruturadas para que as operações de ficheiros continuem a funcionar à medida que a aplicação acumula registos.
14.3.3.1. Onde as coisas ficam¶
O código e os recursos residem nos módulos frozen e no ROMFS que a compilação confirma no momento do envio. O estado da aplicação – tudo o que a aplicação escreve em tempo de execução, tudo o que cresce, tudo o que muda entre arranques – tem de ficar noutro lugar. A câmara expõe duas superfícies graváveis para isso:
Flash interno em
/flash: um pequeno sistema de ficheiros gravável montado antes de qualquer código de aplicação ser executado. O lugar certo para registos pequenos de tamanho fixo que sobrevivem a reinicializações: configuração que a aplicação atualiza em tempo de execução, última calibração conhecida, um contador incremental, um ficheiro marcador de uma linha a dizer «esta câmara foi provisionada.» Ciclos de escrita limitados – a flash interna moderna tolera milhares a dezenas de milhares de escritas por setor, não milhões, pelo que as escritas devem ser pouco frequentes, não por fotograma.Cartão SD em
/sdcard: um sistema de ficheiros gravável de maior dimensão, montado quando um cartão está presente. O lugar certo para ficheiros variáveis volumosos: capturas de imagem e vídeo, ficheiros de registo, dados de ajuste fino de modelos, tudo o que possa crescer para megabytes ou gigabytes. Maior capacidade de escrita do que a flash interna, mas ainda assim finita; amovível, substituível, e a superfície com maior probabilidade de desaparecer quando a aplicação está a meio de uma escrita.
A resposta certa para onde escrever algo é quase sempre «flash para registos pequenos e fixos, SD para todo o resto.» As duas não são intercambiáveis: uma aplicação que escreve o seu ficheiro de registo incremental em /flash irá esgotar a endurance de escrita da flash numa implantação que teria funcionado bem no SD.
14.3.3.2. Tratar ambas como suscetíveis de falha¶
/flash e /sdcard podem ambas falhar. O cartão SD pode ser ejetado, a flash pode ser corrompida por uma perda de alimentação no meio de uma escrita, qualquer uma delas pode ficar sem espaço, e qualquer operação em qualquer uma delas pode lançar OSError por razões que a aplicação não terá oportunidade de diagnosticar no campo.
Dois padrões permitem que a aplicação sobreviva a isso:
Envolver montagens e operações em blocos try. Cada
open(),os.listdir(),os.rename()contra caminhos de dados do utilizador pode falhar. ApanheOSError, registe-o e recorra a uma alternativa definida – escreva para/flashse/sdcardnão estiver disponível, ignore a operação se nenhuma estiver disponível.Escritas atómicas para ficheiros que devem sobreviver a uma perda de alimentação. Escreva para um caminho temporário, feche o identificador e depois execute
os.rename()sobre o nome em uso. Ou o rename foi bem-sucedido e o ficheiro é a nova versão, ou não foi e o ficheiro é a versão antiga. Não existe um terceiro estado em que o ficheiro está meio escrito: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)
O padrão funciona tanto na flash como no SD. Não funciona para ficheiros suficientemente grandes para que o ficheiro temporário ocupe o espaço livre do sistema de ficheiros; reserve-o para registos pequenos.
14.3.3.3. A armadilha da pasta lenta¶
O VFS do MicroPython não indexa o conteúdo das pastas da forma como um sistema de ficheiros de secretária faz. os.listdir() e os.stat() percorrem a tabela de ficheiros subjacente linearmente. Uma pasta com cem ficheiros está bem; uma pasta com dez mil ficheiros é inutilizavelmente lenta, com cada os.listdir() a demorar segundos e cada open() a verificar na tabela ao longo do caminho.
As aplicações que escrevem registos ou capturas em disco atingem este limite mais rapidamente. Um esquema ingénuo /sdcard/logs/<timestamp>.log que abre um novo ficheiro por minuto preenche a pasta logs/ com meio milhão de ficheiros num ano de implantação. Muito antes disso, a aplicação começa a perder a sua taxa de fotogramas porque cada abertura de ficheiro demora mais do que um intervalo de fotograma.
O padrão correto é dividir os ficheiros por uma árvore de subpastas com data para que nenhuma pasta individual contenha mais do que algumas centenas de entradas:
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
Um ano de registos com um ficheiro por hora está agora distribuído por 365 pastas diárias, cada uma contendo no máximo 24 ficheiros; os.listdir() contra qualquer pasta individual mantém-se eficiente, e o ciclo de fotogramas da aplicação não para em operações de ficheiros à medida que a implantação avança.
O mesmo princípio aplica-se a capturas de imagens, rastreios de sensor, ou qualquer outra coisa que a aplicação escreva num ficheiro por evento. Se a taxa de eventos for alta, a árvore precisa de ser mais profunda (ano/mês/dia/hora, ou ano/mês/dia/hora/minuto) para que cada pasta folha se mantenha pequena. Se a taxa de eventos for baixa, uma árvore ano/mês é suficiente.
14.3.3.4. Caminhos por dispositivo¶
Numa frota com mais de uma câmara, os ficheiros de registo precisam de identificar de que unidade física vieram. machine.unique_id() devolve um identificador de hardware incorporado na câmara na fábrica; é o mesmo valor em todas as reinicializações, em todas as atualizações de firmware e em todas as trocas de cartão SD. Incorpore-o no caminho de registo ou nos registros e um operador a olhar para uma pilha de cartões SD ou um registo centralizado pode dizer qual é qual:
import binascii
import machine
UNIT_ID = binascii.hexlify(machine.unique_id()).decode()
LOG_ROOT = '/sdcard/logs/' + UNIT_ID
Combinado com o padrão de subpastas com data, o esquema torna-se /sdcard/logs/<unit-id>/2026/06/09/14.log – uma hora de registos de uma unidade, numa pasta suficientemente superficial para percorrer, num caminho que identifica a unidade no próprio sistema de ficheiros.
14.3.3.5. Tudo junto¶
O armazenamento gravável de uma câmara enviada tem aproximadamente este aspeto:
/flash– configuração, calibração, um marcador de provisionamento. Escrito raramente, lido frequentemente. Padrão de atomic-rename para qualquer ficheiro cuja perda quebraria o próximo arranque./sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log– o registo operacional. Escrito continuamente, rotacionado pelo caminho, nunca escrito através de uma pasta com milhares de irmãos./sdcard/captures/<unit-id>/<year>/<month>/<day>/– capturas de imagem ou vídeo que a aplicação faz. Mesma estrutura de árvore, mesma razão.
Esse esquema custa à aplicação cerca de vinte linhas de código e poupa-a dos modos de falha que derrubam a câmara meses depois do início de uma implantação.