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()