13.1.13. עורך ההגדרות#

יישומי מצלמה רבים זקוקים לכמה הגדרות שמי שאינו מתכנת יכול לשנות – סף, מצב, סיסמת Wi-Fi – מבלי לגעת בסקריפט. OpenMV Cam Settings Editor הוא הכלי של ה-IDE לכך: קובץ JSON מתאר את הפקדים, העורך הופך את התיאור לטופס, והסקריפט שלכם קורא בחזרה את הערכים השמורים באמצעות json.load(). הקובץ הוא גם התצורה וגם ההגדרה של העורך של עצמו, כך שאותה תצורה השומרת את הערכים גם פורשת את ה-GUI לעריכתם.

תת-התפריט 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 (מערך, נדרש) – רשימת אובייקטי הפקד שהעורך מרנדר, לפי הסדר, כשורות של טופס. זהו כל ה-GUI.

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

כל רשומה ב-controls היא פקד אחד – שדה טקסט, מספר, תיבת סימון, תפריט נפתח, מחוון, קבוצה, רצועת לשוניות, תווית סטטית וכן הלאה. ה-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. שמירה וקריאה של ערכים#

פקד שמחזיק ערך – תיבת סימון, מחוון, שדה טקסט – נושא אותו במפתח value. כשאתם לוחצים Save, העורך כותב את ה-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 פשוט, שום דבר בסקריפט שלכם אינו תלוי בעורך – העורך רק כותב את הקובץ שהסקריפט שלכם כבר קורא. התקן יכול לשאת את התצורה שלו ודרך ידידותית לשנות אותה, בעוד שהסקריפט נשאר קורא פשוט של ערכים.

סוג פקד אחד אוכף את הקלט שלו בעת Save: lineedit עם mask או regex מסרב לתת לעורך לשמור בעוד הטקסט שלו אינו תקין או חלקי, ומציין את השדות לתיקון. כל פקד אחר מגביל את הקלט תוך כדי עריכה – מחוון אינו יכול לצאת מהטווח שלו, spinbox נצמד לגבולותיו – כך שקובץ שמור תמיד תקין.

13.1.13.4. עיון בקובץ התצורה#

סוגי הפקדים מתועדים בעמודים שלהלן, מקובצים לפי מה שהם עושים.