14.2.2.1. הקפאת סקריפטים אל תוך הקושחה

מודול מוקפא הוא קובץ .py שעבר הידור (compile) לבייטקוד וקושר אל תוך תמונת הקושחה בזמן ה-build. סביבת הריצה מייבאת מודול מוקפא היישר מזיכרון הפלאש (flash), בלי לפנות אי-פעם אל מערכת הקבצים שעל הדיסק. עבור מוצר שנשלח ללקוח זהו המקום הנכון לקוד היישום: אין דבר שהמשתמש הקצה יוכל למחוק, אין קובץ .py ישן על כרטיס ה-SD שיוכל לדרוס אותו, והמצלמה מריצה את אותו הקוד בכל אתחול ללא תלות במה שמצוי (אם בכלל) על כונניה.

עמוד זה מסביר את רצף האתחול שהמצלמה עוקבת אחריו, ולאחר מכן כיצד manifest.py והדירקטיבה freeze אופים יישום אל תוך ה-build.

14.2.2.1.1. רצף האתחול

מה רץ, ומתי, במצלמה שיוצאת מ-reset:

  • המאתחל (bootloader). ההפעלה נכנסת לחלון DFU קצר שבו ה-IDE משתמש כדי לדחוף עדכוני קושחה. החלון נסגר אחרי כמה שניות והמאתחל מעביר את השליטה ל-MicroPython. סקריפט שרץ יכול להיכנס מחדש לחלון זה לפי דרישה על ידי קריאה ל-machine.bootloader().

  • אתחול מערכת קבצים מוקפאת. לפני שקוד יישום כלשהו רץ, סביבת הריצה מעלה את מערכות הקבצים. הפלאש (flash) הפנימי מותקן (mount) בנתיב /flash (ומאותחל ריק אם אין שם דבר). אם כרטיס SD נוכח וגם קובץ סמן בשם SKIPSD אינו קיים בפלאש הפנימי, כרטיס ה-SD מותקן בנתיב /sdcard. ROMFS, כאשר ה-build כולל אותו, מותקן אוטומטית בנתיב /rom. תיקיית העבודה נקבעת לתיקיית האתחול (/sdcard אם הכרטיס הותקן, אחרת /flash), ו-sys.path מאוכלס ב-/flash, ב-/flash/lib, ב-/sdcard, ב-/sdcard/lib, ב-/rom וב-/rom/lib. ההגדרה השוכנת בפלאש מטופלת על ידי מודול מוקפא בשם _boot.py – תשתית של ה-port והלוח, לא וו (hook) של יישום. יישומים אינם מתאימים אישית את _boot.py; ה-build עושה זאת. הצבת קובץ SKIPSD בפלאש מתוך ה-IDE היא הדרך הנתמכת לגרום למצלמה לאתחל מהפלאש הפנימי במקום מכרטיס ה-SD.

  • הגדרה שלפני ה-REPL. boot.py רץ בכל soft reset – אתחול קר, Ctrl-D מה-REPL, סיום הסקריפט הרץ, והתאוששות של watchdog – לפני שה-REPL נעשה נגיש. תפקידו להכין את הסביבה שבה שאר המערכת רצה: סוג ההגדרה שה-REPL, היישום וכל כלי התאוששות זקוקים לכך שתהיה במקומה כדי לתפקד. זה אינו המקום שבו היישום עצמו שוכן. main.py הוא נקודת הכניסה של היישום.

  • הלולאה הראשית. main.py הוא הלולאה הראשית של היישום. רץ פעם אחת באתחול קר, מיד אחרי boot.py. לא מורץ מחדש ב-soft resets עוקבים – המצלמה נופלת ל-REPL במקום זאת. אי-סימטריה זו חשובה לפיתוח (Ctrl-D נופל ל-REPL בלי להריץ מחדש את הלולאה, כך שהמפתח יכול לבחון את המצב) אך לא לייצור: מצלמה בשטח רואה הפעלה, watchdog ו-hard resets, שכולם אתחולי חומרה הנכנסים מחדש למסלול האתחול הקר ומריצים את main.py שוב.

14.2.2.1.2. הקפאה אל תוך הקושחה

