flexPTP-test/GUI.py
András Wiesner cdbec108cb GUI...
2026-04-27 21:47:57 +02:00

483 lines
18 KiB
Python

import queue
from tkinter import Tk, font, ttk
import serial
import serial.tools.list_ports
import ttk_text as ttkt
from ttkthemes import ThemedTk
import tkinter as tk
import netifaces
from DeviceInterface import DeviceInterface
from FlexPtpController import FlexPtpController
from LinuxPtpObserver import LinuxPtpObserver
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")
if not self.__tests_running:
self.start_tests()
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
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")
self.__init_flexPtp_options()
self.__init_linuxptp_options()
self.__init_test_cases()
self.__init_test_controller()
def __init_logtab(self) -> None:
self.__logtab = ttk.Frame(self.__tabs)
self.__tabs.add(self.__logtab, text="Logs")
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")
self.__flexPtp_log_queue = queue.Queue()
self.__linuxptp_log_queue = queue.Queue()
def __init_test_results(self) -> None:
self.__results_tabs = ttk.Frame(self.__tabs)
self.__tabs.add(self.__results_tabs, text="Results")
def __init_tabs(self) -> None:
self.__init_setup_tab()
self.__init_logtab()
self.__init_test_results()
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 __init_print_polls(self) -> None:
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")
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.__win.after(50, print_logs)
self.__win.after(50, print_logs)
def __init__(self) -> None:
self.__win = ThemedTk(theme="arc")
#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.__init_tabs()
self.__tabs.pack(expand=True, fill="both")
self.__init_print_polls()
def mainloop(self) -> None:
self.__win.mainloop()
def __gather_tests(self) -> None:
# gather test cases
self.__tests = []
for name, case in self.__test_cases.items():
ptype = case["type"]
template = {}
if ptype == "general":
template = { "type": ptype, "name": case["delmech"] + " " + case["layer"], "delmech": case["delmech"], "layer": case["layer"] }
elif ptype == "defined":
template = { "type": ptype, "name": name }
if case["master"].get():
master_mode = dict(template)
master_mode["mode"] = "master"
master_mode["name"] += " (master)"
self.__tests.append(master_mode)
if case["slave"].get():
slave_mode = dict(template)
slave_mode["mode"] = "slave"
slave_mode["name"] += " (slave)"
self.__tests.append(slave_mode)
def __populate_tests_treeview(self) -> None:
tw = self.__test_tw
items = tw.get_children()
if items != ():
tw.delete(*items)
seqnum = 1
n = len(self.__tests)
for test in self.__tests:
test["entry"] = tw.insert("", tk.END, text=str(seqnum) + "/" + str(n), values=(test["name"], "?", "Pending", "-", "-"), tags=("pending"))
seqnum += 1
def __open_flexPtp_interface(self) -> None:
url = self.__sel_device.get()
opts = {
"baudrate": self.__sel_baudrate.get(),
"parity": self.__sel_parity.get(),
"stopbits": self.__sel_stopbits.get()
}
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.__flexPtp_controller = FlexPtpController(self.__device_interface)
def __init_linuxptp_observer(self) -> None:
self.__linuxptp_observer = LinuxPtpObserver(self.__linuxptp_interface.get())
def echo(data) -> None:
self.__linuxptp_log_queue.put(str(data))
self.__linuxptp_observer.register_observer_callback(echo)
def __init_flexPtp(self) -> None:
ctrl = self.__flexPtp_controller
ctrl.disable_all_logging()
ctrl.set_domain(0)
ctrl.reset_flexptp()
def start_tests(self) -> None:
self.__gather_tests()
self.__populate_tests_treeview()
self.__open_flexPtp_interface()
self.__init_flexPtp()
self.__init_linuxptp_observer()
# ----
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"]
ctrl.start_by_id(id)
priority1 = 128
if test["mode"] == "master":
priority1 = 100
ctrl.set_priority(priority1, 255)
observer.start_linuxptp(id)
for test in self.__tests:
pass
def stop_tests(self) -> None:
self.__linuxptp_observer.stop_linuxptp()
self.__device_interface.close()