14.2.2.1. تجميد البرامج النصية داخل البرنامج الثابت¶
الوحدة المجمّدة (frozen) هي ملف .py مُترجَم إلى bytecode ومرتبط بصورة البرنامج الثابت في وقت البناء. يستورد وقت التشغيل الوحدة المجمّدة مباشرة من ذاكرة الفلاش، دون أن ينظر إطلاقًا إلى نظام الملفات الموجود على القرص. وبالنسبة لمنتَج مشحون، فهذا هو المكان الصحيح لشيفرة التطبيق: لا شيء يمكن للمستخدم النهائي حذفه، ولا ملف .py قديم على بطاقة SD يمكنه أن يتجاوزه، وتشغّل الكاميرا الشيفرة نفسها عند كل إقلاع بغض النظر عمّا يوجد (إن وُجد) على أقراصها.
تتناول هذه الصفحة تسلسل بدء التشغيل الذي تتّبعه الكاميرا، ثم كيف يقوم manifest.py وتوجيه freeze بدمج التطبيق في عملية البناء.
14.2.2.1.1. تسلسل بدء التشغيل¶
ما الذي يعمل، ومتى، على كاميرا تخرج من إعادة الضبط:
محمّل الإقلاع. يدخل التشغيل نافذة DFU قصيرة يستخدمها OpenMV IDE لدفع تحديثات البرنامج الثابت. تُغلق النافذة بعد بضع ثوانٍ ويسلّم محمّل الإقلاع المهمة إلى MicroPython. يمكن لبرنامج نصي قيد التشغيل إعادة الدخول إلى هذه النافذة عند الطلب باستدعاء
machine.bootloader().تهيئة نظام الملفات المجمّد. قبل أن تعمل أي شيفرة تطبيق، يقوم وقت التشغيل بإقلاع أنظمة الملفات. تُركّب ذاكرة الفلاش الداخلية عند
/flash(وتُهيّأ فارغة إذا لم يكن هناك أي شيء بها). إذا كانت بطاقة SD موجودة و لم يكن هناك ملف علامة باسمSKIPSDموجودًا على الفلاش الداخلية، فإن بطاقة SD تُركّب عند/sdcard. أما ROMFS، عندما يتضمنها البناء، فتُركّب تلقائيًا عند/rom. يُضبط مجلد العمل على مجلد الإقلاع (/sdcardإذا رُكّبت البطاقة، و/flashخلاف ذلك)، ويُملأsys.pathبـ/flashو/flash/libو/sdcardو/sdcard/libو/romو/rom/lib. وتُتولّى عملية الإعداد المقيمة في الفلاش بواسطة وحدة مجمّدة تُسمى_boot.py-- وهي بنية تحتية للمنفذ واللوحة، وليست خطافًا للتطبيق. لا تخصّص التطبيقات_boot.py؛ بل يفعل ذلك البناء. إنّ إسقاط ملفSKIPSDعلى الفلاش من الـ IDE هو الطريقة المدعومة لجعل الكاميرا تقلع من الفلاش الداخلية بدلًا من بطاقة SD.الإعداد السابق لـ REPL. يعمل
boot.pyعند كل إعادة ضبط ناعمة -- الإقلاع البارد، وCtrl-Dمن الـ REPL، وعودة البرنامج النصي قيد التشغيل، والتعافي من المراقب (watchdog) -- قبل أن يصبح الـ REPL قابلًا للوصول. مهمته هي تحضير البيئة التي يعمل فيها بقية النظام: نوع الإعداد الذي يحتاج الـ REPL والتطبيق وأي أدوات تعافٍ إلى وجوده ليعمل. وهو ليس المكان الذي يقيم فيه التطبيق نفسه. فـmain.pyهو نقطة دخول التطبيق.الحلقة الرئيسية.
main.pyهو الحلقة الرئيسية للتطبيق. يعمل مرة واحدة عند الإقلاع البارد، فور انتهاءboot.py. ولا يُعاد تشغيله عند عمليات إعادة الضبط الناعمة اللاحقة -- بل تنتقل الكاميرا إلى الـ REPL بدلًا من ذلك. هذا التباين مهم للتطوير (إذ ينقل Ctrl-D إلى الـ REPL دون إعادة تشغيل الحلقة، حتى يتمكن المطوّر من فحص الحالة) لكنه ليس مهمًا للإنتاج: فالكاميرا الموزّعة ميدانيًا ترى عمليات التشغيل، والمراقب، وعمليات إعادة الضبط الصلبة، وهي جميعًا عمليات إعادة ضبط للعتاد تعيد الدخول إلى مسار الإقلاع البارد وتشغّلmain.pyمجددًا.
14.2.2.1.2. التجميد داخل البرنامج الثابت¶
تُعلَن مجموعة الوحدات المجمّدة الخاصة باللوحة في boards/<TARGET>/manifest.py ضمن شجرة البرنامج الثابت. والـ manifest هو ملف Python صغير يستدعي حفنة من التوجيهات:
freeze("$(OMV_LIB_DIR)/", "foo.py")-- يدمج ملفfoo.pyواحدًا في البناء.package("mylib", base_path="...")-- يدمج حزمة Python متعددة الملفات، مع الحفاظ على تخطيط مجلداتها تحت المسار الأساسي المعطى.include("...")-- يستدعي ملف manifest آخر. تستخدمه manifest اللوحات لمشاركة مجموعات الوحدات المشتركة.require("logging")-- يستدعي وحدةmicropython-libمُسمّاة من المصدر الأصلي بالاسم.
إن أبسط manifest للتطبيق يضيف سطر freeze واحدًا لكل برنامج نصي من المستوى الأعلى وسطر package واحدًا لكل حزمة يعتمد عليها التطبيق.
14.2.2.1.2.1. أين يقيم الكود المصدري¶
يقيم الكود المصدري للتطبيق تحت scripts/libraries/ في شجرة البرنامج الثابت، إلى جانب الوحدات التي يجمّدها البناء مسبقًا. ويتوسّع متغير الـ 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. البناء والوميض (flashing)¶
بمجرد وضع الـ 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 إلى النسخ المجمّدة قبل أن يفحص نظام الملفات إطلاقًا، فالكاميرا المشحونة تشغّل شيفرة البناء حتى لو احتوت بطاقة 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المجمّد. وهذه هي ميزة التطوير: أسقط وحدة مُصحّحة على البطاقة، وأعد الضبط الناعم، وشاهد التغيير دون إعادة الوميض.
يمكن لمنتَج مشحون يرغب في كبح سلوك تجاوز نظام الملفات للوحدات المجمّدة بالنسبة للاستيرادات أن يمسح sys.path مبكرًا في boot.py
import sys
sys.path.clear()
مع كون sys.path فارغًا، تُحلّ جميع الاستيرادات من الوحدات المجمّدة فقط؛ ولا شيء على الفلاش أو SD أو ROMFS يمكنه أن يحجبها.
14.2.2.1.2.4. مشكلة الأصول¶
إنّ التجميد رائع للشيفرة. لكنه ليس رائعًا للأصول الثنائية الكبيرة: ملفات نماذج تعلّم الآلة، وجداول التسميات، وإعدادات JSON، وقوالب الصور. إنّ تضمين هذه بوصفها قيمًا حرفية في Python يضخّم الكود المصدري، ويعيد الترجمة ببطء، ويهدر حاوية الـ bytecode على بيانات سيقرؤها المفسّر خامًا على أي حال. تتناول صفحة بناء صورة ROMFS نظام الملفات المقيم في الفلاش للقراءة فقط الذي يسدّ هذه الفجوة.