14.2.2.1. Заморозка скриптов в прошивку¶
Замороженный модуль – это файл .py, скомпилированный в байт-код и связанный с образом прошивки на этапе сборки. Среда выполнения импортирует замороженный модуль прямо из флеш-памяти, никогда не обращаясь к файловой системе на диске. Для готового к поставке продукта это правильное место для кода приложения: конечному пользователю нечего удалять, устаревший .py на SD-карте ничего не переопределит, и камера выполняет один и тот же код при каждой загрузке независимо от того, что (если вообще что-то) находится на её накопителях.
На этой странице описывается последовательность запуска, которой следует камера, а затем то, как manifest.py и директива freeze встраивают приложение в сборку.
14.2.2.1.1. Последовательность запуска¶
Что и когда выполняется на камере, выходящей из сброса:
Загрузчик. При включении питания открывается короткое окно DFU, которое IDE использует для отправки обновлений прошивки. Окно закрывается через несколько секунд, и загрузчик передаёт управление MicroPython. Выполняющийся скрипт может повторно войти в это окно по запросу, вызвав
machine.bootloader().Инициализация замороженной файловой системы. Прежде чем выполнится какой-либо код приложения, среда выполнения поднимает файловые системы. Внутренняя флеш-память монтируется в
/flash(и форматируется как пустая, если там ничего нет). Если присутствует SD-карта и во внутренней флеш-памяти не существует файла-маркера с именемSKIPSD, SD-карта монтируется в/sdcard. ROMFS, если сборка включает его, монтируется автоматически в/rom. Рабочий каталог устанавливается в каталог загрузки (/sdcard, если карта смонтирована, иначе/flash), аsys.pathзаполняется значениями/flash,/flash/lib,/sdcard,/sdcard/lib,/romи/rom/lib. Настройка, размещённая во флеш-памяти, выполняется замороженным модулем_boot.py– это инфраструктура порта и платы, а не точка подключения приложения. Приложения не настраивают_boot.py; это делает сборка. Размещение файлаSKIPSDво флеш-памяти из IDE – это поддерживаемый способ заставить камеру загружаться из внутренней флеш-памяти вместо SD-карты.Настройка перед REPL.
boot.pyвыполняется при каждом программном сбросе – холодная загрузка,Ctrl-Dиз REPL, завершение выполняющегося скрипта и восстановление сторожевым таймером – перед тем, как REPL станет доступен. Его задача – подготовить окружение, в котором работает остальная часть системы: ту настройку, которая должна быть на месте, чтобы могли функционировать REPL, приложение и любой инструмент восстановления. Это не то место, где живёт само приложение. Точкой входа приложения являетсяmain.py.Основной цикл.
main.py– это основной цикл приложения. Выполняется один раз при холодной загрузке, сразу послеboot.py. Не выполняется повторно при последующих программных сбросах – вместо этого камера переходит в REPL. Эта асимметрия важна для разработки (нажатие Ctrl-D переходит в REPL без повторного запуска цикла, чтобы разработчик мог исследовать состояние), но не для производства: установленная в полевых условиях камера видит включение питания, срабатывание сторожевого таймера и аппаратные сбросы, которые все являются аппаратными сбросами, повторно входящими в путь холодной загрузки и снова запускающимиmain.py.
14.2.2.1.2. Заморозка в прошивку¶
Набор замороженных модулей платы объявляется в boards/<TARGET>/manifest.py в дереве прошивки. Манифест – это небольшой файл Python, который вызывает несколько директив:
freeze("$(OMV_LIB_DIR)/", "foo.py")– встраивает один файлfoo.pyв сборку.package("mylib", base_path="...")– встраивает многофайловый пакет Python, сохраняя его структуру каталогов относительно заданного базового пути.include("...")– подключает другой файл манифеста. Манифесты плат используют это для совместного использования общих наборов модулей.require("logging")– подключает именованный вышестоящий модульmicropython-libпо имени.
Минимальный манифест приложения добавляет одну строку freeze на каждый скрипт верхнего уровня и одну строку package на каждый пакет, от которого зависит приложение.
14.2.2.1.2.1. Где находится исходный код¶
Исходный код приложения находится в scripts/libraries/ в дереве прошивки, рядом с модулями, которые сборка уже замораживает. Переменная манифеста $(OMV_LIB_DIR) раскрывается в этот путь, поэтому записи манифеста остаются короткими. Редактирование манифеста уже является операцией внутри дерева, поэтому хранение исходного кода внутри дерева избавляет от необходимости жонглировать отдельным репозиторием проекта при разрешении путей.
Типичная структура для приложения, которое поставляет единственный main.py плюс вспомогательный пакет:
scripts/libraries/
main.py
my_lib/
__init__.py
helpers.py
А в boards/<TARGET>/manifest.py платы – одна строка freeze для скрипта и одна строка package для пакета:
freeze("$(OMV_LIB_DIR)/", "main.py")
package("my_lib", base_path="$(OMV_LIB_DIR)/my_lib")
Однофайловые скрипты – здесь main.py, но то же правило применяется к boot.py или любому отдельному вспомогательному модулю – используют freeze. Многофайловые пакеты используют package. Добавление ещё одного скрипта – это ещё одна строка freeze; добавление ещё одного пакета – это ещё одна строка package.
14.2.2.1.2.2. Сборка и прошивка¶
Когда манифест на месте, соберите прошивку точно так, как описано в главе о прошивке:
make -j$(nproc) -C lib/micropython/mpy-cross # once, builds the cross-compiler
make -j$(nproc) TARGET=<TARGET> # builds the firmware
Результат попадает в build/<TARGET>/bin/:
build/<TARGET>/bin/
firmware.bin # flash through the IDE
romfs0.img # flash through the IDE in a separate step
Прошивка файлов .bin и .img через IDE даёт камеру, чьё приложение является частью сборки.
Приведённая выше последовательность запуска – это то, что делает встраивание эффективным: среда выполнения разрешает boot.py и main.py в замороженные копии, прежде чем вообще обратится к файловой системе, поэтому поставляемая камера выполняет код сборки, даже если на SD-карте хранится устаревший boot.py, оставшийся со времён разработки.
14.2.2.1.2.3. Порядок поиска¶
Семантика переопределения различается для пути выполнения boot.py / main.py и для обычных инструкций import. Понимание того, что есть что, важно как для производства, так и для разработки:
Для
boot.pyиmain.py: среда выполнения сначала ищет замороженную копию, затем файловую систему. Замороженныйboot.pyнельзя переопределить, разместив его на SD-карте – тот, у кого в руках камера, не может изменить точку входа без перепрошивки.Для
import foo: среда выполнения сначала просматриваетsys.path– который охватывает/flash,/sdcard,/romи их подкаталогиlib– затем замороженные модули. Файлfoo.pyс тем же именем во флеш-памяти или на SD действительно переопределяет замороженныйfoo. Это удобство для разработки: разместите исправленный модуль на карте, выполните программный сброс, увидьте изменение без перепрошивки.
Готовый к поставке продукт, который хочет подавить поведение «файловая система переопределяет замороженное» для импортов, может очистить sys.path в начале boot.py:
import sys
sys.path.clear()
При пустом sys.path все импорты разрешаются только из замороженных модулей; ничто во флеш-памяти, на SD или в ROMFS не может их затенить.
14.2.2.1.2.4. Проблема ресурсов¶
Заморозка отлично подходит для кода. Она не подходит для крупных бинарных ресурсов: файлов моделей машинного обучения, таблиц меток, конфигурации JSON, шаблонов изображений. Встраивание их в виде литералов Python раздувает исходный код, медленно перекомпилируется и расходует контейнер байт-кода на данные, которые интерпретатор всё равно собирается прочитать в сыром виде. Страница Создание образа ROMFS описывает доступную только для чтения файловую систему во флеш-памяти, которая заполняет этот пробел.