13.1.13. Trình chỉnh sửa cài đặt#

Nhiều ứng dụng camera cần một số ít cài đặt mà một người không phải lập trình viên có thể thay đổi -- một ngưỡng, một chế độ, một mật khẩu Wi-Fi -- mà không cần đụng đến script. OpenMV Cam Settings Editor là công cụ của IDE cho việc đó: một tệp JSON mô tả các điều khiển, trình chỉnh sửa biến mô tả đó thành một biểu mẫu, và script của bạn đọc lại các giá trị đã lưu bằng json.load(). Tệp này vừa là cấu hình vừa là định nghĩa của chính trình chỉnh sửa nó, nên cùng một cấu hình lưu các giá trị cũng bố trí giao diện GUI để chỉnh sửa chúng.

Menu con Tools → OpenMV Cam Settings Editor chứa ba hành động của nó.

  • Open OpenMV Cam Settings Config File đọc cấu hình từ ổ đĩa của camera đã kết nối và mở nó trong trình chỉnh sửa; việc lưu sẽ ghi nó trở lại camera.

  • Open Config File làm điều tương tự cho một tệp .json trên máy tính của bạn, để chuẩn bị một cấu hình ngoại tuyến.

  • Create Default Config ghi một cấu hình khởi đầu ra đĩa và mở nó -- một ví dụ sử dụng mọi loại điều khiển, sẵn sàng để rút gọn xuống còn các thiết lập mà ứng dụng của riêng bạn cần.

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

Trình chỉnh sửa cài đặt với cấu hình mặc định đang mở -- các điều khiển của tệp JSON được bố trí dưới dạng biểu mẫu (tab, thanh trượt, danh sách thả xuống, hộp kiểm), với Save và Cancel ở phía dưới.#

13.1.13.1. Cấu tạo của một tệp cấu hình#

Một tệp cấu hình là một đối tượng JSON duy nhất với hai khóa:

  • title (string, tùy chọn) -- tiêu đề của cửa sổ trình chỉnh sửa. Mặc định là OpenMV Cam Settings Editor.

  • controls (array, bắt buộc) -- danh sách các đối tượng điều khiển mà trình chỉnh sửa hiển thị, theo thứ tự, dưới dạng các hàng của một biểu mẫu. Đây là toàn bộ giao diện đồ họa.

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

Mỗi mục trong controls là một điều khiển -- một trường văn bản, một con số, một checkbox, một danh sách thả xuống, một slider, một group, một dải thẻ, một label tĩnh, v.v. type của nó chọn thành phần giao diện; phần còn lại của đối tượng cấu hình nó. Bất kỳ khóa nào trình chỉnh sửa không nhận ra đều được giữ nguyên khi tệp được lưu, nên bạn có thể lưu siêu dữ liệu của riêng mình trong tệp cùng với các điều khiển.

13.1.13.2. Các khóa mà mọi điều khiển đều có chung#

Mọi đối tượng điều khiển đều chấp nhận các khóa này, bất kể type của nó là gì:

  • type (string, bắt buộc) -- thành phần giao diện nào để dựng, ví dụ "slider" hoặc "combobox". Một type thiếu hoặc không nhận ra sẽ hiển thị một chỗ giữ Unknown control bị vô hiệu hóa thay vì làm hỏng cả tệp, nên một lỗi gõ nhầm chỉ làm mất một điều khiển, không phải cả biểu mẫu.

  • name (string) -- một mã định danh dành cho việc sử dụng của bạn. Trình chỉnh sửa không bao giờ đọc nó; nó chỉ giữ lại. Hãy đặt cho mỗi điều khiển một name duy nhất và script của bạn dùng nó để tìm giá trị đã lưu của điều khiển đó.

  • label (string) -- văn bản hiển thị bên cạnh hoặc trên điều khiển. Đối với hầu hết các điều khiển, đây là văn bản định dạng -- các thẻ HTML và liên kết <a href> đều hoạt động.

  • tooltip (string) -- văn bản hiển thị khi di chuột lên điều khiển.

  • enabled (boolean, mặc định true) -- đặt thành false để hiển thị điều khiển bị làm mờ và chỉ đọc.

Các điều khiển vùng chứa (group, tabs) dùng title thay cho label làm tiêu đề của chúng, và tabs nhận tooltip theo từng thẻ; các khác biệt được ghi chú trên trang của mỗi điều khiển.

13.1.13.3. Lưu và đọc giá trị#

Một điều khiển lưu giá trị -- một checkbox, một slider, một trường văn bản -- mang giá trị đó trong khóa value. Khi bạn nhấn Save, trình chỉnh sửa ghi value hiện tại của mỗi điều khiển thẳng trở lại chính đối tượng điều khiển đó, tại chỗ, và ghi lại tệp. Tệp giữ nguyên cấu trúc của nó: không có gì di chuyển, chỉ các khóa value thay đổi. Không có một bản đồ phẳng riêng biệt từ name tới giá trị -- các giá trị nằm bên trong các điều khiển, ngay tại nơi chúng được định nghĩa.

Đó là lý do vì sao bố cục quan trọng đối với script của bạn. Các mảng controlstabs giữ thứ tự bạn đã dựng chúng, nên tệp không thể được lập chỉ mục theo tên như một từ điển -- config["controls"]["threshold"] không hoạt động. Hàm trợ giúp nhỏ này lấp khoảng cách đó: đưa cho nó đường dẫn các tên xuống tới giá trị bạn muốn, và nó sẽ duyệt qua cây thay bạn.

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

Mỗi bước trong đường dẫn là name của một điều khiển, hoặc title của một thẻ (một thẻ không có name riêng, dù bạn có thể thêm một cái và nó được giữ lại). Dải thẻ và bất kỳ group nào bạn không đặt tên đều trong suốt -- find đi qua chúng -- nên bạn chỉ liệt kê các thẻ và các điều khiển mà bạn đã đặt nhãn, gần với một truy vấn config[...][...] lồng nhau. Một group có tên là một bước riêng của nó, nên find cũng có thể trả về giá trị ô đánh dấu của nó. Hãy đặt cho mỗi điều khiển một name duy nhất để đường dẫn không bao giờ mơ hồ. Ví dụ đầy đủ đọc các giá trị ra khỏi bố cục demo lồng nhau.

Bởi vì cấu hình là JSON thuần túy, không có gì trong script của bạn phụ thuộc vào trình chỉnh sửa -- trình chỉnh sửa chỉ ghi tệp mà script của bạn vốn đã đọc. Một thiết bị có thể mang cấu hình của nó và một cách thân thiện để thay đổi nó trong khi script vẫn là một trình đọc giá trị thuần túy.

Một loại điều khiển thực thi kiểm tra đầu vào khi Save: một lineedit với một mask hoặc regex từ chối cho trình chỉnh sửa lưu khi văn bản của nó không hợp lệ hoặc chưa hoàn chỉnh, và nêu tên các trường cần sửa. Mọi điều khiển khác ràng buộc đầu vào khi bạn chỉnh sửa -- một slider không thể vượt ra ngoài phạm vi của nó, một spinbox kẹp vào các giới hạn của nó -- nên một tệp đã lưu luôn hợp lệ.

13.1.13.4. Tham chiếu tệp cấu hình#

Các loại điều khiển được tài liệu hóa trên các trang bên dưới, nhóm theo chức năng của chúng.