מערך המודולים המוקפאים של לוח מוצהר ב-boards/<TARGET>/manifest.py בעץ הקושחה. ה-manifest הוא קובץ Python קטן הקורא לכמה דירקטיבות:

  • freeze("$(OMV_LIB_DIR)/", "foo.py") – אופה קובץ foo.py יחיד אל תוך ה-build.

  • package("mylib", base_path="...") – אופה חבילת Python מרובת קבצים, תוך שמירה על מבנה התיקיות שלה תחת נתיב הבסיס הנתון.

  • include("...") – מושך פנימה קובץ manifest אחר. ה-manifests של הלוחות משתמשים בכך כדי לשתף מערכי מודולים משותפים.

  • require("logging") – מושך פנימה מודול micropython-lib מעלה-זרם נקוב לפי שמו.

manifest מינימלי של יישום מוסיף שורת freeze אחת לכל סקריפט ברמה העליונה ושורת package אחת לכל חבילה שהיישום תלוי בה.

14.2.2.1.2.1. היכן שוכן קוד המקור

קוד המקור של היישום שוכן תחת scripts/libraries/ בעץ הקושחה, לצד המודולים שה-build כבר מקפיא. משתנה ה-manifest $(OMV_LIB_DIR) מתרחב לאותו נתיב, כך שרשומות ה-manifest נשארות קצרות. עריכת ה-manifest היא ממילא פעולה בתוך העץ, ולכן שמירת קוד המקור בתוך העץ חוסכת ז’ונגלור עם מאגר פרויקט נפרד בפתרון הנתיבים.

מבנה טיפוסי ליישום שמשלוח main.py יחיד בתוספת חבילה תומכת:

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

וב-boards/<TARGET>/manifest.py של הלוח, שורת freeze אחת לסקריפט ושורת package אחת לחבילה:

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

סקריפטים בקובץ יחיד – main.py כאן, אך אותו כלל חל על boot.py או על כל עוזר עצמאי – משתמשים ב-freeze. חבילות מרובות קבצים משתמשות ב-package. הוספת סקריפט נוסף היא שורת freeze אחת נוספת; הוספת חבילה נוספת היא שורת 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 נוחתת על מצלמה שיישומה הוא חלק מה-build.

רצף האתחול לעיל הוא מה שהופך את האפייה-פנימה לאפקטיבית: סביבת הריצה מפענחת את boot.py ואת main.py לעותקים המוקפאים עוד לפני שהיא בודקת את מערכת הקבצים, כך שמצלמה שנשלחה מריצה את הקוד של ה-build גם אם כרטיס ה-SD מחזיק boot.py ישן שנותר מהפיתוח.

14.2.2.1.2.3. סדר החיפוש

סמנטיקת הדריסה שונה עבור מסלול ההרצה של boot.py / main.py ועבור פקודות import רגילות. הבנה מי הוא מי חשובה הן לייצור והן לפיתוח:

  • עבור boot.py ו-main.py: סביבת הריצה מחפשת תחילה עותק מוקפא, ורק אחר כך את מערכת הקבצים. boot.py מוקפא לא ניתן לדריסה על ידי הצבת אחד על כרטיס ה-SD – מי שמחזיק במצלמה אינו יכול לשנות את נקודת הכניסה ללא צריבה מחדש.

  • עבור import foo: סביבת הריצה מחפשת תחילה ב-sys.path – המכסה את /flash, /sdcard, /rom ואת תת-התיקיות lib שלהם – ולאחר מכן מודולים מוקפאים. קובץ foo.py בעל אותו שם בפלאש או ב-SD אכן דורס foo מוקפא. זוהי ההקלה שבפיתוח: הציבו מודול מתוקן על הכרטיס, בצעו soft-reset, וראו את השינוי ללא צריבה מחדש.

מוצר שנשלח המעוניין לדכא את התנהגות מערכת-הקבצים-דורסת-מוקפא עבור imports יכול לרוקן את sys.path מוקדם ב-boot.py

import sys

sys.path.clear()

כאשר sys.path ריק, כל ה-imports מפוענחים מהמודולים המוקפאים בלבד; שום דבר בפלאש, ב-SD או ב-ROMFS אינו יכול להאפיל עליהם.

14.2.2.1.2.4. בעיית הנכסים

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