ไฟล์แมนิเฟสต์ของ MicroPython

สรุป

MicroPython มีคุณสมบัติที่ช่วยให้โค้ด Python สามารถ "แช่แข็ง" ลงในเฟิร์มแวร์ได้ เป็นทางเลือกแทนการโหลดโค้ดจากระบบไฟล์

ประโยชน์ของวิธีนี้ได้แก่:

  • โค้ดถูกคอมไพล์ล่วงหน้าเป็น bytecode ทำให้ไม่จำเป็นต้องคอมไพล์ซอร์ส Python ในขณะโหลด

  • bytecode สามารถรันได้โดยตรงจาก ROM (คือหน่วยความจำแฟลช) แทนที่จะถูกคัดลอกลง RAM วัตถุคงที่ (สตริง, ทูเพิล ฯลฯ) ก็จะโหลดจาก ROM เช่นกัน ซึ่งทำให้แอปพลิเคชันของคุณมีหน่วยความจำเหลือเพิ่มขึ้นอย่างมีนัยสำคัญ

  • บนอุปกรณ์ที่ไม่มีระบบไฟล์ นี่คือวิธีเดียวในการโหลดโค้ด Python

ในระหว่างการพัฒนา โดยทั่วไปแล้วไม่แนะนำให้ใช้การแช่แข็ง เพราะจะทำให้วงจรการพัฒนาของคุณช้าลงมาก เนื่องจากการอัปเดตแต่ละครั้งต้องแฟลชเฟิร์มแวร์ทั้งหมดใหม่ อย่างไรก็ตาม ยังสามารถเลือกแช่แข็งเฉพาะ dependency ที่แทบไม่เปลี่ยนแปลง (เช่น ไลบรารีของบุคคลที่สาม) ได้

วิธีระบุไฟล์ Python ที่จะแช่แข็งลงในเฟิร์มแวร์คือผ่าน "manifest" ซึ่งเป็นไฟล์ Python ที่จะถูกตีความในระหว่างกระบวนการสร้าง โดยทั่วไปคุณจะเขียนไฟล์แมนิเฟสต์เป็นส่วนหนึ่งของนิยามบอร์ด แต่คุณสามารถเขียนไฟล์แมนิเฟสต์แบบ stand-alone และใช้กับนิยามบอร์ดที่มีอยู่ได้เช่นกัน

ไฟล์แมนิเฟสต์สามารถกำหนด dependency บนไลบรารีจาก micropython-lib รวมถึงไฟล์ Python บนระบบไฟล์ และไฟล์แมนิเฟสต์อื่น ๆ ได้ด้วย

การเขียนไฟล์แมนิเฟสต์

ไฟล์แมนิเฟสต์คือไฟล์ Python ที่ประกอบด้วยการเรียกฟังก์ชันต่าง ๆ ดูฟังก์ชันที่ใช้งานได้ในส่วนด้านล่าง

พาธที่ใช้ในไฟล์แมนิเฟสต์สามารถรวมตัวแปรต่อไปนี้ได้ ทั้งหมดจะถูกแก้ไขเป็นพาธสัมบูรณ์

  • $(MPY_DIR) -- พาธไปยัง repo ของ micropython

  • $(MPY_LIB_DIR) -- พาธไปยัง submodule micropython-lib ควรใช้ require() แทน

  • $(PORT_DIR) -- พาธไปยัง port ปัจจุบัน (เช่น ports/stm32)

  • $(BOARD_DIR) -- พาธไปยังบอร์ดปัจจุบัน (เช่น ports/stm32/boards/OPENMV4)

ไฟล์แมนิเฟสต์แบบกำหนดเองไม่ควรอยู่ใน repo หลักของ MicroPython ควรเก็บไว้ใน version control พร้อมกับส่วนที่เหลือของโปรเจกต์ของคุณ

โดยทั่วไปแมนิเฟสต์ที่ใช้สำหรับคอมไพล์เฟิร์มแวร์จะต้อง include แมนิเฟสต์ของ port ซึ่งอาจรวมโมดูลที่แช่แข็งไว้ที่จำเป็นสำหรับการทำงานของบอร์ด หากคุณต้องการเพิ่มโมดูลเพิ่มเติมให้กับบอร์ดที่มีอยู่ ให้ include แมนิเฟสต์ของบอร์ด (ซึ่งจะ include แมนิเฟสต์ของ port อีกต่อ)

