13.1.13. O editor de configurações#

Muitas aplicações de câmera precisam de um punhado de configurações que um não programador possa alterar – um limiar, um modo, uma senha de Wi-Fi – sem tocar no script. O OpenMV Cam Settings Editor é a ferramenta do IDE para isso: um arquivo JSON descreve os controles, o editor transforma essa descrição em um formulário, e seu script lê os valores salvos de volta com json.load(). O arquivo é tanto a configuração quanto a definição de seu próprio editor, então a mesma configuração que armazena os valores também organiza a GUI para editá-los.

O submenu Ferramentas → OpenMV Cam Settings Editor contém suas três ações.

  • Open OpenMV Cam Settings Config File lê a configuração da unidade da câmera conectada e a abre no editor; salvar a grava de volta na câmera.

  • Open Config File faz o mesmo para um arquivo .json no seu computador, para preparar uma configuração offline.

  • Create Default Config grava uma configuração inicial no disco e a abre – um exemplo que exercita todos os tipos de controle, pronto para ser reduzido às configurações que sua própria aplicação precisa.

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

O editor de configurações com a configuração padrão aberta – os controles do arquivo JSON dispostos como um formulário (abas, controles deslizantes, menus suspensos, caixas de seleção), com Save e Cancel na parte inferior.#

13.1.13.1. Anatomia de um arquivo de configuração#

Um arquivo de configuração é um único objeto JSON com duas chaves:

  • title (string, opcional) – o título da janela do editor. Assume por padrão OpenMV Cam Settings Editor.

  • controls (array, obrigatório) – a lista de objetos de controle que o editor renderiza, em ordem, como linhas de um formulário. Esta é toda a interface gráfica.

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

Cada entrada em controls é um controle – um campo de texto, um número, uma checkbox, uma lista suspensa, um slider, um group, uma faixa de abas, um label estático e assim por diante. Seu type seleciona o widget; o restante do objeto o configura. Quaisquer chaves que o editor não reconhecer permanecem intocadas quando o arquivo é salvo, de modo que você pode manter seus próprios metadados no arquivo junto com os controles.

13.1.13.2. Chaves que todo controle compartilha#

Todo objeto de controle aceita estas chaves, qualquer que seja seu type:

  • type (string, obrigatório) – qual widget construir, por exemplo "slider" ou "combobox". Um type ausente ou não reconhecido renderiza um espaço reservado Unknown control desabilitado em vez de falhar o arquivo inteiro, portanto um erro de digitação custa a você um controle, não o formulário.

  • name (string) – um identificador para o seu uso. O editor nunca o lê; apenas o preserva. Dê a cada controle um name único e seu script o utiliza para encontrar o valor salvo desse controle.

  • label (string) – o texto exibido ao lado ou sobre o controle. Para a maioria dos controles isto é texto formatado – tags HTML e links <a href> funcionam.

  • tooltip (string) – texto exibido ao passar o cursor sobre o controle.

  • enabled (boolean, padrão true) – defina como false para exibir o controle esmaecido e somente leitura.

Controles contêiner (group, tabs) usam title em vez de label para seu cabeçalho, e tabs recebe seu tooltip por aba; as diferenças são indicadas na página de cada controle.

13.1.13.3. Salvando e lendo valores#

Um controle que armazena um valor – uma checkbox, um slider, um campo de texto – o carrega em uma chave value. Quando você clica em Save, o editor grava o value atual de cada controle diretamente de volta nesse mesmo objeto de controle, no lugar, e reescreve o arquivo. O arquivo mantém sua estrutura: nada se move, apenas as chaves value mudam. Não existe um mapa plano separado de name para valor – os valores vivem dentro dos controles, exatamente onde são definidos.

É por isso que o layout importa para o seu script. Os arrays controls e tabs mantêm a ordem em que você os construiu, de modo que o arquivo não pode ser indexado por nome como um dicionário – config["controls"]["threshold"] não funciona. Este pequeno auxiliar preenche a lacuna: dê a ele o caminho de nomes até o valor desejado, e ele percorre a árvore por você.

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

Cada etapa do caminho é o name de um controle ou o title de uma aba (uma aba não possui um name próprio, embora você possa adicionar um e ele seja mantido). A barra de abas e qualquer group que você não nomeou são transparentes – find percorre-os – portanto você lista apenas as abas e os controles que rotulou, algo próximo de uma consulta config[...][...] aninhada. Um group nomeado é uma etapa em si, portanto find também pode retornar o valor de sua caixa de seleção. Dê a cada controle um name único para que um caminho nunca seja ambíguo. O Exemplo completo lê valores do layout de demonstração aninhado.

Como a configuração é JSON simples, nada no seu script depende do editor – o editor apenas grava o arquivo que seu script já lê. Um dispositivo pode carregar sua configuração e uma maneira amigável de alterá-la, enquanto o script continua sendo um simples leitor de valores.

Um tipo de controle impõe sua entrada ao salvar: um lineedit com uma mask ou regex se recusa a deixar o editor salvar enquanto seu texto estiver inválido ou incompleto, e nomeia os campos a corrigir. Todo outro controle restringe a entrada conforme você edita – um slider não pode sair de seu intervalo, um spinbox se limita aos seus limites – de modo que um arquivo salvo é sempre válido.

13.1.13.4. Referência do arquivo de configuração#

Os tipos de controle estão documentados nas páginas abaixo, agrupados pelo que fazem.