13.1.13. Der Einstellungseditor#
Viele Kameraanwendungen benötigen eine Handvoll Einstellungen, die ein Nicht-Programmierer ändern kann – einen Schwellenwert, einen Modus, ein Wi-Fi-Passwort – ohne das Skript anzufassen. Der OpenMV Cam Settings Editor ist das Werkzeug der IDE dafür: Eine JSON-Datei beschreibt die Steuerelemente, der Editor macht aus dieser Beschreibung ein Formular, und Ihr Skript liest die gespeicherten Werte mit json.load() wieder ein. Die Datei ist sowohl die Konfiguration als auch die Definition ihres eigenen Editors, sodass dieselbe Konfiguration, die die Werte speichert, auch die GUI zu deren Bearbeitung anordnet.
Das Untermenü Tools → OpenMV Cam Settings Editor enthält seine drei Aktionen.
Open OpenMV Cam Settings Config File liest die Konfiguration vom Laufwerk der verbundenen Kamera und öffnet sie im Editor; das Speichern schreibt sie zurück auf die Kamera.
Open Config File macht dasselbe für eine
.json-Datei auf Ihrem Computer, um eine Konfiguration offline vorzubereiten.Create Default Config schreibt eine Ausgangskonfiguration auf die Festplatte und öffnet sie – ein Beispiel, das jeden Steuerelementtyp vorführt und bereit ist, auf die Einstellungen reduziert zu werden, die Ihre eigene Anwendung benötigt.

Der Settings Editor mit geöffneter Standardkonfiguration – die Steuerelemente der JSON-Datei als Formular angeordnet (Tabs, Schieberegler, Dropdown-Menüs, Kontrollkästchen), mit Save und Cancel am unteren Rand.#
13.1.13.1. Aufbau einer Konfigurationsdatei#
Eine Konfigurationsdatei ist ein einzelnes JSON-Objekt mit zwei Schlüsseln:
title (string, optional) – der Titel des Editorfensters. Standard ist
OpenMV Cam Settings Editor.controls (array, erforderlich) – die Liste der Steuerelement-Objekte, die der Editor der Reihe nach als Zeilen eines Formulars darstellt. Dies ist die gesamte grafische Oberfläche.
{
"title": "Camera Settings",
"controls": [
{ "type": "slider", "name": "threshold", "label": "Threshold", "value": 50 },
{ "type": "checkbox", "name": "draw_overlays", "label": "Draw Overlays", "value": true }
]
}
Jeder Eintrag in controls ist ein Steuerelement – ein Textfeld, eine Zahl, ein Kontrollkästchen, ein Dropdown, ein Schieberegler, eine Gruppe, eine Registerkartenleiste, ein statisches Label usw. Sein type wählt das Widget aus; der Rest des Objekts konfiguriert es. Alle Schlüssel, die der Editor nicht erkennt, bleiben beim Speichern der Datei unangetastet, sodass Sie Ihre eigenen Metadaten neben den Steuerelementen in der Datei behalten können.
13.1.13.3. Werte speichern und lesen#
Ein Steuerelement, das einen Wert hält – ein Kontrollkästchen, ein Schieberegler, ein Textfeld – trägt ihn in einem value-Schlüssel. Wenn Sie auf Speichern klicken, schreibt der Editor den aktuellen value jedes Steuerelements direkt wieder in dasselbe Steuerelement-Objekt an Ort und Stelle und schreibt die Datei neu. Die Datei behält ihre Struktur: nichts verschiebt sich, nur die value-Schlüssel ändern sich. Es gibt keine separate flache Zuordnung von name zu Wert – die Werte befinden sich innerhalb der Steuerelemente, genau dort, wo sie definiert sind.
Deshalb ist das Layout für Ihr Skript von Bedeutung. Die controls- und tabs-Arrays behalten die Reihenfolge bei, in der Sie sie erstellt haben, sodass die Datei nicht wie ein Wörterbuch nach Namen indiziert werden kann – config["controls"]["threshold"] funktioniert nicht. Dieser kleine Helfer überbrückt die Lücke: Geben Sie ihm den Pfad der Namen bis hinunter zu dem gewünschten Wert, und er durchläuft den Baum für Sie.
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"]
Jeder Schritt im Pfad ist der name eines Steuerelements oder der title einer Registerkarte (eine Registerkarte nimmt keinen eigenen name, obwohl Sie einen hinzufügen können und er beibehalten wird). Die Registerkartenleiste und jede group, die Sie nicht benannt haben, sind transparent – find durchläuft sie – sodass Sie nur die Registerkarten und die von Ihnen beschrifteten Steuerelemente auflisten, ähnlich einer verschachtelten config[...][...]-Abfrage. Eine benannte group ist ein eigener Schritt, sodass find auch ihren Kontrollkästchen-Wert zurückgeben kann. Geben Sie jedem Steuerelement einen eindeutigen name, damit ein Pfad nie mehrdeutig ist. Das Vollständiges Beispiel liest Werte aus dem verschachtelten Demo-Layout aus.
Da die Konfiguration reines JSON ist, hängt nichts in Ihrem Skript vom Editor ab – der Editor schreibt nur die Datei, die Ihr Skript ohnehin liest. Ein Gerät kann seine Konfiguration und eine benutzerfreundliche Möglichkeit, sie zu ändern, mit sich führen, während das Skript ein einfacher Leser von Werten bleibt.
Ein Steuerelementtyp erzwingt seine Eingabe beim Speichern: ein lineedit mit einer mask oder einem regex verweigert dem Editor das Speichern, solange sein Text ungültig oder unvollständig ist, und benennt die zu korrigierenden Felder. Jedes andere Steuerelement beschränkt die Eingabe während der Bearbeitung – ein Schieberegler kann seinen Bereich nicht verlassen, eine Spinbox begrenzt sich auf ihre Grenzen – sodass eine gespeicherte Datei immer gültig ist.
13.1.13.4. Referenz zur Konfigurationsdatei#
Die Steuerelementtypen sind auf den untenstehenden Seiten dokumentiert, gruppiert nach ihrer Funktion.