การสร้างด้วยแมนิเฟสต์แบบกำหนดเอง

สามารถระบุแมนิเฟสต์ของคุณในบรรทัดคำสั่ง make ได้ด้วย:

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

ใช้ได้กับทุก port รวมถึง port ที่ใช้ CMake (เช่น rp2) เนื่องจาก Makefile wrapper จะส่งค่านี้เข้าสู่การสร้าง CMake

การเพิ่มแมนิเฟสต์ในนิยามบอร์ด

หากคุณมีนิยามบอร์ดแบบกำหนดเอง คุณสามารถกำหนดให้รวมแมนิเฟสต์แบบกำหนดเองของคุณโดยอัตโนมัติ บน port ที่ใช้ make (ส่วนใหญ่) ให้ตั้งค่าตัวแปร FROZEN_MANIFEST ใน mpconfigboard.mk ของคุณ

FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py

บน port ที่ใช้ CMake (เช่น rp2) ให้ใช้ mpconfigboard.cmake แทน

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

ฟังก์ชันระดับสูง

เหล่านี้คือฟังก์ชันที่คุณจะใช้เป็นปกติ ฟังก์ชันเหล่านี้เพิ่มโค้ดลงในชุดที่คอมไพล์ล่วงหน้าเป็น bytecode และแช่แข็งลงในอิมเมจเฟิร์มแวร์:

  • module และ package แช่แข็งซอร์สโลคอล ของคุณเอง ไม่ว่าจะเป็นไฟล์เดียวหรือไดเรกทอรีแพ็กเกจทั้งหมดตามลำดับ

  • require แช่แข็งแพ็กเกจ ที่เผยแพร่แล้ว (พร้อม dependency ของมัน) จาก micropython-lib โดยระบุชื่อ

  • include ดึงแมนิเฟสต์อื่นเข้ามาเพื่อให้โมดูลที่แช่แข็งไว้ถูกเพิ่มด้วย

  • add_library และ metadata เป็นฟังก์ชันสนับสนุน (ลงทะเบียนพาธค้นหาเพิ่มเติมสำหรับ require และประกาศ metadata ของแพ็กเกจ)

โดยทั่วไปแมนิเฟสต์เฟิร์มแวร์จะ includes แมนิเฟสต์ของ port หรือบอร์ดก่อน (เพื่อให้โมดูลที่บอร์ดต้องการยังคงแช่แข็งอยู่) จากนั้นจึงเพิ่มรายการ module/package/require ของตัวเอง

หมายเหตุ: อาร์กิวเมนต์คีย์เวิร์ด opt สามารถตั้งค่าได้บนฟังก์ชันต่าง ๆ เพื่อควบคุมระดับการเพิ่มประสิทธิภาพที่ใช้โดย cross-compiler ดู micropython.opt_level()

add_library(library, library_path, prepend=False)

ลงทะเบียนพาธไปยัง library ที่มีชื่อภายนอก

ใช้เมื่อคุณต้องการให้ require ค้นหาแพ็กเกจจากไดเรกทอรีอื่นแทน micropython-lib เช่น คอลเลกชัน driver ของคุณเอง หรือ library บุคคลที่สาม

พาธ library_path จะถูกค้นหาโดยอัตโนมัติเมื่อใช้ require โดยค่าเริ่มต้น library ที่เพิ่มเข้ามาจะถูกเพิ่มต่อท้ายรายการ library ที่จะค้นหา ส่งค่า True ไปยัง prepend เพื่อเพิ่มไว้ที่ต้นรายการ

นอกจากนี้ library ที่เพิ่มเข้ามาสามารถร้องขอโดยตรงได้โดยใช้ require("name", library="library")

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

แช่แข็ง แพ็กเกจ ทั้งหมด ซึ่งเป็นไดเรกทอรีของไฟล์ .py (อาจมี sub-package) เพื่อให้ import ได้เป็น import <package> ใช้ module แทนสำหรับไฟล์แบบ standalone เพียงไฟล์เดียว

ซึ่งเทียบเท่ากับการคัดลอกไดเรกทอรี "package_path" ไปยังอุปกรณ์ (ยกเว้นว่าเป็นโค้ดที่แช่แข็งแล้ว)

