GUI class separation
This commit is contained in:
parent
cdbec108cb
commit
3b4976155c
@ -29,11 +29,14 @@ class DeviceInterface:
|
||||
os.write(self.__outpipe_rw_fd[1], data.encode())
|
||||
|
||||
if self.__out_cb is not None: # invoke callback if provided
|
||||
self.__out_cb(data)
|
||||
self.__out_cb(data, "")
|
||||
|
||||
except:
|
||||
break # handle closing the device
|
||||
|
||||
if self.__out_cb is not None: # invoke callback if provided
|
||||
self.__out_cb("", "exit")
|
||||
|
||||
|
||||
def __init__(self, url: str, options: dict = {}) -> None:
|
||||
"""
|
||||
@ -141,7 +144,7 @@ class DeviceInterface:
|
||||
timeout = True
|
||||
|
||||
if separate_results: # separation requested
|
||||
records = re.findall("^[ ]*([^:]+)[ ]*:[ ]*(.+)[ ]*$", results.strip(), flags=re.MULTILINE)
|
||||
records = re.findall(r"^[ ]*([^:]+)[ ]*:[ ]*(.+)[ ]*$", results.strip(), flags=re.MULTILINE)
|
||||
results = dict[str, str]()
|
||||
for rec in records:
|
||||
results[rec[0]] = rec[1].strip()
|
||||
|
||||
@ -1,8 +1,88 @@
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
from DeviceInterface import DeviceInterface
|
||||
|
||||
class FlexPtpEvent:
|
||||
def __init__(self, name: str, args: dict) -> None:
|
||||
self.__name = name
|
||||
self.__args = args
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self.__name
|
||||
|
||||
def get_args(self) -> dict:
|
||||
return self.__args
|
||||
|
||||
|
||||
class FlexPtpController:
|
||||
def __log_processor(self, data: str, spec_event: str) -> None:
|
||||
if spec_event == "exit":
|
||||
event = FlexPtpEvent("exit", {})
|
||||
self.__event_queue.put(event)
|
||||
return
|
||||
|
||||
# -------
|
||||
|
||||
stripped_data = data.strip()
|
||||
line = stripped_data # strip accidental whitespaces from the start and the end of the line
|
||||
match = re.search(r"^\[LOG-([A-Z:]+)\]", line) # serach for the LOGID
|
||||
if match is not None and len(match.groups()) == 1: # check if LOGID was present
|
||||
log_id = match.group(1) # extract LOGID
|
||||
rest = line[len(match.group(0)):].strip()
|
||||
|
||||
if log_id == "BMCA": # BMCA event has occurred
|
||||
match = re.match(r"([A-Z_]+) -> ([A-Z_]+)", rest)
|
||||
if match is not None and len(match.groups()) == 2:
|
||||
event = FlexPtpEvent("bmca-statechange", { "from": match.group(1), "to": match.group(2) })
|
||||
self.__event_queue.put(event)
|
||||
|
||||
elif log_id == "DEF:S:A" or log_id == "DEF:S:H": # DEFAULT SLAVE log event has occurred
|
||||
if_type = log_id[-1]
|
||||
if if_type == "H": # HLT interface columns
|
||||
cn = 10
|
||||
else: # Addend interface columns
|
||||
cn = 11
|
||||
|
||||
match = re.match(r"^" + r"([-.0-9]+)[ ]+" * (cn - 1) + r"([-.0-9]+)", rest)
|
||||
|
||||
if match is not None and len(match.groups()) == cn:
|
||||
args = {
|
||||
"T1": float(match.group(1) + "." + match.group(2)),
|
||||
"T4": float(match.group(3) + "." + match.group(4)),
|
||||
"Dt": int(match.group(5)) * 1000000000 + int(match.group(6)),
|
||||
"corr_ppb": float(match.group(cn - 2)),
|
||||
"mpd_ns": int(match.group(cn - 1)),
|
||||
"sync_period_ns": int(match.group(cn))
|
||||
}
|
||||
|
||||
self.__event_queue.put(FlexPtpEvent("synclog-slave", args=args))
|
||||
|
||||
elif log_id == "DEF:M:M": # DEFAULT master : mean path delay log event has occurred
|
||||
self.__event_queue.put(FlexPtpEvent("mpd-master", { "mpd_ns": int(rest) }))
|
||||
|
||||
elif log_id == "DEF:M:S": # DEFAULT master : P2P slave state change log event has occurred
|
||||
match = re.match(r"([A-Z_]+) -> ([A-Z_]+)", rest)
|
||||
if match is not None and len(match.groups()) == 2:
|
||||
event = FlexPtpEvent("p2p-slave-statechange", { "from": match.group(1), "to": match.group(2) })
|
||||
self.__event_queue.put(event)
|
||||
|
||||
else:
|
||||
# unhandled event
|
||||
pass
|
||||
|
||||
if self.__log_cb is not None:
|
||||
self.__log_cb(stripped_data + "\n")
|
||||
|
||||
def __init__(self, di: DeviceInterface) -> None:
|
||||
self.__di = di
|
||||
self.__di.register_out_callback(self.__log_processor)
|
||||
|
||||
self.__log_cb: Callable | None = None
|
||||
self.__event_queue = queue.Queue()
|
||||
pass
|
||||
|
||||
def reset_flexptp(self) -> None:
|
||||
@ -47,9 +127,57 @@ class FlexPtpController:
|
||||
self.__di.execute_command("ptp domain {:d}".format(domain))
|
||||
|
||||
def disable_all_logging(self) -> None:
|
||||
logging_types = [ "def", "corr", "ts", "info", "locked", "bmca" ]
|
||||
logging_types = [ "def", "corr", "ts", "info", "locked", "bmca", "logid" ]
|
||||
for lt in logging_types:
|
||||
self.__di.execute_command("ptp log " + lt + " off", expect_results=False)
|
||||
|
||||
def enable_log(self, id: str, en: bool) -> None:
|
||||
if en:
|
||||
onoff = "on"
|
||||
else:
|
||||
onoff = "off"
|
||||
|
||||
self.__di.execute_command("ptp log {:s} {:s}".format(id, onoff), expect_results=False)
|
||||
|
||||
def set_servo_offset(self, offset: int) -> None:
|
||||
self.__di.execute_command("ptp servo offset {:d}".format(offset))
|
||||
|
||||
def register_log_callback(self, cb: Callable | None) -> None:
|
||||
self.__log_cb = cb
|
||||
|
||||
def get_event(self, timeout: float) -> FlexPtpEvent:
|
||||
try:
|
||||
return self.__event_queue.get(timeout=timeout)
|
||||
except:
|
||||
return FlexPtpEvent("none", {})
|
||||
|
||||
def wait_for_event(self, expected_name: str, timeout: float, argcrits = {}) -> FlexPtpEvent:
|
||||
timeout_left = timeout
|
||||
start = time.time_ns()
|
||||
|
||||
event = FlexPtpEvent("none", {})
|
||||
critera_met = False
|
||||
while (event.get_name() != expected_name or not critera_met) and timeout_left > 0:
|
||||
try:
|
||||
event = self.__event_queue.get(timeout=timeout_left) # type: FlexPtpEvent
|
||||
|
||||
# propagate exit event
|
||||
if event.get_name() == "exit":
|
||||
return event
|
||||
|
||||
critera_met = True
|
||||
for c_name, c_value in argcrits.items():
|
||||
args = event.get_args()
|
||||
if c_name in args:
|
||||
if args[c_name] != c_value:
|
||||
critera_met = False
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
now = time.time_ns()
|
||||
timeout_left = timeout - ((now - start) / 1E+09)
|
||||
|
||||
return event
|
||||
|
||||
|
||||
654
GUI.py
654
GUI.py
@ -1,5 +1,10 @@
|
||||
import queue
|
||||
import select
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from tkinter import Tk, font, ttk
|
||||
import numpy
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import ttk_text as ttkt
|
||||
@ -7,344 +12,104 @@ from ttkthemes import ThemedTk
|
||||
import tkinter as tk
|
||||
import netifaces
|
||||
|
||||
from matplotlib.backend_bases import key_press_handler
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
from DeviceInterface import DeviceInterface
|
||||
from FlexPtpController import FlexPtpController
|
||||
from LinuxPtpObserver import LinuxPtpObserver
|
||||
|
||||
from gui.LoggingTab import LoggingTab
|
||||
from gui.SetupTab import SetupTab
|
||||
|
||||
class GUI:
|
||||
def __init_flexPtp_options(self) -> None:
|
||||
title = ttk.Label(self.__setup_tab, text="flexPTP options", font=self.__title_font)
|
||||
frame = ttk.LabelFrame(self.__setup_tab, labelwidget=title)
|
||||
frame.grid(row=0, column=0, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
#frame.pack(anchor="nw", fill="x", padx=12, ipady=4, side=tk.LEFT, expand=True)
|
||||
|
||||
frame.rowconfigure([0, 1, 2, 3 ], weight=1)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=3)
|
||||
|
||||
self.__flexPtp_options_frame = frame
|
||||
|
||||
device_label = ttk.Label(frame, text="Device:")
|
||||
device_label.grid(column=0, row=0, sticky=tk.E)
|
||||
|
||||
baudrate_label = ttk.Label(frame, text="Baudrate:")
|
||||
baudrate_label.grid(column=0, row=1, sticky=tk.E)
|
||||
|
||||
baudrate_label = ttk.Label(frame, text="Parity:")
|
||||
baudrate_label.grid(column=0, row=2, sticky=tk.E)
|
||||
|
||||
baudrate_label = ttk.Label(frame, text="Stopbits:")
|
||||
baudrate_label.grid(column=0, row=3, sticky=tk.E)
|
||||
|
||||
self.__sel_device = tk.StringVar()
|
||||
device_combobox = ttk.Combobox(frame, textvariable=self.__sel_device)
|
||||
device_combobox["values"] = list(map(lambda p: p.device, filter(lambda p: p.subsystem == "usb", serial.tools.list_ports.comports())))
|
||||
device_combobox.current(0)
|
||||
device_combobox.grid(column=1, row=0, sticky=tk.EW, padx=4)
|
||||
|
||||
#device_entry = ttk.Entry(frame, font=self.__console_font, textvariable=self.__sel_device)
|
||||
#device_entry.grid(column=1, row=0, sticky=tk.EW, padx=4)
|
||||
|
||||
self.__sel_baudrate = tk.IntVar()
|
||||
baudrate_combobox = ttk.Combobox(frame, textvariable=self.__sel_baudrate)
|
||||
baudrate_combobox["values"] = (115200, 57600, 38400, 19200, 9600, 4800)
|
||||
baudrate_combobox.current(0)
|
||||
baudrate_combobox.grid(column=1, row=1, sticky=tk.EW, padx=4)
|
||||
|
||||
self.__sel_parity = tk.StringVar()
|
||||
self.__sel_parity.set(serial.PARITY_NONE)
|
||||
parity_frame = tk.Frame(frame)
|
||||
parity_frame.grid(column=1, row=2, sticky=tk.W)
|
||||
|
||||
parity_none = ttk.Radiobutton(parity_frame, text="None", variable=self.__sel_parity, value=serial.PARITY_NONE)
|
||||
parity_none.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
parity_even = ttk.Radiobutton(parity_frame, text="Even", variable=self.__sel_parity, value=serial.PARITY_EVEN)
|
||||
parity_even.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
parity_odd = ttk.Radiobutton(parity_frame, text="Odd", variable=self.__sel_parity, value=serial.PARITY_ODD)
|
||||
parity_odd.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
self.__sel_stopbits = tk.IntVar()
|
||||
self.__sel_stopbits.set(1)
|
||||
stopbits_frame = tk.Frame(frame)
|
||||
stopbits_frame.grid(column=1, row=3, sticky=tk.W)
|
||||
|
||||
stopbits_one = ttk.Radiobutton(stopbits_frame, text="1", variable=self.__sel_stopbits, value=1)
|
||||
stopbits_one.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
stopbits_two = ttk.Radiobutton(stopbits_frame, text="2", variable=self.__sel_stopbits, value=2)
|
||||
stopbits_two.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
|
||||
def __init_linuxptp_options(self) -> None:
|
||||
title = ttk.Label(self.__setup_tab, text="linuxptp options", font=self.__title_font)
|
||||
frame = ttk.LabelFrame(self.__setup_tab, labelwidget=title)
|
||||
frame.grid(row=1, rowspan=1, column=0, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
# frame.pack(anchor="nw", fill="x", ipady=4, padx=12, side=tk.LEFT, expand=True)
|
||||
|
||||
frame.rowconfigure([0, 1, 2], weight=1)
|
||||
# frame.rowconfigure(2, weight=100)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.columnconfigure(1, weight=3)
|
||||
|
||||
self.__linuxptp_options_frame = frame
|
||||
|
||||
path_label = ttk.Label(frame, text="Path:")
|
||||
path_label.grid(column=0, row=0, sticky=tk.E)
|
||||
|
||||
if_label = ttk.Label(frame, text="Interface:")
|
||||
if_label.grid(column=0, row=1, sticky=tk.E)
|
||||
|
||||
arg_label = ttk.Label(frame, text="Arguments:")
|
||||
arg_label.grid(column=0, row=2, sticky=tk.E)
|
||||
|
||||
self.__linuxptp_path = tk.StringVar()
|
||||
self.__linuxptp_path.set("/usr/sbin/ptp4l")
|
||||
path_entry = ttk.Entry(frame, font=self.__console_font, textvariable=self.__linuxptp_path)
|
||||
path_entry.grid(column=1, row=0, sticky=tk.EW, padx=4)
|
||||
|
||||
self.__linuxptp_interface = tk.StringVar()
|
||||
baudrate_combobox = ttk.Combobox(frame, textvariable=self.__linuxptp_interface)
|
||||
baudrate_combobox["values"] = netifaces.interfaces()
|
||||
baudrate_combobox.current(0)
|
||||
baudrate_combobox.grid(column=1, row=1, sticky=tk.EW, padx=4)
|
||||
|
||||
self.__linuxptp_args = tk.StringVar()
|
||||
self.__linuxptp_args.set("")
|
||||
args_entry = ttk.Entry(frame, font=self.__console_font, textvariable=self.__linuxptp_args)
|
||||
args_entry.grid(column=1, row=2, sticky=tk.EW, padx=4)
|
||||
|
||||
|
||||
def __init_test_cases(self) -> None:
|
||||
title = ttk.Label(self.__setup_tab, text="Test cases", font=self.__title_font)
|
||||
frame = ttk.LabelFrame(self.__setup_tab, labelwidget=title)
|
||||
frame.grid(row=0, rowspan=2, column=1, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
# frame.pack(anchor="nw", fill="x", ipady=4, padx=12, side=tk.LEFT, expand=True)
|
||||
|
||||
self.__test_cases_frame = frame
|
||||
|
||||
basic_modes_frame = ttk.LabelFrame(frame, text="Basic modes")
|
||||
basic_modes_frame.rowconfigure([0], weight=2)
|
||||
basic_modes_frame.rowconfigure([1, 2], weight=8)
|
||||
basic_modes_frame.columnconfigure([0], weight=2)
|
||||
basic_modes_frame.columnconfigure([1, 2], weight=8)
|
||||
basic_modes_frame.pack(expand=False, fill="both", anchor=tk.W, side=tk.LEFT, padx=4, pady=4)
|
||||
|
||||
e2e_label = ttk.Label(basic_modes_frame, text="E2E", style="ProfileLabel.TLabel", padding=6)
|
||||
e2e_label.grid(row=0, column=1)
|
||||
|
||||
p2p_label = ttk.Label(basic_modes_frame, text="P2P", style="ProfileLabel.TLabel")
|
||||
p2p_label.grid(row=0, column=2)
|
||||
|
||||
l4_label = ttk.Label(basic_modes_frame, text="L4", style="ProfileLabel.TLabel", padding=10)
|
||||
l4_label.grid(row=1, column=0)
|
||||
|
||||
l2_label = ttk.Label(basic_modes_frame, text="L2", style="ProfileLabel.TLabel")
|
||||
l2_label.grid(row=2, column=0)
|
||||
|
||||
modes = {
|
||||
"e2e_l4": {
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
},
|
||||
"e2e_l2": {
|
||||
"row": 2,
|
||||
"col": 1,
|
||||
},
|
||||
"p2p_l4": {
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
},
|
||||
"p2p_l2": {
|
||||
"row": 2,
|
||||
"col": 2,
|
||||
},
|
||||
}
|
||||
|
||||
self.__test_cases = {}
|
||||
|
||||
for name, pos in modes.items():
|
||||
oframe = ttk.Frame(basic_modes_frame, width=100, height=100, style="ProfileSquare.TFrame")
|
||||
oframe.grid(row=pos["row"], column=pos["col"], sticky=tk.NSEW)
|
||||
oframe.pack_propagate(False)
|
||||
|
||||
iframe = tk.Frame(oframe)
|
||||
iframe.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
|
||||
|
||||
m = tk.BooleanVar()
|
||||
m_chk = ttk.Checkbutton(iframe, text="Master", variable=m, style="ProfileChkBox.TCheckbutton")
|
||||
m_chk.pack(anchor=tk.CENTER, side=tk.TOP)
|
||||
|
||||
s = tk.BooleanVar()
|
||||
s_chk = ttk.Checkbutton(iframe, text="Slave ", variable=s, style="ProfileChkBox.TCheckbutton")
|
||||
s_chk.pack(anchor=tk.CENTER, side=tk.BOTTOM)
|
||||
|
||||
self.__test_cases[name] = {
|
||||
"type": "general",
|
||||
"delmech": name[0:3].upper(),
|
||||
"layer": name[4:6].upper(),
|
||||
"master": m,
|
||||
"slave": s
|
||||
}
|
||||
|
||||
defined_profiles_frame = ttk.LabelFrame(frame, text="Defined profiles")
|
||||
defined_profiles_frame.pack(expand=True, fill="both", anchor=tk.W, side=tk.LEFT, padx=4, pady=4)
|
||||
|
||||
defined_profiles = [ "gPTP" ]
|
||||
|
||||
ri = 0
|
||||
for dprof in defined_profiles:
|
||||
label = ttk.Label(defined_profiles_frame, text=dprof + ":", font=self.__profile_font)
|
||||
label.grid(row=ri, column=0, padx=6)
|
||||
|
||||
m = tk.BooleanVar()
|
||||
m_chk = ttk.Checkbutton(defined_profiles_frame, text="Master", variable=m, style="ProfileChkBox.TCheckbutton")
|
||||
m_chk.grid(row=ri, column=1)
|
||||
|
||||
s = tk.BooleanVar()
|
||||
s_chk = ttk.Checkbutton(defined_profiles_frame, text="Slave ", variable=s, style="ProfileChkBox.TCheckbutton")
|
||||
s_chk.grid(row=ri, column=2)
|
||||
|
||||
self.__test_cases[dprof] = {
|
||||
"type": "defined",
|
||||
"master": m,
|
||||
"slave": s
|
||||
}
|
||||
|
||||
|
||||
def __init_test_controller(self) -> None:
|
||||
title = ttk.Label(self.__setup_tab, text="Test controls", font=self.__title_font)
|
||||
frame = ttk.LabelFrame(self.__setup_tab, labelwidget=title)
|
||||
frame.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
|
||||
self.__tests_running = False
|
||||
|
||||
def start_stop_tests() -> None:
|
||||
self.__start_stop_btn.configure(state="disabled")
|
||||
|
||||
def __start_stop_tests(self) -> str:
|
||||
if not self.__tests_running:
|
||||
self.start_tests()
|
||||
if self.start_tests():
|
||||
self.__tests_running = True
|
||||
return "running"
|
||||
else:
|
||||
return "stopped"
|
||||
else:
|
||||
self.stop_tests()
|
||||
|
||||
self.__tests_running = not self.__tests_running
|
||||
|
||||
if self.__tests_running:
|
||||
self.__start_stop_btn.configure(text="STOP")
|
||||
state = "disabled"
|
||||
else:
|
||||
self.__start_stop_btn.configure(text="START")
|
||||
state = "normal"
|
||||
|
||||
frames = [ self.__flexPtp_options_frame, self.__linuxptp_options_frame, self.__test_cases_frame ]
|
||||
|
||||
def set_widget_state(parent: tk.Widget | tk.Toplevel) -> None:
|
||||
for widget in parent.winfo_children():
|
||||
if len(widget.winfo_children()) == 0 and widget.widgetName != "frame":
|
||||
widget.configure(state=state) # type: ignore
|
||||
else:
|
||||
set_widget_state(widget)
|
||||
|
||||
for frame in frames:
|
||||
set_widget_state(frame)
|
||||
|
||||
self.__start_stop_btn.configure(state="enabled")
|
||||
|
||||
self.__start_stop_btn = ttk.Button(frame, text="START", command=start_stop_tests)
|
||||
self.__start_stop_btn.pack()
|
||||
|
||||
tw = ttk.Treeview(frame, columns=("name", "result", "start", "end", "duration"), selectmode="none")
|
||||
tw.heading("#0", text="#")
|
||||
tw.heading("name", text="Name")
|
||||
tw.heading("result", text="Result")
|
||||
tw.heading("start", text="Start")
|
||||
tw.heading("end", text="End")
|
||||
tw.heading("duration", text="Duration")
|
||||
|
||||
tw.tag_configure("passed", background="LawnGreen")
|
||||
tw.tag_configure("failed", background="Salmon")
|
||||
tw.tag_configure("in_progress", background="Gold")
|
||||
tw.tag_configure("pending", font=("TkDefaultFont", 9, "italic"), foreground="Gray")
|
||||
|
||||
tw.pack()
|
||||
|
||||
self.__test_tw = tw
|
||||
self.__tests_running = False
|
||||
return "stopped"
|
||||
|
||||
|
||||
def __init_setup_tab(self) -> None:
|
||||
self.__setup_tab = ttk.Frame(self.__tabs)
|
||||
self.__setup_tab.columnconfigure(0, weight=1)
|
||||
self.__setup_tab.columnconfigure(1, weight=4)
|
||||
self.__setup_tab.rowconfigure([0, 1], weight=1)
|
||||
self.__setup_tab.rowconfigure(2, weight=100)
|
||||
self.__tabs.add(self.__setup_tab, text="Setup")
|
||||
"""
|
||||
Initialize "setup" tab widgets.
|
||||
"""
|
||||
|
||||
self.__init_flexPtp_options()
|
||||
self.__init_linuxptp_options()
|
||||
self.__init_test_cases()
|
||||
self.__init_test_controller()
|
||||
self.__setup_tab = SetupTab(self.__tabs)
|
||||
self.__setup_tab.register_start_stop_tests_cb(self.__start_stop_tests)
|
||||
|
||||
self.__tabs.add(self.__setup_tab, text="Setup")
|
||||
|
||||
|
||||
def __init_logtab(self) -> None:
|
||||
self.__logtab = ttk.Frame(self.__tabs)
|
||||
self.__tabs.add(self.__logtab, text="Logs")
|
||||
"""
|
||||
Initialize log tab.
|
||||
"""
|
||||
|
||||
self.__logtab.rowconfigure(0, weight=8)
|
||||
self.__logtab.rowconfigure(1, weight=2)
|
||||
|
||||
self.__logtab.columnconfigure(0, weight=1)
|
||||
self.__logtab.columnconfigure(1, weight=1)
|
||||
|
||||
terminal_settings = { "wrap": tk.WORD, "background": "#3D3D3D", "borderwidth": 0, "foreground": "white", "blockcursor": True, "insertbackground": "white"}
|
||||
self.__flexPtp_log = tk.Text(self.__logtab, **terminal_settings)
|
||||
self.__flexPtp_log.grid(column=0, row=0, sticky=tk.NSEW)
|
||||
self.__flexPtp_log.configure(font=self.__console_font, state="disabled")
|
||||
|
||||
self.__linuxptp_log = tk.Text(self.__logtab, **terminal_settings)
|
||||
self.__linuxptp_log.grid(column=1, row=0, sticky=tk.NSEW, pady=0)
|
||||
self.__linuxptp_log.configure(font=self.__console_font, state="disabled")
|
||||
# initialize tab and its widgets
|
||||
self.__logtab = LoggingTab(self.__tabs)
|
||||
|
||||
# initialize messaging queues
|
||||
self.__flexPtp_log_queue = queue.Queue()
|
||||
self.__linuxptp_log_queue = queue.Queue()
|
||||
|
||||
|
||||
def __init_test_results(self) -> None:
|
||||
def __init_results_tab(self) -> None:
|
||||
"""
|
||||
Initialize "test results" tab.
|
||||
"""
|
||||
|
||||
self.__results_tabs = ttk.Frame(self.__tabs)
|
||||
self.__tabs.add(self.__results_tabs, text="Results")
|
||||
|
||||
fig = Figure(figsize=(5, 4), dpi=100)
|
||||
t = numpy.arange(0, 3, .01)
|
||||
ax = fig.add_subplot()
|
||||
line, = ax.plot(t, 2 * numpy.sin(2 * numpy.pi * t))
|
||||
ax.set_xlabel("time [s]")
|
||||
ax.set_ylabel("f(t)")
|
||||
|
||||
canvas = FigureCanvasTkAgg(fig, master=self.__results_tabs) # A tk.DrawingArea.
|
||||
canvas.draw()
|
||||
|
||||
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
|
||||
|
||||
def __init_tabs(self) -> None:
|
||||
"""
|
||||
Initialize tabs.
|
||||
"""
|
||||
|
||||
self.__init_setup_tab()
|
||||
self.__init_logtab()
|
||||
self.__init_test_results()
|
||||
self.__init_results_tab()
|
||||
|
||||
def __init_statusbar(self) -> None:
|
||||
self.__status = tk.StringVar(value="Idle")
|
||||
statusbar = ttk.Label(self.__win, textvariable=self.__status)
|
||||
statusbar.pack(anchor=tk.S, side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
def __init_fonts(self) -> None:
|
||||
self.__console_font = font.Font(family="Ubuntu Mono", size=9)
|
||||
self.__title_font=font.Font(family="Ubuntu", size=14)
|
||||
self.__mode_font=font.Font(family="Ubuntu", size=12, weight="bold")
|
||||
self.__profile_font=font.Font(family="Ubuntu", size=12, slant="italic")
|
||||
|
||||
|
||||
def __init_styles(self) -> None:
|
||||
style = ttk.Style()
|
||||
style.configure("ProfileChkBox.TCheckbutton", font=self.__console_font)
|
||||
style.configure("ProfileSquare.TFrame", bordercolor="#003039", borderwidth=1, relief="solid")
|
||||
style.configure("ProfileLabel.TLabel", font=self.__mode_font)
|
||||
def set_status(self, status: str) -> None:
|
||||
self.__status.set(status)
|
||||
|
||||
|
||||
def __init_print_polls(self) -> None:
|
||||
"""
|
||||
Initialize polled log printing.
|
||||
"""
|
||||
|
||||
def print_logs() -> None:
|
||||
while not self.__flexPtp_log_queue.empty():
|
||||
self.__flexPtp_log.configure(state="normal")
|
||||
self.__flexPtp_log.insert(tk.END, self.__flexPtp_log_queue.get())
|
||||
self.__flexPtp_log.configure(state="disabled")
|
||||
self.__logtab.add_log("flexPTP", self.__flexPtp_log_queue.get())
|
||||
|
||||
while not self.__linuxptp_log_queue.empty():
|
||||
self.__linuxptp_log.configure(state="normal")
|
||||
self.__linuxptp_log.insert(tk.END, self.__linuxptp_log_queue.get())
|
||||
self.__linuxptp_log.configure(state="disabled")
|
||||
self.__logtab.add_log("linuxptp", self.__linuxptp_log_queue.get())
|
||||
|
||||
self.__win.after(50, print_logs)
|
||||
|
||||
@ -353,38 +118,45 @@ class GUI:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__win = ThemedTk(theme="arc")
|
||||
self.__win.minsize(1000, 700)
|
||||
#self.__win.configure()
|
||||
self.__win.title("flexPTP test suite")
|
||||
self.__win.iconphoto(False, tk.PhotoImage(file="media/flexPTP_test.png"))
|
||||
|
||||
self.__init_fonts()
|
||||
self.__init_styles()
|
||||
|
||||
self.__tabs = ttk.Notebook(self.__win)
|
||||
self.__tabs.configure(width=1000, height=600)
|
||||
self.__tabs.configure()
|
||||
|
||||
self.__init_tabs()
|
||||
self.__tabs.pack(expand=True, anchor=tk.S, side=tk.TOP, fill=tk.BOTH)
|
||||
|
||||
self.__tabs.pack(expand=True, fill="both")
|
||||
self.__init_statusbar()
|
||||
|
||||
self.__init_print_polls()
|
||||
|
||||
self.__tests_running = False
|
||||
self.__user_stop = False
|
||||
|
||||
|
||||
def mainloop(self) -> None:
|
||||
self.__win.mainloop()
|
||||
|
||||
|
||||
def __gather_tests(self) -> None:
|
||||
# gather test cases
|
||||
"""
|
||||
Gather user-selected test cases.
|
||||
"""
|
||||
|
||||
self.__tests = []
|
||||
for name, case in self.__test_cases.items():
|
||||
for name, case in self.__setup_tab.get_test_cases().items():
|
||||
ptype = case["type"]
|
||||
|
||||
template = {}
|
||||
if ptype == "general":
|
||||
template = { "type": ptype, "name": case["delmech"] + " " + case["layer"], "delmech": case["delmech"], "layer": case["layer"] }
|
||||
id = case["delmech"] + "_" + case["layer"]
|
||||
name = case["delmech"] + " " + case["layer"]
|
||||
template = { "type": ptype, "name": name, "id": id, "delmech": case["delmech"], "layer": case["layer"] }
|
||||
elif ptype == "defined":
|
||||
template = { "type": ptype, "name": name }
|
||||
template = { "type": ptype, "name": name, "id": name }
|
||||
|
||||
if case["master"].get():
|
||||
master_mode = dict(template)
|
||||
@ -400,7 +172,11 @@ class GUI:
|
||||
|
||||
|
||||
def __populate_tests_treeview(self) -> None:
|
||||
tw = self.__test_tw
|
||||
"""
|
||||
Fill the test treeview.
|
||||
"""
|
||||
|
||||
tw = self.__setup_tab.get_test_treeview()
|
||||
items = tw.get_children()
|
||||
if items != ():
|
||||
tw.delete(*items)
|
||||
@ -413,71 +189,267 @@ class GUI:
|
||||
|
||||
|
||||
def __open_flexPtp_interface(self) -> None:
|
||||
url = self.__sel_device.get()
|
||||
"""
|
||||
Open the flexPTP device interface.
|
||||
"""
|
||||
|
||||
flexPtp_settings = self.__setup_tab.get_settings()["flexPTP"]
|
||||
|
||||
device = flexPtp_settings["device"]
|
||||
opts = {
|
||||
"baudrate": self.__sel_baudrate.get(),
|
||||
"parity": self.__sel_parity.get(),
|
||||
"stopbits": self.__sel_stopbits.get()
|
||||
"baudrate": flexPtp_settings["baudrate"],
|
||||
"parity": flexPtp_settings["parity"],
|
||||
"stopbits": flexPtp_settings["stopbits"]
|
||||
}
|
||||
|
||||
def echo(data: str) -> None:
|
||||
self.__flexPtp_log_queue.put(data.replace("\r", ""))
|
||||
|
||||
self.__device_interface = DeviceInterface(url, opts)
|
||||
self.__device_interface.register_out_callback(echo)
|
||||
self.__device_interface = DeviceInterface(device, opts)
|
||||
self.__flexPtp_controller = FlexPtpController(self.__device_interface)
|
||||
|
||||
|
||||
def __init_linuxptp_observer(self) -> None:
|
||||
self.__linuxptp_observer = LinuxPtpObserver(self.__linuxptp_interface.get())
|
||||
"""
|
||||
Initialize the linuxptp observer.
|
||||
"""
|
||||
|
||||
def echo(data) -> None:
|
||||
self.__linuxptp_log_queue.put(str(data))
|
||||
|
||||
self.__linuxptp_observer.register_observer_callback(echo)
|
||||
linuxptp_settings = self.__setup_tab.get_settings()["linuxptp"]
|
||||
|
||||
self.__linuxptp_observer = LinuxPtpObserver(linuxptp_settings["interface"])
|
||||
|
||||
def __init_flexPtp(self) -> None:
|
||||
"""
|
||||
Initialize flexPTP.
|
||||
"""
|
||||
|
||||
ctrl = self.__flexPtp_controller
|
||||
|
||||
ctrl.disable_all_logging()
|
||||
ctrl.set_domain(0)
|
||||
ctrl.reset_flexptp()
|
||||
ctrl.enable_log("logid", True)
|
||||
|
||||
def test_routine(self) -> None:
|
||||
LINUXPTP_INIT_TIMEOUT = 5
|
||||
BMCA_TIMEOUT = 150
|
||||
SYNC_TIMEOUT = 4
|
||||
SYNC_MAX_CYCLES = 100
|
||||
TARGET_RANGE = 50**2
|
||||
WINDOW_SIZE = 10
|
||||
|
||||
def start_tests(self) -> None:
|
||||
self.__gather_tests()
|
||||
self.__populate_tests_treeview()
|
||||
self.__open_flexPtp_interface()
|
||||
self.__init_flexPtp()
|
||||
self.__init_linuxptp_observer()
|
||||
def flexPtp_echo(data: str) -> None:
|
||||
self.__flexPtp_log_queue.put(str(data))
|
||||
|
||||
# ----
|
||||
def linuxptp_echo(data: str) -> None:
|
||||
self.__linuxptp_log_queue.put(str(data))
|
||||
|
||||
def both_echo(data: str) -> None:
|
||||
flexPtp_echo(data)
|
||||
linuxptp_echo(data)
|
||||
|
||||
self.__flexPtp_controller.register_log_callback(flexPtp_echo)
|
||||
self.__linuxptp_observer.register_observer_callback(linuxptp_echo)
|
||||
|
||||
def time_to_str(t: float) -> str:
|
||||
return time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime(t))
|
||||
|
||||
ctrl = self.__flexPtp_controller
|
||||
observer = self.__linuxptp_observer
|
||||
for test in self.__tests:
|
||||
if test["type"] == "general":
|
||||
id = test["delmech"] + "_" + test["layer"]
|
||||
else:
|
||||
id = test["name"]
|
||||
|
||||
tests_errored = False
|
||||
|
||||
tsn = 1
|
||||
for test in self.__tests:
|
||||
# identify correct mode or profile
|
||||
id = test["id"]
|
||||
mode = test["mode"]
|
||||
|
||||
# set status
|
||||
self.set_status("[{:d}/{:d}] Test in progress: {:s}".format(tsn, len(self.__tests), test["name"]))
|
||||
|
||||
# setup info variables for the test
|
||||
test["start"] = time.time()
|
||||
str_start_time = time_to_str(test["start"])
|
||||
values = (test["name"], "In-progress", str_start_time, "-", "-")
|
||||
self.__setup_tab.get_test_treeview().item(test["entry"], values=values, tags=("in_progress"))
|
||||
|
||||
# announce the start of a new testcase
|
||||
both_echo("\n\n----------\nSTARTING test: {:s} ({:s})\n start time: {:s}\n----------\n\n".format(id, mode, str_start_time))
|
||||
|
||||
# set mode or profile on the flexPTP device
|
||||
ctrl.start_by_id(id)
|
||||
|
||||
# adjust device priority to force required working mode
|
||||
priority1 = 128
|
||||
if test["mode"] == "master":
|
||||
if mode == "master":
|
||||
priority1 = 100
|
||||
|
||||
ctrl.set_priority(priority1, 255)
|
||||
|
||||
# start linuxptp with the matching mode or profile
|
||||
observer.start_linuxptp(id)
|
||||
|
||||
n = 0
|
||||
var = 1E+09
|
||||
dts = []
|
||||
try:
|
||||
# wait for the LISTENING state clearly indicating the startup of the linuxptp
|
||||
event = observer.wait_for_event("bmca-statechange", LINUXPTP_INIT_TIMEOUT, { "to": "LISTENING" })
|
||||
if event.get_name() != "bmca-statechange":
|
||||
raise Exception("linuxptp init failed")
|
||||
|
||||
# turn on BMCA and default logging on the flexPTP device
|
||||
ctrl.enable_log("bmca", True)
|
||||
ctrl.enable_log("def", True)
|
||||
|
||||
# assign flexPTP and linuxptp sides
|
||||
if mode == "slave":
|
||||
master_side = observer
|
||||
slave_side = ctrl
|
||||
observer_bmca_role = "MASTER"
|
||||
ctrl_bmca_role = "SLAVE"
|
||||
else:
|
||||
master_side = ctrl
|
||||
slave_side = observer
|
||||
observer_bmca_role = "SLAVE"
|
||||
ctrl_bmca_role = "MASTER"
|
||||
|
||||
|
||||
for test in self.__tests:
|
||||
pass
|
||||
# wait for the correct BMCA states to settle in
|
||||
error = False
|
||||
error_cause = "none"
|
||||
|
||||
event = ctrl.wait_for_event("bmca-statechange", BMCA_TIMEOUT, { "to": ctrl_bmca_role }) # flexPTP
|
||||
if event.get_name() != "bmca-statechange":
|
||||
raise Exception(event.get_name())
|
||||
|
||||
event = observer.wait_for_event("bmca-statechange", BMCA_TIMEOUT, { "to": observer_bmca_role }) # linuxptp
|
||||
if event.get_name() != "bmca-statechange":
|
||||
raise Exception(event.get_name())
|
||||
|
||||
# wait for the synchronization to settle
|
||||
while n < SYNC_MAX_CYCLES and var > TARGET_RANGE:
|
||||
event = slave_side.get_event(SYNC_TIMEOUT)
|
||||
|
||||
# a state change with the BMCA indicates a clear error
|
||||
if event.get_name() == "synclog-slave":
|
||||
dts.append(event.get_args()["Dt"])
|
||||
elif event.get_name() in ("bmca-statechange", "exit"):
|
||||
raise Exception(event.get_name())
|
||||
|
||||
if n >= WINDOW_SIZE:
|
||||
var = numpy.var(dts[n-10:n])
|
||||
|
||||
n += 1
|
||||
|
||||
# check variance
|
||||
error = not var < TARGET_RANGE
|
||||
|
||||
except Exception as e:
|
||||
error = True
|
||||
error_cause = e.args[0]
|
||||
|
||||
# display linuxptp errors
|
||||
linuxptp_errors = observer.get_errors()
|
||||
if linuxptp_errors != "":
|
||||
linuxptp_errors = "ERROR:\n" + linuxptp_errors
|
||||
linuxptp_echo(linuxptp_errors)
|
||||
|
||||
# stop linuxptp
|
||||
observer.stop_linuxptp()
|
||||
|
||||
# exit if some of the subordinate layers signaled an exit
|
||||
if error and error_cause in ("exit", "linuxptp init failed"):
|
||||
self.set_status("Tests stopped")
|
||||
tests_errored = True
|
||||
|
||||
# save results, conclusion time and calculate duration
|
||||
test["dts"] = dts
|
||||
test["cycles"] = n
|
||||
|
||||
passed = not error
|
||||
if passed:
|
||||
test["result"] = "passed"
|
||||
else:
|
||||
if tests_errored:
|
||||
test["result"] = "errored"
|
||||
else:
|
||||
test["result"] = "failed"
|
||||
|
||||
# save timestamps
|
||||
test["end"] = time.time()
|
||||
test["duration"] = test["end"] - test["start"]
|
||||
|
||||
str_end_time = time_to_str(test["end"])
|
||||
str_duration = time.strftime("%H:%M:%S", time.gmtime(test["duration"]))
|
||||
|
||||
self.__setup_tab.get_test_treeview().item(test["entry"], values=(
|
||||
test["name"], test["result"].upper(),
|
||||
str_start_time,
|
||||
str_end_time,
|
||||
str_duration
|
||||
), tags=(test["result"]))
|
||||
|
||||
# announce the end of the test
|
||||
both_echo("\n\n----------\nCONCLUDING test: {:s} ({:s})\n result: {:s}\n start time: {:s}\n stop time: {:s}\n duration: {:s}\n----------\n\n".format(
|
||||
id, mode, test["result"].upper(), str_start_time, str_end_time, str_duration))
|
||||
|
||||
if tests_errored:
|
||||
break
|
||||
|
||||
tsn += 1
|
||||
|
||||
if not self.__user_stop:
|
||||
self.stop_tests()
|
||||
|
||||
if not tests_errored:
|
||||
self.set_status("Tests concluded")
|
||||
|
||||
self.__user_stop = False
|
||||
|
||||
|
||||
def stop_tests(self) -> None:
|
||||
def start_tests(self) -> bool:
|
||||
"""
|
||||
Start selected tests.
|
||||
"""
|
||||
|
||||
self.__gather_tests()
|
||||
|
||||
if len(self.__tests) == 0:
|
||||
self.set_status("No tests have been selected")
|
||||
return False
|
||||
|
||||
self.__populate_tests_treeview()
|
||||
|
||||
self.set_status("Preparing flexPTP device")
|
||||
|
||||
self.__open_flexPtp_interface()
|
||||
self.__init_flexPtp()
|
||||
|
||||
self.set_status("Starting linuxptp observer")
|
||||
|
||||
self.__init_linuxptp_observer()
|
||||
|
||||
# ----
|
||||
|
||||
self.set_status("Starting tests")
|
||||
|
||||
self.__test_thread = threading.Thread(target=self.test_routine)
|
||||
self.__test_thread.start()
|
||||
|
||||
self.__setup_tab.set_test_state("running")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def stop_tests(self) -> bool:
|
||||
"""
|
||||
Stop tests in progress.
|
||||
"""
|
||||
|
||||
self.__user_stop = True
|
||||
self.__linuxptp_observer.stop_linuxptp()
|
||||
self.__device_interface.close()
|
||||
|
||||
self.__setup_tab.set_test_state("stopped")
|
||||
|
||||
return True
|
||||
|
||||
@ -1,42 +1,101 @@
|
||||
import queue
|
||||
import signal
|
||||
import subprocess
|
||||
import select
|
||||
from threading import Thread
|
||||
import time
|
||||
from typing import Callable
|
||||
import re
|
||||
|
||||
class LinuxPtpEvent:
|
||||
def __init__(self, name: str, args: dict) -> None:
|
||||
self.__name = name
|
||||
self.__args = args
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self.__name
|
||||
|
||||
def get_args(self) -> dict:
|
||||
return self.__args
|
||||
|
||||
class LinuxPtpObserver:
|
||||
OFFSET_REGEX = r"ptp4l\[[0-9.]+\]: master offset[ ]+([-\d]+) s\d freq[ ]+([-\d]+) path delay[ ]+([\d]+)"
|
||||
BMCA_REGEX = r"ptp4l\[[0-9.]+\]: port \d+ \([a-z0-9]+\): ([A-Z_]+) to ([A-Z_]+) on [A-Z_]+"
|
||||
|
||||
def __init__(self, ni: str) -> None:
|
||||
self.__ni = ni
|
||||
self.__process: subprocess.Popen
|
||||
self.__observer_thread: Thread
|
||||
self.__observer_cb: Callable | None = None
|
||||
self.__event_queue = queue.Queue()
|
||||
self.__error_queue = queue.Queue()
|
||||
pass
|
||||
|
||||
def observer_routine(self) -> None:
|
||||
if self.__process.stdout is not None:
|
||||
pfd = select.poll()
|
||||
if self.__process.stdout is not None:
|
||||
pfd.register(self.__process.stdout.fileno(), select.POLLIN)
|
||||
else:
|
||||
return
|
||||
|
||||
if self.__process.stderr is not None:
|
||||
pfd.register(self.__process.stderr.fileno(), select.POLLIN)
|
||||
else:
|
||||
return
|
||||
|
||||
while self.__process.poll() is None:
|
||||
res = pfd.poll()
|
||||
|
||||
if len(res) > 0 and res[0][1] & select.POLLIN:
|
||||
data = self.__process.stdout.readline()
|
||||
if self.__observer_cb is not None:
|
||||
self.__observer_cb(data)
|
||||
for fde in res:
|
||||
if fde[0] == self.__process.stdout.fileno() and fde[1] & select.POLLIN: # STDOUT
|
||||
orig_data = self.__process.stdout.readline()
|
||||
data = orig_data.strip()
|
||||
|
||||
# SYNCLOG lines
|
||||
match = re.match(LinuxPtpObserver.OFFSET_REGEX, data)
|
||||
if match is not None:
|
||||
args = {
|
||||
"Dt": int(match.group(1)),
|
||||
"mpd_ns": int(match.group(3))
|
||||
}
|
||||
event = LinuxPtpEvent("synclog-slave", args)
|
||||
self.__event_queue.put(event)
|
||||
|
||||
# BMCA state change lines
|
||||
match = re.match(LinuxPtpObserver.BMCA_REGEX, data)
|
||||
if match is not None:
|
||||
args = {
|
||||
"from": match.group(1),
|
||||
"to": match.group(2)
|
||||
}
|
||||
event = LinuxPtpEvent("bmca-statechange", args)
|
||||
self.__event_queue.put(event)
|
||||
|
||||
|
||||
if self.__observer_cb is not None:
|
||||
self.__observer_cb(orig_data)
|
||||
|
||||
elif fde[0] == self.__process.stderr.fileno() and fde[1] & select.POLLIN: # STDERR
|
||||
text = self.__process.stderr.read()
|
||||
self.__error_queue.put(text)
|
||||
event = LinuxPtpEvent("error", { "text": text })
|
||||
self.__event_queue.put(event)
|
||||
|
||||
|
||||
# thread has returned
|
||||
event = LinuxPtpEvent("exit", {})
|
||||
self.__event_queue.put(event)
|
||||
|
||||
def start_linuxptp(self, profile: str) -> None:
|
||||
cmd = ["sudo", "ptp4l", "--priority1=127", "--priority2=255", "--gmCapable=1", "--neighborPropDelayThresh=100000",
|
||||
"--min_neighbor_prop_delay=-20000000", "--assume_two_step=1", "--ptp_minor_version=0",
|
||||
"--min_neighbor_prop_delay=-20000000", "--assume_two_step=1", "--ptp_minor_version=0", "--summary_interval=-5",
|
||||
"-i", self.__ni, "-f", "linuxptp_configs/{:s}.cfg".format(profile), "-m", "-l", "6"]
|
||||
|
||||
self.__process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
@ -46,6 +105,51 @@ class LinuxPtpObserver:
|
||||
def stop_linuxptp(self) -> None:
|
||||
self.__process.send_signal(signal.SIGKILL)
|
||||
self.__observer_thread.join()
|
||||
self.__event_queue = queue.Queue()
|
||||
|
||||
def register_observer_callback(self, cb : Callable) -> None:
|
||||
self.__observer_cb = cb
|
||||
|
||||
def get_event(self, timeout: float) -> LinuxPtpEvent:
|
||||
try:
|
||||
return self.__event_queue.get(timeout=timeout)
|
||||
except:
|
||||
return LinuxPtpEvent("none", {})
|
||||
|
||||
def wait_for_event(self, expected_name: str, timeout: float, argcrits = {}) -> LinuxPtpEvent:
|
||||
timeout_left = timeout
|
||||
start = time.time_ns()
|
||||
|
||||
event = LinuxPtpEvent("none", {})
|
||||
critera_met = False
|
||||
while (event.get_name() != expected_name or not critera_met) and timeout_left > 0:
|
||||
try:
|
||||
event = self.__event_queue.get(timeout=timeout_left) # type: LinuxPtpEvent
|
||||
|
||||
# propagate exit event
|
||||
if event.get_name() in ("exit", "error"):
|
||||
return event
|
||||
|
||||
critera_met = True
|
||||
for c_name, c_value in argcrits.items():
|
||||
args = event.get_args()
|
||||
if c_name in args:
|
||||
if args[c_name] != c_value:
|
||||
critera_met = False
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
now = time.time_ns()
|
||||
timeout_left = timeout - ((now - start) / 1E+09)
|
||||
|
||||
return event
|
||||
|
||||
def get_errors(self) -> str:
|
||||
error = ""
|
||||
while not self.__error_queue.empty():
|
||||
error += self.__error_queue.get()
|
||||
|
||||
return error
|
||||
|
||||
|
||||
3
gui/GuiCommon.py
Normal file
3
gui/GuiCommon.py
Normal file
@ -0,0 +1,3 @@
|
||||
TITLE_FONT = ("Ubuntu", 14) # frame title font
|
||||
TERMINAL_FONT = ("Ubuntu Mono", 9) # terminal font
|
||||
MODE_FONT = ("Ubuntu", 12, "bold") # mode font
|
||||
41
gui/LoggingTab.py
Normal file
41
gui/LoggingTab.py
Normal file
@ -0,0 +1,41 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Literal
|
||||
|
||||
from gui.TerminalDisplay import TerminalDisplay
|
||||
|
||||
class LoggingTab(ttk.Frame):
|
||||
def __init_layout(self) -> None:
|
||||
self.rowconfigure(0, weight=8)
|
||||
self.rowconfigure(1, weight=2)
|
||||
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.columnconfigure(1, weight=1)
|
||||
|
||||
def __init_widgets(self) -> None:
|
||||
self.__flexPtp_log = TerminalDisplay(self)
|
||||
self.__flexPtp_log.grid(column=0, row=0, sticky=tk.NSEW)
|
||||
|
||||
self.__linuxptp_log = TerminalDisplay(self)
|
||||
self.__linuxptp_log.grid(column=1, row=0, sticky=tk.NSEW)
|
||||
|
||||
def __init__(self, notebook: ttk.Notebook) -> None:
|
||||
super().__init__(master=notebook)
|
||||
notebook.add(self, text="Logs")
|
||||
|
||||
self.__init_layout() # initialize widget layout
|
||||
self.__init_widgets() # initialize widgets
|
||||
|
||||
def clear_log(self, which: Literal["flexPTP", "linuxptp", "both"]) -> None:
|
||||
if which in ("flexPTP", "both"):
|
||||
self.__flexPtp_log.delete(1, tk.END)
|
||||
|
||||
if which in ("linuxptp", "both"):
|
||||
self.__linuxptp_log.delete(1, tk.END)
|
||||
|
||||
def add_log(self, which: Literal["flexPTP", "linuxptp", "both"], what: str) -> None:
|
||||
if which in ("flexPTP", "both"):
|
||||
self.__flexPtp_log.insert(tk.END, what)
|
||||
|
||||
if which in ("linuxptp", "both"):
|
||||
self.__linuxptp_log.insert(tk.END, what)
|
||||
446
gui/SetupTab.py
Normal file
446
gui/SetupTab.py
Normal file
@ -0,0 +1,446 @@
|
||||
from collections.abc import Callable
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Callable, Literal
|
||||
|
||||
import serial.tools.list_ports
|
||||
import netifaces
|
||||
|
||||
import gui.GuiCommon as gcm
|
||||
|
||||
class FlexPtpOptionsPanel(ttk.LabelFrame):
|
||||
def __init_layout(self) -> None:
|
||||
self.rowconfigure([0, 1, 2, 3 ], weight=1)
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.columnconfigure(1, weight=3)
|
||||
|
||||
def __init_widgets(self) -> None:
|
||||
# initialize labels
|
||||
device_label = ttk.Label(self, text="Device:") # Serial device entry
|
||||
device_label.grid(column=0, row=0, sticky=tk.E)
|
||||
|
||||
baudrate_label = ttk.Label(self, text="Baudrate:") # Baudrate entry
|
||||
baudrate_label.grid(column=0, row=1, sticky=tk.E)
|
||||
|
||||
baudrate_label = ttk.Label(self, text="Parity:") # Parity selector line
|
||||
baudrate_label.grid(column=0, row=2, sticky=tk.E)
|
||||
|
||||
baudrate_label = ttk.Label(self, text="Stopbits:") # Number of stopbits
|
||||
baudrate_label.grid(column=0, row=3, sticky=tk.E)
|
||||
|
||||
# initialize value selectors
|
||||
|
||||
# Serial device
|
||||
self.__sel_device = tk.StringVar() # device var
|
||||
device_combobox = ttk.Combobox(self, textvariable=self.__sel_device)
|
||||
device_combobox["values"] = list(map(lambda p: p.device, filter(lambda p: p.subsystem == "usb", serial.tools.list_ports.comports()))) # list of possible devices
|
||||
device_combobox.current(0)
|
||||
device_combobox.grid(column=1, row=0, sticky=tk.EW, padx=4)
|
||||
|
||||
# Baudrate (default: 115200)
|
||||
self.__sel_baudrate = tk.IntVar() # baudrate var
|
||||
baudrate_combobox = ttk.Combobox(self, textvariable=self.__sel_baudrate)
|
||||
baudrate_combobox["values"] = (115200, 57600, 38400, 19200, 9600, 4800) # possible baudrates
|
||||
baudrate_combobox.current(0)
|
||||
baudrate_combobox.grid(column=1, row=1, sticky=tk.EW, padx=4)
|
||||
|
||||
# Parity (default: NONE)
|
||||
self.__sel_parity = tk.StringVar() # parity var
|
||||
self.__sel_parity.set(serial.PARITY_NONE)
|
||||
parity_frame = tk.Frame(self)
|
||||
parity_frame.grid(column=1, row=2, sticky=tk.W)
|
||||
|
||||
parity_none = ttk.Radiobutton(parity_frame, text="None", variable=self.__sel_parity, value=serial.PARITY_NONE) # no parity
|
||||
parity_none.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
parity_even = ttk.Radiobutton(parity_frame, text="Even", variable=self.__sel_parity, value=serial.PARITY_EVEN) # even parity
|
||||
parity_even.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
parity_odd = ttk.Radiobutton(parity_frame, text="Odd", variable=self.__sel_parity, value=serial.PARITY_ODD) # odd parity
|
||||
parity_odd.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
# Stopbits (default: 1)
|
||||
self.__sel_stopbits = tk.IntVar()
|
||||
self.__sel_stopbits.set(1)
|
||||
stopbits_frame = tk.Frame(self)
|
||||
stopbits_frame.grid(column=1, row=3, sticky=tk.W)
|
||||
|
||||
stopbits_one = ttk.Radiobutton(stopbits_frame, text="1", variable=self.__sel_stopbits, value=1)
|
||||
stopbits_one.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
stopbits_two = ttk.Radiobutton(stopbits_frame, text="2", variable=self.__sel_stopbits, value=2)
|
||||
stopbits_two.pack(anchor=tk.W, side=tk.LEFT)
|
||||
|
||||
|
||||
def __init__(self, master: tk.Misc | None = None) -> None:
|
||||
super().__init__(master)
|
||||
self.configure(labelwidget=ttk.Label(self, text="flexPTP options", font=gcm.TITLE_FONT))
|
||||
|
||||
self.__init_layout() # initialize panel's inner layout
|
||||
self.__init_widgets() # initialize widgets
|
||||
|
||||
def get_settings(self) -> dict:
|
||||
return {
|
||||
"device": self.__sel_device.get(),
|
||||
"baudrate": self.__sel_baudrate.get(),
|
||||
"parity": self.__sel_parity.get(),
|
||||
"stopbits": self.__sel_stopbits.get()
|
||||
}
|
||||
|
||||
|
||||
class LinuxPtpOptionsPanel(ttk.LabelFrame):
|
||||
def __init_layout(self) -> None:
|
||||
self.rowconfigure([0, 1, 2], weight=1)
|
||||
# self.rowconfigure(2, weight=100)
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.columnconfigure(1, weight=3)
|
||||
|
||||
def __init_widgets(self) -> None:
|
||||
# initialize labels
|
||||
path_label = ttk.Label(self, text="Path:") # linuxptp binary path
|
||||
path_label.grid(column=0, row=0, sticky=tk.E)
|
||||
|
||||
if_label = ttk.Label(self, text="Interface:") # network interface
|
||||
if_label.grid(column=0, row=1, sticky=tk.E)
|
||||
|
||||
arg_label = ttk.Label(self, text="Arguments:") # additional arguments passed to the linuxptp
|
||||
arg_label.grid(column=0, row=2, sticky=tk.E)
|
||||
|
||||
# initialize entry widgets
|
||||
|
||||
# linuxptp binary path entry (default: /usr/sbin/ptp4l)
|
||||
self.__linuxptp_path = tk.StringVar() # path var
|
||||
self.__linuxptp_path.set("/usr/sbin/ptp4l")
|
||||
path_entry = ttk.Entry(self, font=gcm.TERMINAL_FONT, textvariable=self.__linuxptp_path)
|
||||
path_entry.grid(column=1, row=0, sticky=tk.EW, padx=4)
|
||||
|
||||
# linuxptp interface selector (default: first interface in list)
|
||||
self.__linuxptp_interface = tk.StringVar() # interface var
|
||||
baudrate_combobox = ttk.Combobox(self, textvariable=self.__linuxptp_interface)
|
||||
baudrate_combobox["values"] = netifaces.interfaces() # list of available interfaces
|
||||
baudrate_combobox.current(0)
|
||||
baudrate_combobox.grid(column=1, row=1, sticky=tk.EW, padx=4)
|
||||
|
||||
# linuxptp additional argument entry (default: <empty>)
|
||||
self.__linuxptp_args = tk.StringVar() # argument var
|
||||
self.__linuxptp_args.set("")
|
||||
args_entry = ttk.Entry(self, font=gcm.TERMINAL_FONT, textvariable=self.__linuxptp_args)
|
||||
args_entry.grid(column=1, row=2, sticky=tk.EW, padx=4)
|
||||
|
||||
def __init__(self, master: tk.Misc | None = None) -> None:
|
||||
super().__init__(master)
|
||||
self.configure(labelwidget=ttk.Label(self, text="linuxptp options", font=gcm.TITLE_FONT))
|
||||
|
||||
self.__init_layout() # initialize layout
|
||||
self.__init_widgets() # initialize widgets
|
||||
|
||||
def get_settings(self) -> dict:
|
||||
return {
|
||||
"path": self.__linuxptp_path.get(),
|
||||
"interface": self.__linuxptp_interface.get(),
|
||||
"arguments": self.__linuxptp_args.get()
|
||||
}
|
||||
|
||||
|
||||
class BasicTestCasesPanel(ttk.LabelFrame):
|
||||
# test modes and grid positions
|
||||
MODES = {
|
||||
"e2e_l4": {
|
||||
"row": 1,
|
||||
"col": 1,
|
||||
},
|
||||
"e2e_l2": {
|
||||
"row": 2,
|
||||
"col": 1,
|
||||
},
|
||||
"p2p_l4": {
|
||||
"row": 1,
|
||||
"col": 2,
|
||||
},
|
||||
"p2p_l2": {
|
||||
"row": 2,
|
||||
"col": 2,
|
||||
},
|
||||
}
|
||||
|
||||
def __init_styles(self) -> None:
|
||||
style = ttk.Style()
|
||||
style.configure("BasicTestChkBox.TCheckbutton", font=gcm.TERMINAL_FONT)
|
||||
style.configure("BasicTestSquare.TFrame", bordercolor="#003039", borderwidth=1, relief="solid")
|
||||
style.configure("BasicTestLabel.TLabel", font=gcm.MODE_FONT)
|
||||
|
||||
def __init_layout(self) -> None:
|
||||
self.rowconfigure([0], weight=2)
|
||||
self.rowconfigure([1, 2], weight=8)
|
||||
self.columnconfigure([0], weight=2)
|
||||
self.columnconfigure([1, 2], weight=8)
|
||||
|
||||
def __init_testcase_grid(self) -> None:
|
||||
# initialize labels
|
||||
e2e_label = ttk.Label(self, text="E2E", style="BasicTestLabel.TLabel", padding=6)
|
||||
e2e_label.grid(row=0, column=1)
|
||||
|
||||
p2p_label = ttk.Label(self, text="P2P", style="BasicTestLabel.TLabel")
|
||||
p2p_label.grid(row=0, column=2)
|
||||
|
||||
l4_label = ttk.Label(self, text="L4", style="BasicTestLabel.TLabel", padding=10)
|
||||
l4_label.grid(row=1, column=0)
|
||||
|
||||
l2_label = ttk.Label(self, text="L2", style="BasicTestLabel.TLabel")
|
||||
l2_label.grid(row=2, column=0)
|
||||
|
||||
# initialize mode selectors for all test cases
|
||||
self.__test_cases = {}
|
||||
for name, pos in self.MODES.items():
|
||||
# cell frame containing all checkboxes
|
||||
oframe = ttk.Frame(self, width=100, height=100, style="BasicTestSquare.TFrame")
|
||||
oframe.grid(row=pos["row"], column=pos["col"], sticky=tk.NSEW) # place to their designated grid position
|
||||
oframe.pack_propagate(False)
|
||||
|
||||
# internal frame
|
||||
iframe = tk.Frame(oframe)
|
||||
iframe.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
|
||||
|
||||
# "Master" checkbox
|
||||
m = tk.BooleanVar()
|
||||
m_chk = ttk.Checkbutton(iframe, text="Master", variable=m, style="BasicTestChkBox.TCheckbutton")
|
||||
m_chk.pack(anchor=tk.CENTER, side=tk.TOP)
|
||||
|
||||
# "Slave" checkbox
|
||||
s = tk.BooleanVar()
|
||||
s_chk = ttk.Checkbutton(iframe, text="Slave ", variable=s, style="BasicTestChkBox.TCheckbutton")
|
||||
s_chk.pack(anchor=tk.CENTER, side=tk.BOTTOM)
|
||||
|
||||
# store test case
|
||||
self.__test_cases[name] = {
|
||||
"type": "general",
|
||||
"delmech": name[0:3].upper(),
|
||||
"layer": name[4:6].upper(),
|
||||
"master": m,
|
||||
"slave": s
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, master: tk.Misc | None = None) -> None:
|
||||
super().__init__(master, text="Basic modes")
|
||||
|
||||
self.__init_styles() # initialize widget styles
|
||||
self.__init_layout() # initialize the layout
|
||||
self.__init_testcase_grid() # initialize the grid widgets
|
||||
|
||||
def get_test_cases(self) -> dict:
|
||||
return self.__test_cases
|
||||
|
||||
|
||||
class DefinedTestCasesPanel(ttk.LabelFrame):
|
||||
DEFINED_PROFILES = [ "gPTP" ]
|
||||
PROFILE_FONT = ("Ubuntu", 12, "italic")
|
||||
|
||||
def __init_styles(self) -> None:
|
||||
style = ttk.Style()
|
||||
style.configure("DefinedTestChkBox.TCheckbutton", font=gcm.TERMINAL_FONT)
|
||||
|
||||
def __init_widgets(self) -> None:
|
||||
self.__test_cases = {}
|
||||
ri = 0
|
||||
for dprof in self.DEFINED_PROFILES:
|
||||
# test case label
|
||||
label = ttk.Label(self, text=dprof + ":", font=self.PROFILE_FONT)
|
||||
label.grid(row=ri, column=0, padx=6)
|
||||
|
||||
# "Master" checkbox
|
||||
m = tk.BooleanVar()
|
||||
m_chk = ttk.Checkbutton(self, text="Master", variable=m, style="DefinedTestChkBox.TCheckbutton")
|
||||
m_chk.grid(row=ri, column=1)
|
||||
|
||||
# "Slave" checkbox
|
||||
s = tk.BooleanVar()
|
||||
s_chk = ttk.Checkbutton(self, text="Slave ", variable=s, style="DefinedTestChkBox.TCheckbutton")
|
||||
s_chk.grid(row=ri, column=2)
|
||||
|
||||
# store test case
|
||||
self.__test_cases[dprof] = {
|
||||
"type": "defined",
|
||||
"master": m,
|
||||
"slave": s
|
||||
}
|
||||
|
||||
def __init__(self, master: tk.Misc | None = None) -> None:
|
||||
super().__init__(master, text="Defined profiles")
|
||||
|
||||
self.__init_styles()
|
||||
self.__init_widgets()
|
||||
|
||||
def get_test_cases(self) -> dict:
|
||||
return self.__test_cases
|
||||
|
||||
|
||||
class TestCasePanel(ttk.LabelFrame):
|
||||
def __init_layout(self) -> None:
|
||||
pass
|
||||
|
||||
def __init_widgets(self) -> None:
|
||||
self.__basic_test_panel = BasicTestCasesPanel(self)
|
||||
self.__basic_test_panel.pack(expand=False, fill=tk.BOTH, anchor=tk.W, side=tk.LEFT, padx=4, pady=4)
|
||||
|
||||
self.__defined_test_panel = DefinedTestCasesPanel(self)
|
||||
self.__defined_test_panel.pack(expand=True, fill="both", anchor=tk.W, side=tk.LEFT, padx=4, pady=4)
|
||||
|
||||
|
||||
def __init__(self, master: tk.Misc | None = None) -> None:
|
||||
super().__init__(master)
|
||||
self.configure(labelwidget= ttk.Label(self, text="Test cases", font=gcm.TITLE_FONT))
|
||||
|
||||
self.__init_layout()
|
||||
self.__init_widgets()
|
||||
|
||||
def get_test_cases(self) -> dict:
|
||||
return dict(self.__basic_test_panel.get_test_cases()) | dict(self.__defined_test_panel.get_test_cases())
|
||||
|
||||
|
||||
class TestManagerMonitorPanel(ttk.LabelFrame):
|
||||
def __init_layout(self) -> None:
|
||||
pass
|
||||
|
||||
def __init_widgets(self) -> None:
|
||||
self.__start_stop_btn = ttk.Button(self, text="START", command=self.__start_stop_tests)
|
||||
self.__start_stop_btn.pack()
|
||||
|
||||
tw = ttk.Treeview(self, columns=("name", "result", "start", "end", "duration"), selectmode="none")
|
||||
tw.heading("#0", text="#")
|
||||
tw.heading("name", text="Name")
|
||||
tw.heading("result", text="Result")
|
||||
tw.heading("start", text="Start")
|
||||
tw.heading("end", text="End")
|
||||
tw.heading("duration", text="Duration")
|
||||
|
||||
tw.tag_configure("passed", background="LawnGreen")
|
||||
tw.tag_configure("failed", background="Salmon")
|
||||
tw.tag_configure("errored", background="firebrick4", foreground="white")
|
||||
tw.tag_configure("in_progress", background="Gold")
|
||||
tw.tag_configure("pending", font=("TkDefaultFont", 9, "italic"), foreground="Gray")
|
||||
|
||||
tw.pack(expand=True, fill="x")
|
||||
|
||||
self.__test_tw = tw
|
||||
|
||||
def __init__(self, master: tk.Misc) -> None:
|
||||
super().__init__(master)
|
||||
self.configure(labelwidget=ttk.Label(self, text="Test controls", font=gcm.TITLE_FONT))
|
||||
|
||||
self.__start_stop_tests_cb: Callable[..., Literal["stopped", "running"]] | None = None
|
||||
|
||||
self.__init_layout()
|
||||
self.__init_widgets()
|
||||
|
||||
def __start_stop_tests(self) -> None:
|
||||
# momentarily disable the start/stop button
|
||||
self.__start_stop_btn.configure(state="disabled")
|
||||
|
||||
# invoke start/stop callback
|
||||
test_state: Literal["stopped", "running"] = "stopped"
|
||||
if self.__start_stop_tests_cb is not None:
|
||||
test_state = self.__start_stop_tests_cb()
|
||||
|
||||
# set button state according to the test state (running/stopped)
|
||||
self.set_test_state(test_state)
|
||||
|
||||
# re-enable start/stop button
|
||||
self.__start_stop_btn.configure(state="enabled")
|
||||
|
||||
def register_start_stop_tests_cb(self, start_stop_tests_cb: Callable) -> None:
|
||||
self.__start_stop_tests_cb = start_stop_tests_cb
|
||||
|
||||
def get_test_treeview(self) -> ttk.Treeview:
|
||||
return self.__test_tw
|
||||
|
||||
def set_test_state(self, state: Literal["stopped", "running"]) -> None:
|
||||
# set button state according to the test state (running/stopped)
|
||||
if state == "running":
|
||||
self.__start_stop_btn.configure(text="STOP")
|
||||
else:
|
||||
self.__start_stop_btn.configure(text="START")
|
||||
|
||||
|
||||
class SetupTab(ttk.Frame):
|
||||
def __init_layout(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.columnconfigure(1, weight=4)
|
||||
self.rowconfigure([0, 1], weight=1)
|
||||
self.rowconfigure(2, weight=100)
|
||||
|
||||
def __init_widgets(self) -> None:
|
||||
self.__flexPtp_options_panel = FlexPtpOptionsPanel(self)
|
||||
self.__flexPtp_options_panel.grid(row=0, column=0, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
|
||||
self.__linuxptp_options_panel = LinuxPtpOptionsPanel(self)
|
||||
self.__linuxptp_options_panel.grid(row=1, rowspan=1, column=0, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
|
||||
self.__test_case_panel = TestCasePanel(self)
|
||||
self.__test_case_panel.grid(row=0, rowspan=2, column=1, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
|
||||
self.__test_mgr_monitor_panel = TestManagerMonitorPanel(self)
|
||||
self.__test_mgr_monitor_panel.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW, ipady=4, padx=4)
|
||||
|
||||
def __init__(self, master: tk.Misc | None = None) -> None:
|
||||
super().__init__(master)
|
||||
|
||||
self.__init_layout()
|
||||
self.__init_widgets()
|
||||
|
||||
self.__start_stop_tests_cb : Callable[..., Literal["stopped", "running"]] | None = None
|
||||
self.__test_mgr_monitor_panel.register_start_stop_tests_cb(self.__start_stop_tests)
|
||||
|
||||
def __start_stop_tests(self) -> str:
|
||||
test_state: Literal["stopped", "running"] = "stopped"
|
||||
if self.__start_stop_tests_cb is not None: # invoke callback
|
||||
test_state = self.__start_stop_tests_cb()
|
||||
|
||||
# set widget state by test state
|
||||
self.set_test_state(test_state)
|
||||
|
||||
# return with current test state
|
||||
return test_state
|
||||
|
||||
def register_start_stop_tests_cb(self, start_stop_tests_cb) -> None:
|
||||
self.__start_stop_tests_cb = start_stop_tests_cb
|
||||
|
||||
|
||||
def get_settings(self) -> dict:
|
||||
settings = {
|
||||
"flexPTP": self.__flexPtp_options_panel.get_settings(),
|
||||
"linuxptp": self.__linuxptp_options_panel.get_settings()
|
||||
}
|
||||
return settings
|
||||
|
||||
def set_option_widgets_state(self, state: Literal["normal", "disabled"]) -> None:
|
||||
# collect frames to deal with
|
||||
frames = [ self.__flexPtp_options_panel, self.__linuxptp_options_panel, self.__test_case_panel ]
|
||||
|
||||
# create a recursive state configuration function that traverses widgets
|
||||
def set_widget_state(parent: tk.Widget | tk.Toplevel) -> None:
|
||||
for widget in parent.winfo_children():
|
||||
if len(widget.winfo_children()) == 0 and widget.widgetName != "frame":
|
||||
widget.configure(state=state) # type: ignore
|
||||
else:
|
||||
set_widget_state(widget)
|
||||
|
||||
# apply required state to all specified frames
|
||||
for frame in frames:
|
||||
set_widget_state(frame)
|
||||
|
||||
def get_test_cases(self) -> dict:
|
||||
return self.__test_case_panel.get_test_cases()
|
||||
|
||||
def get_test_treeview(self) -> ttk.Treeview:
|
||||
return self.__test_mgr_monitor_panel.get_test_treeview()
|
||||
|
||||
def set_test_state(self, state: Literal["stopped", "running"]) -> None:
|
||||
self.__test_mgr_monitor_panel.set_test_state(state)
|
||||
|
||||
if state == "running":
|
||||
self.set_option_widgets_state("disabled")
|
||||
else:
|
||||
self.set_option_widgets_state("normal")
|
||||
|
||||
|
||||
31
gui/TerminalDisplay.py
Normal file
31
gui/TerminalDisplay.py
Normal file
@ -0,0 +1,31 @@
|
||||
from _tkinter import Tcl_Obj
|
||||
import tkinter as tk
|
||||
|
||||
import gui.GuiCommon as gcm
|
||||
|
||||
class TerminalDisplay(tk.Text):
|
||||
# global terminal-style outlook
|
||||
TERMINAL_STYLE = {
|
||||
"wrap": tk.WORD,
|
||||
"background": "#3D3D3D",
|
||||
"borderwidth": 0,
|
||||
"foreground": "white",
|
||||
"blockcursor": True,
|
||||
"insertbackground": "white"
|
||||
}
|
||||
|
||||
def __init__(self, master: tk.Widget) -> None:
|
||||
super().__init__(master=master, **self.TERMINAL_STYLE, font=gcm.TERMINAL_FONT, state=tk.DISABLED)
|
||||
|
||||
def insert(self, index: str | float | Tcl_Obj | tk.Widget, chars: str, *args: str | list[str] | tuple[str, ...]) -> None:
|
||||
self.configure(state=tk.NORMAL)
|
||||
ret = super().insert(index, chars, *args)
|
||||
self.yview(tk.END)
|
||||
self.configure(state=tk.DISABLED)
|
||||
return ret
|
||||
|
||||
def delete(self, index1: str | float | Tcl_Obj | tk.Widget, index2: str | float | Tcl_Obj | tk.Widget | None = None) -> None:
|
||||
self.configure(state=tk.NORMAL)
|
||||
ret = super().delete(index1, index2)
|
||||
self.configure(state=tk.DISABLED)
|
||||
return ret
|
||||
Loading…
x
Reference in New Issue
Block a user