Файли маніфестів MicroPython

Короткий огляд

MicroPython має функцію, яка дозволяє «заморозити» Python-код у мікропрограмі як альтернативу завантаженню коду з файлової системи.

Це має такі переваги:

  • код попередньо компілюється у байт-код, що усуває необхідність компіляції Python-джерела під час завантаження.

  • байт-код можна виконувати безпосередньо з ROM (тобто флеш-пам’яті), не копіюючи його в RAM. Аналогічно, будь-які константні об’єкти (рядки, кортежі тощо) також завантажуються з ROM. Це може значно збільшити обсяг пам’яті, доступної для вашого застосунку.

  • на пристроях без файлової системи це єдиний спосіб завантажити Python-код.

Під час розробки заморожування, як правило, не рекомендується, оскільки це значно уповільнить цикл розробки — кожне оновлення вимагатиме повторного прошивання всієї мікропрограми. Проте вибіркове заморожування рідко змінюваних залежностей (наприклад, сторонніх бібліотек) може бути корисним.

Спосіб перелічення Python-файлів для заморожування у мікропрограмі — це «маніфест», тобто Python-файл, який буде інтерпретовано процесом збірки. Зазвичай файл маніфесту створюється як частина визначення плати, але можна написати окремий файл маніфесту та використовувати його з наявним визначенням плати.

Файли маніфестів можуть визначати залежності від бібліотек micropython-lib, а також від Python-файлів у файловій системі та від інших файлів маніфестів.

Написання файлів маніфестів

Файл маніфесту — це Python-файл, що містить послідовність викликів функцій. Дивіться доступні функції, описані нижче.

Будь-які шляхи, що використовуються у файлах маніфестів, можуть містити такі змінні. Усі вони перетворюються на абсолютні шляхи.

  • $(MPY_DIR) — шлях до репозиторію micropython.

  • $(MPY_LIB_DIR) — шлях до підмодуля micropython-lib. Рекомендується використовувати require().

  • $(PORT_DIR) — шлях до поточного порту (наприклад, ports/stm32)

  • $(BOARD_DIR) — шлях до поточної плати (наприклад, ports/stm32/boards/OPENMV4)

Власні файли маніфестів не повинні розміщуватися в основному репозиторії MicroPython. Їх слід зберігати під контролем версій разом із рештою проекту.

Зазвичай маніфест для компіляції мікропрограми має включати маніфест порту, який може містити заморожені модулі, необхідні для роботи плати. Якщо потрібно лише додати додаткові модулі до наявної плати, включіть маніфест плати (який, своєю чергою, включить маніфест порту).

Збірка з власним маніфестом

Ваш маніфест можна вказати в командному рядку make за допомогою:

$ make BOARD=MYBOARD FROZEN_MANIFEST=/path/to/my/project/manifest.py

Це застосовується до всіх портів, включно з CMake-based (наприклад, rp2), оскільки обгортка Makefile передає це до збірки CMake.

Додавання маніфесту до визначення плати

Якщо у вас є власне визначення плати, ви можете налаштувати автоматичне включення вашого маніфесту. На портах на основі make (більшість портів) встановіть змінну FROZEN_MANIFEST у файлі mpconfigboard.mk.

FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py

На портах на основі CMake (наприклад, rp2) використовуйте натомість mpconfigboard.cmake

set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)

Функції високого рівня

Це функції, які ви будете використовувати найчастіше. Вони додають код до набору, що попередньо компілюється у байт-код та заморожується в образ мікропрограми:

  • module та package заморожують ваш власний локальний код — один файл або цілий каталог пакета відповідно.

  • require заморожує опублікований пакет (та його залежності) з micropython-lib за іменем.

  • include включає інший маніфест, щоб його заморожені модулі також були додані.

  • add_library та metadata — допоміжні функції (реєстрація додаткових шляхів пошуку для require та оголошення метаданих пакета).

Типовий маніфест мікропрограми спочатку includeує маніфест порту або плати (щоб необхідні модулі залишались замороженими), а потім додає власні рядки module/package/require.

Примітка: аргумент opt може бути встановлений для різних функцій — він керує рівнем оптимізації, що використовується крос-компілятором. Дивіться micropython.opt_level().

add_library(library, library_path, prepend=False)

Реєструє шлях до зовнішньої іменованої бібліотеки (library).

Використовуйте це, коли потрібно, щоб require знаходив пакети з каталогу, відмінного від micropython-lib — наприклад, з вашої власної колекції драйверів або стороннього репозиторію бібліотеки.

Шлях library_path буде автоматично проглядатися при використанні require. За замовчуванням додана бібліотека додається в кінець списку бібліотек для пошуку. Передайте True у prepend, щоб додати її на початок списку.

Крім того, додану бібліотеку можна явно запросити за допомогою require("name", library="library").

package(package_path, files=None, base_path='.', opt=None)

Заморозити цілий пакет — каталог файлів .py (можливо, з підпакетами) — щоб його можна було імпортувати як import <package>. Використовуйте module для одного окремого файлу.

Це еквівалентно копіюванню каталогу «package_path» на пристрій (але як заморожений код).

У найпростішому випадку, щоб заморозити пакет «foo» у поточному каталозі:

package("foo")

рекурсивно включить усі .py файли у foo і буде заморожено як foo/**/*.py.

Якщо пакет знаходиться не в тому самому каталозі, що й файл маніфесту, використовуйте base_path:

package("foo", base_path="path/to/libraries")

Ви можете використовувати змінні, наведені вище, наприклад $(PORT_DIR) у base_path.

Щоб обмежити набір певними файлами пакета, використовуйте files (примітка: шляхи повинні бути відносними до пакета): package("foo", files=["bar/baz.py"]).

module(module_path, base_path='.', opt=None)

