13.1.13. محرّر الإعدادات#

تحتاج كثير من تطبيقات الكاميرا إلى حفنة من الإعدادات التي يمكن لغير المبرمج تغييرها -- عتبة، أو وضع، أو كلمة مرور Wi-Fi -- دون المساس بالنص البرمجي. وأداة الـ IDE لذلك هي OpenMV Cam Settings Editor: يصف ملف JSON عناصر التحكم، ويحوّل المحرّر ذلك الوصف إلى نموذج، ويقرأ نصك البرمجي القيم المحفوظة باستخدام json.load(). الملف هو التكوين وتعريف محرّره في آنٍ واحد، بحيث يقوم التكوين نفسه الذي يخزّن القيم أيضًا بتخطيط الواجهة الرسومية لتحريرها.

تحتوي القائمة الفرعية Tools → OpenMV Cam Settings Editor على إجراءاتها الثلاثة.

  • يقرأ Open OpenMV Cam Settings Config File التكوين من محرّك الكاميرا المتصلة ويفتحه في المحرّر؛ ويؤدي الحفظ إلى كتابته مرة أخرى إلى الكاميرا.

  • يفعل Open Config File الشيء نفسه لملف .json على جهاز الكمبيوتر الخاص بك، لإعداد تكوين دون اتصال.

  • يكتب Create Default Config إعدادًا مبدئيًا إلى القرص ويفتحه -- مثالًا يجرّب كل نوع من عناصر التحكم، جاهزًا لاختصاره إلى الإعدادات التي يحتاجها تطبيقك الخاص.

The OpenMV Cam Settings Editor showing the default demo configuration -- Camera, Processing, Network, and System tabs, with the Camera tab's exposure and gain sliders, resolution and pixel-format dropdowns, mirror and flip checkboxes, and a digital-zoom slider, above the Save and Cancel buttons

محرّر الإعدادات مع فتح الإعدادات الافتراضية -- عناصر التحكّم في ملف JSON معروضة على هيئة نموذج (علامات تبويب وأشرطة تمرير وقوائم منسدلة ومربّعات اختيار)، مع الزرّين Save وCancel في الأسفل.#

13.1.13.1. تشريح ملف الإعداد#

ملف الإعداد هو كائن JSON واحد يحتوي على مفتاحين:

  • title (سلسلة نصية، اختياري) -- عنوان نافذة المحرر. القيمة الافتراضية هي OpenMV Cam Settings Editor.

  • controls (مصفوفة، مطلوب) -- قائمة كائنات التحكم التي يعرضها المحرر، بالترتيب، كصفوف في نموذج. وهذه هي الواجهة الرسومية بأكملها.

{
  "title": "Camera Settings",
  "controls": [
    { "type": "slider", "name": "threshold", "label": "Threshold", "value": 50 },
    { "type": "checkbox", "name": "draw_overlays", "label": "Draw Overlays", "value": true }
  ]
}

كل عنصر في controls هو عنصر تحكم واحد -- حقل نصي، أو رقم، أو مربع اختيار، أو قائمة منسدلة، أو slider، أو group، أو شريط علامات تبويب، أو تسمية ثابتة، وهكذا. ويحدد type الخاص به عنصر الواجهة؛ ويضبطه باقي الكائن. وأي مفاتيح لا يتعرّف عليها المحرر تُترك دون مساس عند حفظ الملف، لذا يمكنك الاحتفاظ ببياناتك الوصفية الخاصة في الملف إلى جانب عناصر التحكم.

13.1.13.2. المفاتيح التي يشترك فيها كل عنصر تحكم#

يقبل كل كائن تحكم هذه المفاتيح، مهما كان type الخاص به:

  • type (سلسلة نصية، مطلوب) -- عنصر الواجهة المراد بناؤه، مثل "slider" أو "combobox". و*type* المفقود أو غير المعروف يعرض عنصرًا نائبًا معطّلًا باسم Unknown control بدلًا من إفشال الملف بأكمله، لذا يكلّفك الخطأ المطبعي عنصر تحكم واحدًا لا النموذج كله.

  • name (سلسلة نصية) -- معرّف لاستخدامك أنت. لا يقرأه المحرر أبدًا؛ بل يحافظ عليه فقط. امنح كل عنصر تحكم اسمًا name فريدًا ليستخدمه نصك البرمجي في العثور على القيمة المحفوظة لذلك العنصر.

  • label (سلسلة نصية) -- النص المعروض بجوار عنصر التحكم أو عليه. وبالنسبة لمعظم عناصر التحكم يكون هذا نصًا منسّقًا -- إذ تعمل وسوم HTML وروابط <a href>.

  • tooltip (سلسلة نصية) -- نص التلميح عند تمرير المؤشر فوق عنصر التحكم.

  • enabled (قيمة منطقية، الافتراضي true) -- اضبطها على false لعرض عنصر التحكم باهتًا وللقراءة فقط.

