14.3.3. Higiene do sistema de arquivos¶
O armazenamento em flash e em cartão SD de uma câmera em produção se enche de arquivos que nenhum operador vai limpar manualmente. Duas decisões sobre esse armazenamento acompanham o produto por toda a sua vida útil: qual superfície guarda qual tipo de dado e como os diretórios são estruturados para que as operações de arquivo continuem funcionando à medida que a aplicação acumula registros.
14.3.3.1. Onde cada coisa vai¶
O código e os assets ficam nos módulos congelados (frozen) e no ROMFS que a build define no momento do envio. O estado da aplicação – tudo o que a aplicação grava em tempo de execução, tudo o que cresce, tudo o que muda entre inicializações – precisa ficar em outro lugar. A câmera expõe duas superfícies graváveis para isso:
Flash interna em
/flash: um pequeno sistema de arquivos gravável montado antes de qualquer código da aplicação rodar. O lugar certo para pequenos registros de tamanho fixo que sobrevivem a reinicializações: configuração que a aplicação atualiza em tempo de execução, a última calibração conhecida, um contador acumulativo, um arquivo marcador de uma linha dizendo “esta câmera foi provisionada”. Ciclos de gravação limitados – a flash interna moderna tolera de milhares a dezenas de milhares de gravações por setor, não milhões, então as gravações precisam ser infrequentes, não a cada quadro.Cartão SD em
/sdcard: um sistema de arquivos gravável maior, montado quando um cartão está presente. O lugar certo para arquivos volumosos e variáveis: capturas de imagem e vídeo, arquivos de log, dados de ajuste fino de modelos, qualquer coisa que possa crescer para megabytes ou gigabytes. Maior capacidade de gravação que a flash interna, mas ainda finita; removível, substituível e a superfície com maior probabilidade de desaparecer enquanto a aplicação está no meio de uma gravação.
A resposta certa para onde gravar algo é quase sempre “flash para pequenos registros fixos, SD para todo o resto”. As duas não são intercambiáveis: uma aplicação que escreve seu arquivo de log acumulativo em /flash vai esgotar a resistência de gravação da flash em uma implantação que teria funcionado bem no SD.
14.3.3.2. Trate ambos como passíveis de falha¶
/flash e /sdcard podem falhar. O cartão SD pode ser ejetado, a flash pode ser corrompida por uma queda de energia no meio de uma gravação, ambos podem ficar sem espaço, e qualquer operação em qualquer um deles pode levantar OSError por razões que a aplicação não terá a chance de diagnosticar em campo.
Dois padrões fazem a aplicação sobreviver a isso:
Envolva montagens e operações em blocos try. Cada
open(),os.listdir(),os.rename()em caminhos de dados do usuário pode falhar. CaptureOSError, registre-o em log e recorra a uma alternativa definida – grave em/flashse/sdcardsumiu, pule a operação se nenhum dos dois estiver disponível.Gravações atômicas para arquivos que precisam sobreviver a uma queda de energia. Grave em um caminho temporário, feche o handle e então faça
os.rename()sobre o nome ativo. Ou o rename teve sucesso e o arquivo é a nova versão, ou não teve e o arquivo é a versão antiga. Não há um terceiro estado em que o arquivo fique gravado pela metade: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 quanto no SD. Ele não funciona para arquivos grandes o suficiente para que o arquivo tmp consuma todo o espaço livre do sistema de arquivos; reserve-o para registros pequenos.
14.3.3.3. A armadilha do diretório lento¶
O VFS do MicroPython não indexa o conteúdo dos diretórios da forma que um sistema de arquivos de desktop faz. os.listdir() e os.stat() percorrem a tabela de arquivos subjacente linearmente. Um diretório com cem arquivos não é problema; um diretório com dez mil arquivos é inutilizavelmente lento, com cada os.listdir() levando segundos e cada open() verificando a tabela ao longo do caminho.
Aplicações que gravam logs ou capturas em disco atingem isso mais rápido. Um esquema ingênuo de /sdcard/logs/<timestamp>.log que abre um novo arquivo por minuto enche o diretório logs/ com meio milhão de arquivos em um ano de implantação. Bem antes disso, a aplicação começa a perder sua taxa de quadros porque cada abertura de arquivo está levando mais tempo que um intervalo de quadro.
O padrão certo é dividir os arquivos em uma árvore de subdiretórios datados, de modo que nenhum diretório individual contenha mais 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 log de um-arquivo-por-hora agora está distribuído por 365 diretórios de dia, cada um contendo no máximo 24 arquivos; os.listdir() em qualquer um dos diretórios permanece barato, e o laço de quadros da aplicação não trava em operações de arquivo conforme a implantação envelhece.
O mesmo princípio se aplica a capturas de imagem, traços de sensor ou qualquer outra coisa para a qual a aplicação grave um arquivo por evento. Se a taxa de eventos for alta, a árvore deve ser mais profunda (ano/mês/dia/hora, ou ano/mês/dia/hora/minuto) para que cada diretório folha permaneça pequeno. Se a taxa de eventos for baixa, uma árvore ano/mês é suficiente.
14.3.3.4. Caminhos por dispositivo¶
Em uma frota de mais de uma câmera, os arquivos de log precisam identificar de qual unidade física vieram. machine.unique_id() retorna um identificador de hardware gravado na câmera na fábrica; é o mesmo valor entre reinicializações, entre atualizações de firmware e entre trocas de cartão SD. Incorpore-o no caminho do log ou nos registros do log, e um operador olhando para uma pilha de cartões SD ou um log centralizado consegue saber 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 subdiretórios datados, o layout passa a ser /sdcard/logs/<unit-id>/2026/06/09/14.log – uma hora de registros de uma unidade, em um diretório raso o suficiente para percorrer, em um caminho que nomeia a unidade no próprio sistema de arquivos.
14.3.3.5. Juntando tudo¶
O armazenamento gravável de uma câmera em produção se parece, aproximadamente, com isto:
/flash– configuração, calibração, um marcador de provisionamento. Gravado raramente, lido com frequência. Padrão de rename atômico para qualquer arquivo cuja perda quebraria a próxima inicialização./sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log– o log operacional. Gravado continuamente, rotacionado pelo caminho, nunca gravado através de um diretório com milhares de irmãos./sdcard/captures/<unit-id>/<year>/<month>/<day>/– capturas de imagem ou vídeo que a aplicação faz. Mesmo formato de árvore, mesma razão.
Esse layout custa à aplicação cerca de vinte linhas de código e a salva dos modos de falha que derrubam a câmera meses após o início de uma implantação.