14.2.2.2. بناء صورة ROMFS¶
إنّ صورة ROMFS هي نظام ملفات مقيم في الفلاش للقراءة فقط يركّبه وقت التشغيل تلقائيًا عند /rom. وهي تحلّ مشكلة الأصول التي خُتمت بها الصفحة السابقة: ملفات نماذج تعلّم الآلة، وجداول التسميات، وإعدادات JSON، وقوالب الصور -- أي شيء يفتحه التطبيق ويقرؤه لكنه لا يكتبه أبدًا -- ينتقل إلى البناء دون دفع تكلفة تضمينه بوصفه قيمًا حرفية في 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 مشحون.
والسبب في أهمية ذلك: تأتي ملفات النماذج مع متطلبات محاذاة يفرضها المُحمّل في وقت التشغيل. فملفات .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 بالترجمة المتقاطعة لملفات .py إلى bytecode بصيغة .mpy أثناء دخولها إلى صورة ROMFS، لكي تستوردها الكاميرا دون دفع تكلفة التحليل النحوي وقت التحميل. وتبقى الملفات المصدرية في المحرّر بصيغة .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 هو الموطن الصحيح لكل ما يشحنه التطبيق تقريبًا: المكتبات التي يستوردها، وملفات النماذج التي يحمّلها، والإعدادات التي يقرؤها، وأي أصل جاء مُخرَجه من أداة بناء تُنتج شجرة ملفات (محوّلات النماذج، خطوط أنابيب الصور، أدوات تجميع الأصول)، و-- وهذا مهم -- شيفرة التطبيق نفسها.
أما جانب الوحدات المجمّدة فينبغي أن يبقى صغيرًا: boot.py للإعداد السابق لـ REPL، وmain.py بوصفه نقطة دخول رفيعة، وفقط المكتبات التي لا تستطيع الكاميرا الإقلاع بدونها حقًا. وكل ما عدا ذلك يذهب إلى ROMFS، حيث يكون التكرار عليه مجرد ملف .img جديد محفوظ من الـ IDE ومُعاد الوميض -- دون الحاجة إلى إعادة بناء البرنامج الثابت، ودون حاجة إلى سلسلة أدوات في المتناول للقيام بذلك.
إنّ النمط الناتج عن ذلك هو main.py لا يفعل شيئًا سوى التفويض إلى التطبيق المقيم في ROMFS:
# main.py (frozen)
import app
app.run()
# /rom/app/__init__.py (in ROMFS)
def run():
...
إنّ التغيير في app هو تحرير لـ ROMFS وإعادة وميض. ويبقى بناء البرنامج الثابت ثابتًا طوال مدة المنتَج ما لم يكن هناك شيء على الجانب المجمّد يجب أن يتغير فعلًا.