تستخدم عناصر تحكم الحاويات (group، tabs) المفتاح title بدلًا من label لعنوانها، ويأخذ tabs تلميحه لكل علامة تبويب على حدة؛ والفروق مذكورة في صفحة كل عنصر تحكم.

13.1.13.3. حفظ القيم وقراءتها#

عنصر التحكم الذي يحمل قيمة -- مربع اختيار، أو slider، أو حقل نصي -- يحملها في المفتاح value. عند النقر على حفظ، يكتب المحرر القيمة value الحالية لكل عنصر تحكم مباشرة إلى كائن التحكم نفسه، في موضعه، ويعيد كتابة الملف. ويحتفظ الملف ببنيته: لا يتحرك شيء، وتتغير مفاتيح value فقط. ولا توجد خريطة مسطّحة منفصلة تربط name بالقيمة -- إذ تعيش القيم داخل عناصر التحكم، تمامًا حيث عُرّفت.

لهذا السبب يهم التخطيط بالنسبة لنصك البرمجي. تحتفظ مصفوفتا controls و*tabs* بالترتيب الذي بنيتهما به، لذا لا يمكن فهرسة الملف بالاسم كما يُفهرس القاموس -- فـ``config["controls"]["threshold"]`` لا يعمل. ويسدّ هذا المساعد الصغير الفجوة: امنحه مسار الأسماء وصولًا إلى القيمة التي تريدها، وسيتنقّل عبر الشجرة نيابة عنك.

import json


def find(node, *path):
    """Return the control at a path of names; read its value with
    ["value"]. The tab strip and any unnamed group are transparent,
    so you list only the tabs and controls you gave a name."""
    for key in path:
        match = _child(node, key)
        if match is None:
            raise KeyError(key)
        node = match
    return node


def _child(node, key):
    for member in node.get("controls", []) + node.get("tabs", []):
        if member.get("name") == key or member.get("title") == key:
            return member
        if "name" not in member and ("controls" in member or "tabs" in member):
            match = _child(member, key)   # see through an unnamed group / the tab strip
            if match is not None:
                return match
    return None


with open("config.json") as file:
    config = json.load(file)

threshold = find(config, "threshold")["value"]

كل خطوة في المسار هي name لعنصر تحكم، أو title لعلامة تبويب (لا تأخذ علامة التبويب name خاصًا بها، وإن كان بإمكانك إضافة واحد فيُحتفظ به). ويكون شريط علامات التبويب وأي group لم تسمِّه شفافين -- إذ يتنقّل find عبرهما -- لذا تُدرج فقط علامات التبويب وعناصر التحكم التي وسمتها، بما يقارب بحثًا متداخلًا من نوع config[...][...]. أما group المسمّى فهو خطوة قائمة بذاتها، لذا يمكن لـ``find`` أن يُرجع أيضًا قيمة مربع اختياره. امنح كل عنصر تحكم اسمًا name فريدًا لكي لا يكون المسار غامضًا أبدًا. ويقرأ مثال كامل القيم من التخطيط التوضيحي المتداخل.

لأن الإعداد هو JSON عادي، لا يعتمد أي شيء في نصك البرمجي على المحرر -- إذ يكتب المحرر فقط الملف الذي يقرأه نصك أصلًا. ويمكن للجهاز أن يحمل إعداده وطريقة سهلة لتغييره بينما يبقى النص البرمجي مجرد قارئ عادي للقيم.

نوع واحد من عناصر التحكم يفرض إدخاله عند الحفظ: فـ*lineedit* المزوّد بـ*mask* أو regex يرفض السماح للمحرر بالحفظ ما دام نصه غير صالح أو غير مكتمل، ويسمّي الحقول التي ينبغي إصلاحها. أما كل عنصر تحكم آخر فيقيّد الإدخال أثناء التحرير -- فـ slider لا يمكنه مغادرة نطاقه، و spinbox يُقيَّد ضمن حدوده -- لذا يكون الملف المحفوظ صالحًا دائمًا.

13.1.13.4. مرجع ملف الإعداد#

أنواع عناصر التحكم موثّقة في الصفحات أدناه، مجمّعة بحسب وظيفتها.