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, поверненні виконуваного скрипту та відновленні після watchdog – перед тим як REPL стане доступним. Його завдання – підготувати середовище, в якому буде працювати вся решта системи: налаштування, які потрібні REPL, застосунку та будь-яким інструментам відновлення. Тут не міститься сам застосунок. main.py є точкою входу застосунку.

  • Основний цикл. main.py — це основний цикл застосунку. Запускається один раз при холодному завантаженні, одразу після boot.py. Не запускається повторно при наступних програмних скиданнях – замість цього камера переходить до REPL. Ця асиметрія важлива для розробки (Ctrl-D переходить до REPL без повторного запуску циклу, тому розробник може перевірити стан), але не для продакшену: готова камера бачить увімкнення живлення, watchdog та апаратні скидання — усі вони є апаратними скиданнями, які повертаються до шляху холодного завантаження та виконують 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 описує файлову систему у флеш-пам’яті лише для читання, яка заповнює цю прогалину.