Заморозити один окремий файл .py, щоб його можна було імпортувати за іменем (module("foo.py") дозволяє import foo). Використовуйте package для каталогу/пакета.

Якщо файл знаходиться у поточному каталозі:

module("foo.py")

Інакше використовуйте base_path для визначення розташування файлу:

module("foo.py", base_path="src/drivers")

Ви можете використовувати змінні, наведені вище, наприклад $(PORT_DIR) у base_path.

require(name, library=None)

Вимагати пакет за іменем (та його залежності) з micropython-lib.

Саме так стандартні розширення бібліотеки та драйвери спільноти заморожуються: названий пакет отримується з підмодуля micropython-lib і заморожується разом із усім, від чого залежить. Використовуйте module або package для заморожування власного коду замість опублікованого пакета.

За бажанням вкажіть library (рядок) для посилання на пакет з бібліотеки, попередньо зареєстрованої за допомогою add_library. Інакше буде використано список шляхів до бібліотек.

include(manifest_path)

Включити інший маніфест. Саме так компонуються маніфести: власний маніфест мікропрограми повинен include маніфест порту (або плати), щоб необхідні модулі залишалися замороженими, а потім додавати власні записи.

Зазвичай маніфест для компіляції мікропрограми має включати маніфест порту, який може містити заморожені модулі, необхідні для роботи плати.

Аргумент manifest може бути рядком (ім’я файлу) або ітерованим об’єктом рядків.

Відносні шляхи розраховуються відносно поточного файлу маніфесту.

Якщо шлях вказує на каталог, то неявно включається файл manifest.py всередині цього каталогу.

Ви можете використовувати змінні, наведені вище, наприклад $(PORT_DIR) у manifest_path.

metadata(description=None, version=None, license=None, author=None)

Визначити метадані для цього файлу маніфесту. Це корисно для маніфестів пакетів micropython-lib.

Ці поля використовуються при публікації/встановленні пакета з/до micropython-lib через mip; вони не потрібні у маніфесті мікропрограми плати.

Функції низького рівня

Ці функції задокументовані для повноти, але за винятком freeze_as_str весь функціонал доступний через функції високого рівня.

Функції freeze* відрізняються лише способом зберігання коду:

  • freeze_as_mpy / freeze_mpy зберігають попередньо скомпільований байт-код (.mpy) у флеш-пам’яті. Код виконується безпосередньо з флеш-пам’яті, використовує мінімальну RAM та швидко імпортується. Саме це використовують module, package та require внутрішньо.

  • freeze_as_str натомість заморожує Python джерело, яке компілюється у байт-код під час імпорту (використовуючи RAM і потребуючи компілятора на пристрої). Це єдина можливість, не передбачена функціями високого рівня, тому вона є зазначеним вище винятком.

freeze(path, script=None, opt=0)

Базовий примітив, на якому будуються функції високого рівня; надавайте перевагу їм. Заморожує вхідні дані, задані path, автоматично визначаючи їхній тип. Скрипт .py буде спочатку скомпільовано у .mpy, а потім заморожено; файл .mpy буде заморожено безпосередньо.

path повинен бути каталогом — базовим каталогом для початку пошуку файлів. При імпорті отриманих заморожених модулів ім’я модуля починається після path, тобто path не включається в ім’я модуля.

Якщо path є відносним, він розраховується відносно поточного manifest.py.

Якщо script дорівнює None, всі файли у path будуть заморожені.

Якщо script є ітерованим об’єктом, то freeze() викликається для всіх його елементів (з тими самими path та opt).

Якщо script є рядком, то він вказує файл або каталог для заморожування та може включати додаткові каталоги перед файлом або останнім каталогом. Файл або каталог буде шукатися у path. Якщо script є каталогом, усі файли в ньому будуть заморожені.

opt — рівень оптимізації, що передається mpy-cross під час компіляції .py у .mpy. Ці рівні описані у micropython.opt_level().

freeze_as_str(path)

Заморозити вказаний path та всі скрипти .py у ньому як рядок, який буде скомпільовано при імпорті. Використовуйте це лише тоді, коли заморожений код повинен залишатися джерелом Python; це коштує RAM під час імпорту порівняно з варіантами .mpy.

freeze_as_mpy(path, script=None, opt=0)

Заморозити вхідні дані, попередньо скомпілювавши скрипти .py у файли .mpy, а потім заморозивши отримані файли .mpy. Саме це робить module та package під капотом. Детальніше про аргументи дивіться у freeze().

freeze_mpy(path, script=None, opt=0)

Заморозити вхідні дані, які повинні бути файлами .mpy, що заморожуються безпосередньо (без кроку компіляції). Детальніше про аргументи дивіться у freeze().

Приклади

Щоб заморозити один файл із поточного каталогу, який буде доступний як import mydriver, використовуйте:

module("mydriver.py")

Щоб заморозити каталог файлів у підкаталозі «mydriver» поточного каталогу, який буде доступний як import mydriver, використовуйте:

package("mydriver")

Щоб заморозити бібліотеку «hmac» з micropython-lib, використовуйте:

require("hmac")

Більш повний приклад власного файлу manifest.py (для плати з власним маніфестом за замовчуванням):

# Include the board's default manifest.
include("$(BOARD_DIR)/manifest.py")
# Add a custom driver
module("mydriver.py")
# Add aiorepl from micropython-lib
require("aiorepl")

Тоді плату можна зібрати за допомогою

$ cd ports/stm32
$ make BOARD=MYBOARD FROZEN_MANIFEST=~/src/myproject/manifest.py

Зверніть увагу, що більшість плат не мають власного файлу manifest.py, натомість вони безпосередньо використовують портовий; у такому разі ваш маніфест повинен лише include("$(PORT_DIR)/boards/manifest.py").