Fișierele manifest MicroPython

Rezumat

MicroPython are o funcționalitate care permite ca un cod Python să fie „înghețat” în firmware, ca alternativă la încărcarea codului din sistemul de fișiere.

Aceasta oferă următoarele avantaje:

  • codul este precompilat în bytecode, evitând necesitatea de a compila sursa Python la momentul încărcării.

  • bytecode-ul poate fi executat direct din ROM (adică din memoria flash) în loc să fie copiat în RAM. În mod similar, orice obiecte constante (șiruri de caractere, tupluri etc.) sunt încărcate tot din ROM. Acest lucru poate duce la o cantitate semnificativ mai mare de memorie disponibilă pentru aplicația dumneavoastră.

  • pe dispozitivele care nu au un sistem de fișiere, aceasta este singura modalitate de a încărca cod Python.

În timpul dezvoltării, înghețarea nu este recomandată în general, deoarece va încetini semnificativ ciclul de dezvoltare, întrucât fiecare actualizare va necesita reflashing-ul întregului firmware. Totuși, poate fi în continuare utilă pentru a îngheța selectiv unele dependențe care se modifică rar (cum ar fi bibliotecile terțe).

Modalitatea de a lista fișierele Python care vor fi înghețate în firmware este prin intermediul unui „manifest”, care este un fișier Python ce va fi interpretat de procesul de compilare. De obicei, ați scrie un fișier manifest ca parte a definiției unei plăci, dar puteți de asemenea scrie un fișier manifest de sine stătător și să îl utilizați cu o definiție de placă existentă.

Fișierele manifest pot defini dependențe de biblioteci din micropython-lib, precum și de fișiere Python din sistemul de fișiere, și de asemenea de alte fișiere manifest.

Scrierea fișierelor manifest

Un fișier manifest este un fișier Python care conține o serie de apeluri de funcții. Consultați funcțiile disponibile definite mai jos.

Orice cale folosită în fișierele manifest poate include următoarele variabile. Toate acestea se rezolvă în căi absolute.

  • $(MPY_DIR) – calea către repozitoriul micropython.

  • $(MPY_LIB_DIR) – calea către submodulul micropython-lib. Preferați să folosiți require().

  • $(PORT_DIR) – calea către portul curent (de exemplu ports/stm32)

  • $(BOARD_DIR) – calea către placa curentă (de exemplu ports/stm32/boards/OPENMV4)

Fișierele manifest personalizate nu ar trebui să se afle în repozitoriul principal MicroPython. Ar trebui să le păstrați sub control de versiune împreună cu restul proiectului dumneavoastră.

De obicei, un manifest folosit pentru compilarea firmware-ului va trebui să includă manifestul portului, care ar putea include module înghețate necesare pentru funcționarea plăcii. Dacă doriți doar să adăugați module suplimentare la o placă existentă, atunci includeți manifestul plăcii (care la rândul său va include manifestul portului).

Compilarea cu un manifest personalizat

Manifestul dumneavoastră poate fi specificat în linia de comandă make cu:

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

Acest lucru se aplică tuturor porturilor, inclusiv celor bazate pe CMake (de exemplu rp2), deoarece wrapper-ul Makefile va transmite acest lucru către compilarea CMake.

Adăugarea unui manifest la o definiție de placă

Dacă aveți o definiție de placă personalizată, puteți face ca aceasta să includă automat manifestul dumneavoastră personalizat. Pe porturile bazate pe make (majoritatea porturilor), în fișierul mpconfigboard.mk setați variabila FROZEN_MANIFEST.

FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py

Pe porturile bazate pe CMake (de exemplu rp2), folosiți în schimb mpconfigboard.cmake

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

Funcții de nivel înalt

Acestea sunt funcțiile pe care le veți folosi în mod normal. Ele adaugă cod la setul care este precompilat în bytecode și înghețat în imaginea firmware-ului:

  • module și package îngheață sursa locală proprie — un singur fișier, respectiv un întreg director de pachet.

  • require îngheață un pachet publicat (și dependențele sale) din micropython-lib, după nume.

  • include aduce un alt manifest, astfel încât modulele sale înghețate să fie adăugate și ele.

  • add_library și metadata sunt funcții de suport (înregistrarea de căi de căutare suplimentare pentru require și declararea metadatelor pachetului).

