13.1.13. Edytor ustawień#

Wiele aplikacji kamerowych potrzebuje kilku ustawień, które osoba nieprogramująca może zmienić – progu, trybu, hasła Wi-Fi – bez ruszania skryptu. OpenMV Cam Settings Editor to narzędzie IDE do tego celu: plik JSON opisuje kontrolki, edytor zamienia ten opis w formularz, a Twój skrypt odczytuje zapisane wartości z powrotem za pomocą json.load(). Plik jest zarówno konfiguracją, jak i definicją własnego edytora, więc ta sama konfiguracja, która przechowuje wartości, wyznacza również układ GUI do ich edycji.

Podmenu Tools → OpenMV Cam Settings Editor zawiera jego trzy akcje.

  • Open OpenMV Cam Settings Config File odczytuje konfigurację z dysku podłączonej kamery i otwiera ją w edytorze; zapisanie zapisuje ją z powrotem do kamery.

  • Open Config File robi to samo dla pliku .json na Twoim komputerze, do przygotowania konfiguracji offline.

  • Create Default Config zapisuje konfigurację startową na dysku i otwiera ją – przykład, który przećwicza każdy typ kontrolki, gotowy do ograniczenia do ustawień, których potrzebuje Twoja własna aplikacja.

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

Edytor ustawień z otwartą domyślną konfiguracją – elementy sterujące pliku JSON rozmieszczone w postaci formularza (karty, suwaki, listy rozwijane, pola wyboru), z przyciskami Save i Cancel na dole.#

13.1.13.1. Budowa pliku konfiguracyjnego#

Plik konfiguracyjny to pojedynczy obiekt JSON z dwoma kluczami:

  • title (string, opcjonalny) – tytuł okna edytora. Domyślnie OpenMV Cam Settings Editor.

  • controls (array, wymagany) – lista obiektów kontrolek, które edytor renderuje po kolei jako wiersze formularza. To cały interfejs graficzny.

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

Każdy wpis w controls to jedna kontrolka – pole tekstowe, liczba, pole wyboru, lista rozwijana, suwak, grupa, pasek zakładek, statyczna etykieta i tak dalej. Jego type wybiera widżet; reszta obiektu go konfiguruje. Wszelkie klucze, których edytor nie rozpoznaje, pozostają nietknięte podczas zapisywania pliku, więc możesz przechowywać w pliku własne metadane obok kontrolek.

13.1.13.2. Klucze wspólne dla wszystkich kontrolek#

Każdy obiekt kontrolki akceptuje te klucze, niezależnie od jego type:

  • type (string, wymagany) – który widżet zbudować, np. "slider" lub "combobox". Brakujący lub nierozpoznany type renderuje wyłączony obiekt zastępczy Unknown control zamiast unieważniać cały plik, więc literówka kosztuje Cię jedną kontrolkę, a nie formularz.

  • name (string) – identyfikator do Twojego użytku. Edytor nigdy go nie odczytuje; jedynie go zachowuje. Nadaj każdej kontrolce unikalny name, a Twój skrypt użyje go do odnalezienia zapisanej wartości tej kontrolki.

  • label (string) – tekst wyświetlany obok kontrolki lub na niej. Dla większości kontrolek jest to tekst sformatowany – działają znaczniki HTML i odnośniki <a href>.

  • tooltip (string) – tekst podpowiedzi wyświetlany po najechaniu na kontrolkę.

  • enabled (boolean, domyślnie true) – ustaw na false, aby wyświetlić kontrolkę wyszarzoną i tylko do odczytu.

Kontrolki kontenerów (group, tabs) używają title zamiast label jako nagłówka, a tabs przyjmuje podpowiedź osobno dla każdej zakładki; różnice są opisane na stronie każdej kontrolki.

13.1.13.3. Zapisywanie i odczytywanie wartości#

Kontrolka, która przechowuje wartość – pole wyboru, suwak, pole tekstowe – przechowuje ją w kluczu value. Gdy klikniesz Zapisz, edytor zapisuje bieżącą value każdej kontrolki z powrotem bezpośrednio do tego samego obiektu kontrolki, w miejscu, i przepisuje plik. Plik zachowuje swoją strukturę: nic się nie przesuwa, zmieniają się tylko klucze value. Nie ma osobnej płaskiej mapy name na wartość – wartości żyją wewnątrz kontrolek, dokładnie tam, gdzie są zdefiniowane.

Dlatego układ ma znaczenie dla Twojego skryptu. Tablice controls i tabs zachowują kolejność, w jakiej je zbudowałeś, więc pliku nie można indeksować po nazwie jak słownika – config["controls"]["threshold"] nie działa. Ta mała funkcja pomocnicza wypełnia tę lukę: podaj jej ścieżkę nazw aż do wartości, której szukasz, a ona przejdzie za Ciebie po drzewie.

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"]

Każdy krok w ścieżce to name kontrolki lub title zakładki (zakładka nie przyjmuje własnego name, choć możesz go dodać i zostanie zachowany). Pasek zakładek oraz każda group, której nie nazwałeś, są przezroczyste – find przez nie przechodzi – więc wymieniasz tylko zakładki i kontrolki, którym nadałeś etykietę, blisko zagnieżdżonego odwołania config[...][...]. Nazwana group jest osobnym krokiem, więc find może też zwrócić wartość jej pola wyboru. Nadaj każdej kontrolce unikalny name, aby ścieżka nigdy nie była niejednoznaczna. Pełny przykład odczytuje wartości z zagnieżdżonego układu demonstracyjnego.

Ponieważ konfiguracja to zwykły JSON, nic w Twoim skrypcie nie zależy od edytora – edytor tylko zapisuje plik, który Twój skrypt i tak już odczytuje. Urządzenie może nosić swoją konfigurację i przyjazny sposób jej zmiany, podczas gdy skrypt pozostaje zwykłym czytelnikiem wartości.

Jeden typ kontrolki wymusza swoje dane wejściowe przy zapisie: lineedit z mask lub regex nie pozwala edytorowi zapisać, dopóki jego tekst jest nieprawidłowy lub niekompletny, i wskazuje pola do poprawienia. Każda inna kontrolka ogranicza dane wejściowe podczas edycji – suwak nie może opuścić swojego zakresu, spinbox przycina wartość do swoich granic – więc zapisany plik jest zawsze prawidłowy.

13.1.13.4. Dokumentacja pliku konfiguracyjnego#

Typy kontrolek są udokumentowane na poniższych stronach, pogrupowane według tego, co robią.