ในกรณีที่ง่ายที่สุด เพื่อแช่แข็งแพ็กเกจ "foo" ในไดเรกทอรีปัจจุบัน:

package("foo")

จะรวมไฟล์ .py ทั้งหมดใน foo แบบ recursive และจะถูกแช่แข็งเป็น foo/**/*.py

หากแพ็กเกจไม่ได้อยู่ในไดเรกทอรีเดียวกับไฟล์แมนิเฟสต์ ให้ใช้ base_path:

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

คุณสามารถใช้ตัวแปรข้างต้น เช่น $(PORT_DIR) ใน base_path ได้

หากต้องการจำกัดเฉพาะบางไฟล์ในแพ็กเกจ ให้ใช้ files (หมายเหตุ: พาธควรเป็น relative ต่อแพ็กเกจ): package("foo", files=["bar/baz.py"])

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

แช่แข็งไฟล์ .py แบบ standalone เพียงไฟล์เดียว เพื่อให้ import ได้ด้วยชื่อ (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)

ต้องการแพ็กเกจตามชื่อ (พร้อม dependency ของมัน) จาก micropython-lib

นี่คือวิธีที่ส่วนขยาย standard-library และ driver ของชุมชนถูกแช่แข็ง: แพ็กเกจที่ระบุชื่อจะถูกดึงจาก submodule micropython-lib และแช่แข็งพร้อมกับทุกอย่างที่มัน depend on ใช้ module หรือ package แทนเพื่อแช่แข็งซอร์สของคุณเองแทนที่จะเป็นแพ็กเกจที่เผยแพร่แล้ว

ระบุ library (สตริง) เพื่ออ้างอิงแพ็กเกจจาก library ที่ลงทะเบียนไว้แล้วด้วย add_library หรือไม่ก็จะใช้รายการพาธ library

include(manifest_path)

Include แมนิเฟสต์อื่น นี่คือวิธีการเขียนแมนิเฟสต์แบบประกอบ: แมนิเฟสต์เฟิร์มแวร์แบบกำหนดเองควร include แมนิเฟสต์ของ port (หรือบอร์ด) เพื่อให้โมดูลที่บอร์ดต้องการยังคงแช่แข็ง จากนั้นจึงเพิ่มรายการของตัวเอง

โดยทั่วไปแมนิเฟสต์ที่ใช้สำหรับคอมไพล์เฟิร์มแวร์จะต้อง include แมนิเฟสต์ของ port ซึ่งอาจรวมโมดูลที่แช่แข็งไว้ที่จำเป็นสำหรับการทำงานของบอร์ด

อาร์กิวเมนต์ manifest สามารถเป็นสตริง (ชื่อไฟล์) หรือ iterable ของสตริง

พาธ relative จะถูกแก้ไขโดยอ้างอิงกับไฟล์แมนิเฟสต์ปัจจุบัน

หากพาธนำไปสู่ไดเรกทอรี ระบบจะ include ไฟล์ manifest.py ภายในไดเรกทอรีนั้นโดยอัตโนมัติ

คุณสามารถใช้ตัวแปรข้างต้น เช่น $(PORT_DIR) ใน manifest_path ได้

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

กำหนด metadata สำหรับไฟล์แมนิเฟสต์นี้ มีประโยชน์สำหรับแมนิเฟสต์ของแพ็กเกจ micropython-lib

ฟิลด์เหล่านี้ถูกใช้งานเมื่อแพ็กเกจถูกเผยแพร่/ติดตั้งจาก micropython-lib ผ่าน mip ไม่จำเป็นต้องมีในแมนิเฟสต์เฟิร์มแวร์บอร์ด

ฟังก์ชันระดับต่ำ

ฟังก์ชันเหล่านี้มีเอกสารประกอบเพื่อความครบถ้วน แต่ยกเว้น freeze_as_str แล้ว ฟังก์ชันระดับสูงสามารถเข้าถึงฟังก์ชันการทำงานทั้งหมดได้