Un manifest tipic de firmware mai întâi includemanifestul portului sau al plăcii (astfel încât modulele de care are nevoie placa să rămână înghețate), apoi adaugă propriile linii module/package/require.

Notă: Argumentul-cuvânt-cheie opt poate fi setat pe diverse funcții; acesta controlează nivelul de optimizare folosit de compilatorul încrucișat. Consultați micropython.opt_level().

add_library(library, library_path, prepend=False)

Înregistrează calea către o bibliotecă externă cu nume.

Folosiți acest lucru atunci când doriți ca require să rezolve pachete dintr-un director diferit de micropython-lib — de exemplu propria dumneavoastră colecție de drivere sau o copie a unei biblioteci terțe.

Calea library_path va fi căutată automat la utilizarea lui require. În mod implicit, biblioteca adăugată este adăugată la sfârșitul listei de biblioteci de căutat. Transmiteți True pentru a o prepune, adică pentru a o adăuga la începutul listei.

În plus, biblioteca adăugată poate fi solicitată explicit folosind require("name", library="library").

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

Îngheață un întreg pachet — un director de fișiere .py (opțional cu subpachete) — astfel încât să poată fi importat ca import <package>. Folosiți module în schimb pentru un singur fișier de sine stătător.

Acest lucru este echivalent cu copierea directorului „package_path” pe dispozitiv (cu excepția faptului că este cod înghețat).

În cel mai simplu caz, pentru a îngheța un pachet „foo” din directorul curent:

package("foo")

va include recursiv toate fișierele .py din foo și va fi înghețat ca foo/**/*.py.

Dacă pachetul nu se află în același director ca fișierul manifest, folosiți base_path:

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

Puteți folosi variabilele de mai sus, cum ar fi $(PORT_DIR) în base_path.

Pentru a restrânge la anumite fișiere din pachet, folosiți files (notă: căile ar trebui să fie relative la pachet): package("foo", files=["bar/baz.py"]).

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

Îngheață un singur fișier .py de sine stătător, astfel încât să poată fi importat după numele său (module("foo.py") face ca import foo să funcționeze). Folosiți package pentru un director/pachet.

Dacă fișierul se află în directorul curent:

module("foo.py")

În caz contrar, folosiți base_path pentru a localiza fișierul:

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

Puteți folosi variabilele de mai sus, cum ar fi $(PORT_DIR) în base_path.

require(name, library=None)

Solicită un pachet după nume (și dependențele sale) din micropython-lib.

Astfel sunt înghețate extensiile bibliotecii standard și driverele comunitare: pachetul cu numele dat este preluat din submodulul micropython-lib și înghețat împreună cu tot ceea ce depinde de el. Folosiți module sau package în schimb pentru a îngheța propria sursă în loc de un pachet publicat.

Opțional, specificați library (un șir de caractere) pentru a referi un pachet dintr-o bibliotecă care a fost înregistrată anterior cu add_library. În caz contrar, va fi folosită lista de căi de biblioteci.

include(manifest_path)

Include un alt manifest. Astfel se compun manifestele: un manifest de firmware personalizat ar trebui să include manifestul portului (sau al plăcii), astfel încât modulele de care are nevoie placa să rămână înghețate, și apoi să adauge propriile intrări.

De obicei, un manifest folosit pentru compilarea firmware-ului va trebui să includă manifestul portului, care ar putea include module înghețate necesare pentru funcționarea plăcii.

Argumentul manifest poate fi un șir de caractere (nume de fișier) sau un obiect iterabil de șiruri de caractere.

Căile relative sunt rezolvate în raport cu fișierul manifest curent.

Dacă calea duce către un director, atunci include implicit fișierul manifest.py din acel director.

Puteți folosi variabilele de mai sus, cum ar fi $(PORT_DIR) în manifest_path.

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

