14.3.3. Порядок на файловій системі¶
Флеш-пам’ять і SD-сховище на відправленій камері заповнюються файлами, які жоден оператор не буде видаляти вручну. Два рішення щодо цього сховища залишаються з продуктом на весь його термін служби: яка поверхня зберігає який тип даних і як структуровані каталоги, щоб файлові операції продовжували працювати в міру того, як застосунок накопичує записи.
14.3.3.1. Де що зберігається¶
Код і ресурси зберігаються у заморожених модулях і ROMFS, які фіксуються під час збірки перед відвантаженням. Стан застосунку – все, що застосунок записує під час виконання, все, що зростає, все, що змінюється між перезавантаженнями – має зберігатися в іншому місці. Камера надає для цього дві доступні для запису поверхні:
Внутрішня флеш-пам’ять за адресою
/flash: невелика файлова система з можливістю запису, що монтується до запуску будь-якого коду застосунку. Правильне місце для невеликих записів фіксованого розміру, що зберігаються між перезавантаженнями: конфігурація, яку застосунок оновлює під час виконання, останнє відоме калібрування, лічильник, що постійно змінюється, однорядковий файл-маркер зі словами «ця камера була підготовлена до роботи.» Обмежена кількість циклів запису – сучасна внутрішня флеш-пам’ять витримує від тисяч до десятків тисяч записів на сектор, але не мільйони, тому записи мають бути рідкісними, а не на кожен кадр.SD-карта за адресою
/sdcard: більша файлова система з можливістю запису, що монтується за наявності карти. Правильне місце для об’ємних змінних файлів: знімки зображень і відео, журнали, дані для донавчання моделі (МН), все, що може вирости до мегабайтів або гігабайтів. Більший ресурс запису, ніж у внутрішньої флеш-пам’яті, але також обмежений; знімна, замінна – і та поверхня, яка найімовірніше зникне, коли застосунок перебуває в процесі запису.
Правильна відповідь на питання куди щось записувати майже завжди звучить так: «флеш для невеликих записів фіксованого розміру, SD для всього іншого.» Вони не є взаємозамінними: застосунок, який записує журнал, що постійно змінюється, до /flash, вичерпає ресурс запису флеш-пам’яті за час розгортання, при якому SD-карта продовжувала б нормально працювати.
14.3.3.2. Вважайте обидві поверхні ненадійними¶
/flash і /sdcard можуть обидві відмовити. SD-карту можна вийняти, флеш-пам’ять може бути пошкоджена через втрату живлення під час запису, кожна з них може вичерпати простір, і будь-яка операція з будь-якою з них може викинути OSError з причин, які застосунок не матиме можливості діагностувати у польових умовах.
Два підходи дозволяють застосунку пережити це:
Обгортайте монтування та операції в блоки try. Кожна
open(),os.listdir(),os.rename()над шляхами до даних користувача потенційно може завершитися помилкою. ПерехоплюйтеOSError, журналюйте її та переходьте до визначеної альтернативи – записуйте до/flash, якщо/sdcardнедоступна, пропускайте операцію, якщо жодна не доступна.Атомарні записи для файлів, що мають пережити втрату живлення. Запишіть до тимчасового шляху, закрийте дескриптор, а потім виконайте
os.rename()поверх основного імені. Або перейменування пройшло успішно і файл має нову версію, або ні – і файл має стару версію. Третього стану, де файл наполовину записаний, не існує: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)
Цей підхід працює як з флеш-пам’яттю, так і з SD. Він не працює для файлів, достатньо великих, щоб тимчасовий файл використав весь вільний простір файлової системи; резервуйте його для невеликих записів.
14.3.3.3. Пастка повільного каталогу¶
MicroPython VFS не індексує вміст каталогів так, як це робить файлова система десктопа. os.listdir() і os.stat() лінійно обходять базову таблицю файлів. Каталог зі сотнею файлів – це нормально; каталог з десятьма тисячами файлів – непридатно повільний, де кожен os.listdir() займає секунди, а кожен open() перевіряє таблицю по дорозі.
Застосунки, що записують журнали або знімки на диск, стикаються з цим найшвидше. Проста схема /sdcard/logs/<timestamp>.log, що відкриває один новий файл на хвилину, заповнить каталог logs/ півмільйоном файлів за рік розгортання. Ще задовго до цього застосунок почне не встигати за частотою кадрів, оскільки кожне відкриття файлу займатиме більше часу, ніж інтервал між кадрами.
Правильний підхід – розподілити файли по дереву датованих підкаталогів, щоб жоден окремий каталог ніколи не містив більше кількох сотень записів:
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
Річний журнал із одним файлом на годину тепер розподілений по 365 денних каталогах, кожен з яких містить щонайбільше 24 файли; os.listdir() будь-якого окремого каталогу залишається швидким, і цикл кадрів застосунку не зависає на файлових операціях у міру того, як розгортання старіє.
Той самий принцип стосується знімків зображень, трасувань датчика або будь-якого іншого контенту, для якого застосунок записує файл на кожну подію. Якщо частота подій висока, дерево має бути глибшим (рік/місяць/день/година або рік/місяць/день/година/хвилина), щоб кожен кінцевий каталог залишався невеликим. Якщо частота подій низька, дерево рік/місяць є достатнім.
14.3.3.4. Шляхи для окремих пристроїв¶
У парку з кількох камер файли журналів мають ідентифікувати, з якого фізичного пристрою вони надійшли. machine.unique_id() повертає апаратний ідентифікатор, вбудований у камеру на заводі; це одне і те саме значення після перезавантажень, оновлень мікропрограми та заміни SD-карт. Вбудуйте його у шлях журналу або в записи журналу, і оператор, що дивиться на стопку SD-карт або централізований журнал, зможе визначити, яка з них яка:
import binascii
import machine
UNIT_ID = binascii.hexlify(machine.unique_id()).decode()
LOG_ROOT = '/sdcard/logs/' + UNIT_ID
У поєднанні з підходом датованих підкаталогів розмітка набуває вигляду /sdcard/logs/<unit-id>/2026/06/09/14.log – год записів одного пристрою, у каталозі, достатньо невеликому для обходу, на шляху, що вказує на пристрій у самій файловій системі.
14.3.3.5. Зведення воєдино¶
Доступне для запису сховище відправленої камери виглядає приблизно так:
/flash– конфігурація, калібрування, маркер підготовки до роботи. Записується рідко, читається часто. Підхід з атомарним перейменуванням для будь-якого файлу, втрата якого може порушити наступне завантаження./sdcard/logs/<unit-id>/<year>/<month>/<day>/<hour>.log– операційний журнал. Записується безперервно, ротується за шляхом, ніколи не записується через каталог із тисячами сусідів./sdcard/captures/<unit-id>/<year>/<month>/<day>/– знімки зображень або відео, зроблені застосунком. Та сама структура дерева, та сама причина.
Така розмітка коштує застосунку приблизно двадцять рядків коду і рятує його від режимів відмов, які виводять камеру з ладу через місяці після розгортання.