ฟังก์ชัน freeze* ต่างกันเฉพาะใน วิธี เก็บโค้ด:

  • freeze_as_mpy / freeze_mpy เก็บ bytecode ที่คอมไพล์ล่วงหน้า (.mpy) ลงในแฟลช โค้ดรันโดยตรงจากแฟลช ใช้ RAM น้อยที่สุด และ import ได้รวดเร็ว นี่คือสิ่งที่ module, package และ require ใช้ภายใน

  • freeze_as_str แช่แข็ง ซอร์ส Python แทน ซึ่งจะถูกคอมไพล์เป็น bytecode เมื่อ import (ใช้ RAM และต้องใช้ compiler บนอุปกรณ์) นี่คือความสามารถเดียวที่ฟังก์ชันระดับสูงไม่ได้เปิดเผย จึงเป็นข้อยกเว้นที่ระบุไว้ข้างต้น

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

primitive พื้นฐานที่ฟังก์ชันระดับสูงสร้างขึ้นมา ควรใช้ฟังก์ชันเหล่านั้นแทน แช่แข็ง input ที่ระบุโดย path โดยกำหนดประเภทโดยอัตโนมัติ สคริปต์ .py จะถูกคอมไพล์เป็น .mpy ก่อนแล้วจึงแช่แข็ง ส่วนไฟล์ .mpy จะถูกแช่แข็งโดยตรง

path ต้องเป็นไดเรกทอรี ซึ่งเป็นไดเรกทอรีฐานในการเริ่มค้นหาไฟล์ เมื่อ import โมดูลที่แช่แข็งแล้ว ชื่อโมดูลจะเริ่มหลังจาก path คือ path จะถูกยกเว้นจากชื่อโมดูล

หาก path เป็น relative จะถูกแก้ไขเป็น manifest.py ปัจจุบัน

หาก script เป็น None ไฟล์ทั้งหมดใน path จะถูกแช่แข็ง

หาก script เป็น iterable ระบบจะเรียก freeze() บนทุก item ของ iterable (โดยส่ง path และ opt เดิมผ่านไป)

หาก script เป็นสตริง จะระบุไฟล์หรือไดเรกทอรีที่จะแช่แข็ง และสามารถรวมไดเรกทอรีพิเศษก่อนไฟล์หรือไดเรกทอรีสุดท้าย ไฟล์หรือไดเรกทอรีจะถูกค้นหาใน path หาก script เป็นไดเรกทอรี ไฟล์ทั้งหมดในไดเรกทอรีนั้นจะถูกแช่แข็ง

opt คือระดับการเพิ่มประสิทธิภาพที่ส่งให้ mpy-cross เมื่อคอมไพล์ .py เป็น .mpy ระดับเหล่านี้อธิบายไว้ใน micropython.opt_level()

freeze_as_str(path)

แช่แข็ง path ที่กำหนดและสคริปต์ .py ทั้งหมดในนั้นเป็นสตริง ซึ่งจะถูกคอมไพล์เมื่อ import ใช้เฉพาะเมื่อโค้ดที่แช่แข็งต้องคงอยู่เป็นซอร์ส Python เนื่องจากมีต้นทุน RAM เมื่อ import เมื่อเทียบกับตัวแปร .mpy

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

แช่แข็ง input โดยการคอมไพล์สคริปต์ .py เป็นไฟล์ .mpy ก่อน แล้วจึงแช่แข็งไฟล์ .mpy ที่ได้ นี่คือสิ่งที่ module และ package ทำภายใน ดู freeze() สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับอาร์กิวเมนต์

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

แช่แข็ง input ซึ่งต้องเป็นไฟล์ .mpy ที่ถูกแช่แข็งโดยตรง (ไม่มีขั้นตอนการคอมไพล์) ดู freeze() สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับอาร์กิวเมนต์

ตัวอย่าง

เพื่อแช่แข็งไฟล์เดียวจากไดเรกทอรีปัจจุบันที่จะสามารถ import ได้เป็น import mydriver ให้ใช้:

module("mydriver.py")

เพื่อแช่แข็งไดเรกทอรีของไฟล์ใน subdirectory "mydriver" ของไดเรกทอรีปัจจุบันที่จะสามารถ import ได้เป็น import mydriver ให้ใช้:

package("mydriver")

เพื่อแช่แข็ง library "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 ของตัวเอง แต่ใช้แมนิเฟสต์ของ port โดยตรง ในกรณีนั้นแมนิเฟสต์ของคุณควรเพียงแค่ include("$(PORT_DIR)/boards/manifest.py") แทน