14.2.2.2. בניית תמונת ROMFS

תמונת ROMFS היא מערכת קבצים לקריאה-בלבד השוכנת בפלאש שסביבת הריצה מתקינה אוטומטית בנתיב /rom. היא פותרת את בעיית הנכסים שהעמוד הקודם הסתיים עליה: קובצי מודלים של למידת מכונה, טבלאות תוויות, קונפיגורציית JSON, תבניות תמונה – כל דבר שהיישום פותח וקורא אך לעולם אינו כותב – נכנסים אל תוך ה-build בלי לשלם את המחיר של הטמעה כליטרלים של Python.

שלושה דברים הופכים את ROMFS לכלי הנכון עבור נכסים שנשלחים:

  • מערכת הקבצים היא חלק מתמונת הקושחה. משתמשי הקצה אינם יכולים למחוק קובץ מתוך /rom, לערוך אחד או להחליף אחד בשלהם.

  • קבצים ב-/rom נגישים במקומם. צרכנים כמו מודול ml הטוען קובץ מודל מקבלים מבט ישיר אל תוך הפלאש ללא עותק ב-RAM – מודל בגודל מספר מגה-בייטים על /rom ”נטען“ בעצם בחינם, בעוד אותו קובץ על /sdcard נקרא אל תוך ה-RAM בזמן הטעינה ונשאר שם לכל אורך חיי ההפניה. open() רגיל + read מעתיק לפי דרישה: כל קריאת read(n) מעתיקה n בייטים מהפלאש ל-RAM ברגע הקריאה, כאשר read() חשוף מבקש את כל הקובץ.

  • /rom ו-/rom/lib מתווספים ל-sys.path באתחול. חבילות Python המוצבות בתוך התמונה ניתנות לייבוא לפי שם; שום דבר מיוחד באתר הקריאה.

14.2.2.2.1. בניית תמונה

תמונות ROMFS נוצרות, נערכות ונצרבות דרך ה-IDE. השתמשו בו כמקור האמת לתוכן של כל מחיצת ROMFS שנשלחת.

הסיבה שזה חשוב: קובצי מודלים מגיעים עם דרישות יישור (alignment) שהטוען אוכף בזמן ריצה. קובצי .tflite חייבים להיות מרופדים לגבולות של 16 בייטים, וה-NPU של ה-N6 דורש יישור של 32 בייטים עבור מודלים מהודרים. ה-IDE מיישם ריפוד זה אוטומטית כשהוא כותב את התמונה. כלים העוברים על עץ המקור בלי ליישם את הריפוד – mpremote romfs בפרט – מייצרים תמונה שמותקנת בנקיון אך מודליה נכשלים בקריאת ההסקה הראשונה.

עורך ה-ROMFS של ה-IDE הוא מבט אינטראקטיבי על תוכן התמונה. ניתן להוסיף, לשנות שם ולמחוק קבצים ותיקיות בזיכרון; שמירה כותבת את התוצאה החוצה כקובץ .img מוכן לצריבה. מבנה טיפוסי ליישום שמשלוח מודל לצד כמה נכסים וחבילת Python נראה כך:

model.tflite
labels.txt
config.json
templates/
    calibration.jpg
lib/
    mylib/
        __init__.py
        helpers.py

טיפ

הן ה-IDE והן mpremote מבצעים הידור-צולב (cross-compile) של קובצי .py ל-בייטקוד .mpy בדרכם אל תוך תמונת ROMFS, כך שהמצלמה מייבאת אותם בלי לשלם את עלות הניתוח (parse) בזמן הטעינה. קובצי המקור בעורך נשארים .py; התמונה מכילה .mpy.

ברגע שהתמונה נצרבת, העץ נראה מ-MicroPython בנתיב /rom/

>>> import os
>>> os.listdir('/rom')
['model.tflite', 'labels.txt', 'config.json', 'templates', 'lib']
>>> import mylib
>>> mylib.helpers
<module 'mylib.helpers' from '/rom/lib/mylib/helpers.mpy'>

14.2.2.2.2. רוב היישום שוכן ב-ROMFS

ROMFS הוא הבית הנכון כמעט לכל מה שיישום משלוח: הספריות שהוא מייבא, קובצי המודלים שהוא טוען, הקונפיגורציה שהוא קורא, כל נכס שהפלט שלו הגיע מכלי build שפולט עץ קבצים (ממירי מודלים, צינורות עיבוד תמונה, אורזי נכסים), ו– חשוב מכך – קוד היישום עצמו.

צד המודולים-המוקפאים אמור להישאר קטן: boot.py להגדרה שלפני ה-REPL, main.py כנקודת כניסה דקה, ורק הספריות שהמצלמה באמת אינה יכולה לאתחל בלעדיהן. כל השאר נכנס ל-ROMFS, שבו האיטרציה עליו היא קובץ .img חדש שנשמר מתוך ה-IDE ונצרב מחדש – ללא צורך בבנייה מחדש של הקושחה, ללא toolchain זמין כדי לעשות זאת.

התבנית שנובעת מכך היא main.py שאינו עושה דבר מלבד להאציל אל היישום השוכן ב-ROMFS:

# main.py (frozen)
import app
app.run()

# /rom/app/__init__.py (in ROMFS)
def run():
    ...

שינוי ל-app הוא עריכת ROMFS וצריבה מחדש. בניית הקושחה נשארת במקומה לכל אורך חיי המוצר אלא אם משהו בצד המוקפא באמת חייב להשתנות.