Definește metadatele pentru acest fișier manifest. Acest lucru este util pentru manifestele pachetelor micropython-lib.

Aceste câmpuri sunt consumate atunci când un pachet este publicat în / instalat din micropython-lib prin mip; ele nu sunt necesare într-un manifest de firmware al unei plăci.

Funcții de nivel scăzut

Aceste funcții sunt documentate pentru completitudine, dar cu excepția lui freeze_as_str toată funcționalitatea poate fi accesată prin intermediul funcțiilor de nivel înalt.

Funcțiile freeze* diferă doar prin modul în care este stocat codul:

  • freeze_as_mpy / freeze_mpy stochează bytecode precompilat (.mpy) în memoria flash. Codul rulează direct din memoria flash, folosește un minim de RAM și se importă rapid. Acest lucru este utilizat intern de module, package și require.

  • freeze_as_str îngheață în schimb sursa Python, care este compilată în bytecode la momentul importului (folosind RAM și necesitând compilatorul de pe dispozitiv). Aceasta este singura capabilitate neexpusă de funcțiile de nivel înalt, motiv pentru care reprezintă excepția menționată mai sus.

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

Primitiva de bază pe care se construiesc funcțiile de nivel înalt; preferați-le pe acelea. Îngheață intrarea specificată de path, determinându-i automat tipul. Un script .py va fi mai întâi compilat într-un .mpy și apoi înghețat, iar un fișier .mpy va fi înghețat direct.

path trebuie să fie un director, care este directorul de bază de la care începe căutarea fișierelor. La importul modulelor înghețate rezultate, numele modulului va începe după path, adică path este exclus din numele modulului.

Dacă path este relativ, este rezolvat în raport cu fișierul manifest.py curent.

Dacă script este None, toate fișierele din path vor fi înghețate.

Dacă script este un obiect iterabil, atunci freeze() este apelat pe toate elementele iterabilului (cu aceiași path și opt transmiși mai departe).

Dacă script este un șir de caractere, atunci acesta specifică fișierul sau directorul de înghețat și poate include directoare suplimentare înaintea fișierului sau a ultimului director. Fișierul sau directorul va fi căutat în path. Dacă script este un director, atunci toate fișierele din acel director vor fi înghețate.

opt este nivelul de optimizare care urmează să fie transmis lui mpy-cross la compilarea .py în .mpy. Aceste niveluri sunt descrise în micropython.opt_level().

freeze_as_str(path)

Îngheață path-ul dat și toate scripturile .py din interiorul său ca șir de caractere, care va fi compilat la import. Folosiți acest lucru doar atunci când codul înghețat trebuie să rămână sursă Python; costă RAM la momentul importului în comparație cu variantele .mpy.

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

Îngheață intrarea compilând mai întâi scripturile .py în fișiere .mpy, apoi înghețând fișierele .mpy rezultate. Acest lucru îl fac module și package în fundal. Consultați freeze() pentru detalii suplimentare despre argumente.

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

Îngheață intrarea, care trebuie să fie fișiere .mpy ce sunt înghețate direct (fără pas de compilare). Consultați freeze() pentru detalii suplimentare despre argumente.

Exemple

Pentru a îngheța un singur fișier din directorul curent care va fi disponibil ca import mydriver, folosiți:

module("mydriver.py")

Pentru a îngheța un director de fișiere dintr-un subdirector „mydriver” al directorului curent care va fi disponibil ca import mydriver, folosiți:

package("mydriver")

Pentru a îngheța biblioteca „hmac” din micropython-lib, folosiți:

require("hmac")

Un exemplu mai complet de fișier manifest.py personalizat (pentru o placă ce are propriul manifest implicit) este:

# 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")

Apoi placa poate fi compilată cu

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

Rețineți că majoritatea plăcilor nu au propriul manifest.py, ci folosesc direct pe cel al portului, caz în care manifestul dumneavoastră ar trebui doar să facă include("$(PORT_DIR)/boards/manifest.py") în schimb.