14.2.2.1. การแช่แข็งสคริปต์ลงในเฟิร์มแวร์

โมดูล frozen คือไฟล์ .py ที่ถูกคอมไพล์เป็น bytecode และลิงก์เข้าไปในเฟิร์มแวร์ image ณ เวลาสร้าง (build time) รันไทม์จะนำเข้าโมดูล frozen โดยตรงจากแฟลช โดยไม่ต้องค้นหาจากระบบไฟล์บนดิสก์เลย สำหรับผลิตภัณฑ์ที่จัดส่งแล้ว นี่คือตำแหน่งที่เหมาะสมสำหรับโค้ดแอปพลิเคชัน: ผู้ใช้ปลายทางไม่สามารถลบออกได้ ไฟล์ .py เก่าบน SD card ไม่สามารถแทนที่ได้ และกล้องจะรันโค้ดเดิมทุกครั้งที่บูต ไม่ว่าไดรฟ์จะมีอะไรอยู่หรือไม่ก็ตาม

หน้านี้อธิบายลำดับการเริ่มต้น (startup sequence) ที่กล้องปฏิบัติตาม รวมถึงวิธีที่ manifest.py และคำสั่ง freeze ฝังแอปพลิเคชันเข้าสู่การสร้าง

14.2.2.1.1. ลำดับการเริ่มต้น

สิ่งที่รันและเมื่อไหร่ เมื่อกล้องออกจาก reset:

  • บูตโหลดเดอร์ การเปิดเครื่องจะเข้าสู่ช่วง DFU สั้น ๆ ที่ IDE ใช้ในการส่งอัปเดตเฟิร์มแวร์ ช่วงนี้จะปิดลงหลังจากไม่กี่วินาที และบูตโหลดเดอร์จะส่งต่อให้ MicroPython สคริปต์ที่รันอยู่สามารถกลับเข้าสู่ช่วงนี้ได้ตามต้องการโดยเรียก machine.bootloader()

  • การเริ่มต้นระบบไฟล์ frozen ก่อนที่โค้ดแอปพลิเคชันใด ๆ จะรัน รันไทม์จะเตรียมระบบไฟล์ให้พร้อม โดยแฟลชภายในถูกเมาท์ที่ /flash (และฟอร์แมตว่างเปล่าหากไม่มีข้อมูลอยู่) หาก SD card มีอยู่ และ ไม่มีไฟล์เครื่องหมาย SKIPSD บนแฟลชภายใน SD card จะถูกเมาท์ที่ /sdcard ROMFS เมื่อการสร้างรวมไว้ จะถูกเมาท์อัตโนมัติที่ /rom ไดเรกทอรีทำงานถูกตั้งเป็นไดเรกทอรีบูต (/sdcard หาก card เมาท์แล้ว หรือ /flash หากไม่ได้เมาท์) และ sys.path จะถูกเติมด้วย /flash, /flash/lib, /sdcard, /sdcard/lib, /rom, และ /rom/lib การตั้งค่าที่อยู่ในแฟลชจัดการโดยโมดูล frozen ที่ชื่อ _boot.py -- เป็นโครงสร้างพื้นฐานของ port และ board ไม่ใช่ hook ของแอปพลิเคชัน แอปพลิเคชันไม่ควรปรับแต่ง _boot.py; การสร้างเป็นผู้ดูแล การวาง SKIPSD ลงบนแฟลชจาก IDE คือวิธีที่รองรับเพื่อให้กล้องบูตจากแฟลชภายในแทน SD card

  • การตั้งค่าก่อน REPL boot.py รันทุก soft reset -- การบูตเย็น, Ctrl-D จาก REPL, เมื่อสคริปต์ที่รันคืนค่า, และการกู้คืนจาก watchdog -- ก่อนที่ REPL จะสามารถเข้าถึงได้ หน้าที่คือเตรียมสภาพแวดล้อมที่ระบบส่วนที่เหลือใช้: การตั้งค่าประเภทที่ REPL, แอปพลิเคชัน, และเครื่องมือกู้คืนต้องการให้พร้อมใช้งาน ไม่ใช่ที่อยู่ของแอปพลิเคชันเอง main.py คือจุดเริ่มต้นของแอปพลิเคชัน

  • ลูปหลัก main.py คือลูปหลักของแอปพลิเคชัน รันครั้งเดียวเมื่อบูตเย็น หลังจาก boot.py ทันที ไม่ รันซ้ำเมื่อ soft reset ในภายหลัง -- กล้องจะตกลงสู่ REPL แทน ความไม่สมมาตรนี้มีความสำคัญในการพัฒนา (Ctrl-D จะตกลงสู่ REPL โดยไม่รันลูปซ้ำ ทำให้นักพัฒนาตรวจสอบสถานะได้) แต่ไม่สำคัญในการผลิต: กล้องที่ใช้งานจริงรับ power-on, watchdog, และ hard reset ซึ่งล้วนเป็น hardware reset ที่เข้าสู่เส้นทางบูตเย็นและรัน main.py อีกครั้ง

14.2.2.1.2. การแช่แข็งเข้าสู่เฟิร์มแวร์

ชุดโมดูล frozen ของบอร์ดถูกประกาศใน boards/<TARGET>/manifest.py ในต้นไม้เฟิร์มแวร์ manifest คือไฟล์ Python ขนาดเล็กที่เรียกใช้คำสั่งไม่กี่อย่าง:

  • freeze("$(OMV_LIB_DIR)/", "foo.py") -- ฝัง foo.py ไฟล์เดียวเข้าสู่การสร้าง

  • package("mylib", base_path="...") -- ฝัง Python package หลายไฟล์โดยรักษาโครงสร้างไดเรกทอรีภายใต้ base path ที่กำหนด

  • include("...") -- ดึงไฟล์ manifest อื่นเข้ามา บอร์ด manifests ใช้สิ่งนี้เพื่อแบ่งปันชุดโมดูลร่วมกัน

  • require("logging") -- ดึงโมดูล micropython-lib upstream ที่มีชื่อมาตามชื่อ

manifest แอปพลิเคชันขั้นต่ำจะเพิ่มบรรทัด freeze หนึ่งบรรทัดต่อสคริปต์ระดับบนสุด และบรรทัด package หนึ่งบรรทัดต่อ package ที่แอปพลิเคชันพึ่งพา

14.2.2.1.2.1. ตำแหน่งของซอร์สโค้ด

ซอร์สโค้ดแอปพลิเคชันอยู่ใน scripts/libraries/ ในต้นไม้เฟิร์มแวร์ ข้างๆ โมดูลที่การสร้างแช่แข็งไว้แล้ว ตัวแปร manifest $(OMV_LIB_DIR) ขยายไปยังเส้นทางนั้น ทำให้รายการ manifest สั้น การแก้ไข manifest เป็นการดำเนินการภายใน tree อยู่แล้ว ดังนั้นการเก็บซอร์สใน tree ช่วยหลีกเลี่ยงการจัดการ repo โปรเจกต์แยกต่างหากในการแก้ไขเส้นทาง

โครงสร้างทั่วไปสำหรับแอปพลิเคชันที่จัดส่ง main.py ไฟล์เดียวพร้อม package สนับสนุน:

scripts/libraries/
    main.py
    my_lib/
        __init__.py
        helpers.py

และใน boards/<TARGET>/manifest.py ของบอร์ด บรรทัด freeze หนึ่งบรรทัดสำหรับสคริปต์ และบรรทัด package หนึ่งบรรทัดสำหรับ package:

freeze("$(OMV_LIB_DIR)/", "main.py")
package("my_lib", base_path="$(OMV_LIB_DIR)/my_lib")

สคริปต์ไฟล์เดียว -- main.py ที่นี่ แต่กฎเดียวกันใช้กับ boot.py หรือ helper แบบ standalone ใด ๆ -- ใช้ freeze Package หลายไฟล์ใช้ package การเพิ่มสคริปต์อื่นคือบรรทัด freeze อีกหนึ่งบรรทัด; การเพิ่ม package อื่นคือบรรทัด package อีกหนึ่งบรรทัด

14.2.2.1.2.2. การสร้างและการแฟลช

เมื่อ manifest พร้อมแล้ว ให้สร้างเฟิร์มแวร์ตามที่ บทเฟิร์มแวร์ อธิบายไว้:

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 ไปยังสำเนา frozen ก่อนที่จะตรวจสอบระบบไฟล์ ดังนั้นกล้องที่จัดส่งจะรันโค้ดของการสร้างแม้ว่า SD card จะมี boot.py เก่าจากการพัฒนาก็ตาม

14.2.2.1.2.3. ลำดับการค้นหา

ความหมายของการแทนที่ แตกต่างกัน สำหรับเส้นทางการรัน boot.py / main.py และสำหรับคำสั่ง import ทั่วไป การรู้ว่าสิ่งใดเป็นอะไรมีความสำคัญทั้งในการผลิตและการพัฒนา:

  • สำหรับ boot.py และ main.py: รันไทม์ค้นหาสำเนา frozen ก่อน แล้วจึงค้นหาระบบไฟล์ boot.py frozen ไม่สามารถถูกแทนที่โดยการวางบน SD card ได้ -- ผู้ที่ถือกล้องไม่สามารถเปลี่ยน entry point ได้โดยไม่ต้องแฟลชใหม่

  • สำหรับ import foo: รันไทม์ค้นหา sys.path ก่อน -- ซึ่งครอบคลุม /flash, /sdcard, /rom, และไดเรกทอรีย่อย lib ของแต่ละ -- จากนั้นค้นหาโมดูล frozen foo.py ที่มีชื่อเดียวกันบนแฟลชหรือ SD จะ แทนที่ foo frozen การนี้คือความสะดวกในการพัฒนา: วางโมดูลที่แก้ไขแล้วบนการ์ด soft-reset แล้วเห็นการเปลี่ยนแปลงโดยไม่ต้องแฟลชใหม่

ผลิตภัณฑ์ที่จัดส่งที่ต้องการปิดกั้นพฤติกรรมที่ระบบไฟล์แทนที่ frozen สำหรับ imports สามารถล้าง sys.path ตั้งแต่ต้นใน boot.py

import sys

sys.path.clear()

เมื่อ sys.path ว่างเปล่า imports ทั้งหมดจะแก้ไขจากโมดูล frozen เท่านั้น ไม่มีสิ่งใดบนแฟลช, SD, หรือ ROMFS สามารถบดบังได้

14.2.2.1.2.4. ปัญหา asset

การแช่แข็งเหมาะสำหรับโค้ด แต่ ไม่เหมาะ สำหรับ asset ไบนารีขนาดใหญ่: ไฟล์โมเดล machine learning, ตารางป้ายกำกับ, การกำหนดค่า JSON, เทมเพลตภาพ การฝังสิ่งเหล่านี้เป็น Python literals จะทำให้ซอร์สบวมขึ้น คอมไพล์ช้า และเสียพื้นที่ bytecode container บนข้อมูลที่ interpreter จะต้องอ่านดิบอยู่ดี หน้า การสร้าง ROMFS image อธิบาย read-only flash filesystem ที่เติมเต็มช่องว่างนี้