From 056fd3efdddd497ecffc27a03a3808d6cf8c8552 Mon Sep 17 00:00:00 2001 From: Martin Zietz Date: Wed, 17 Feb 2021 11:51:20 +0100 Subject: [PATCH] code cleanup and comments UI: CSV mode --- User_Interface.py | 85 +++++++++++++++++++++++++++++------------------ csv_threading.py | 1 + 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/User_Interface.py b/User_Interface.py index bfed20a..7bf1d7d 100644 --- a/User_Interface.py +++ b/User_Interface.py @@ -315,27 +315,31 @@ class ManualMode(Frame): class ExecuteCSVMode(Frame): - # generate configuration window to set program constants + # Mode for executing magnetic field sequences from csv files. + # Inherits the Frame object from Tkinter and is placed in the mainArea of the application window. + # Multithreading is used to execute the sequence without crashing the UI def __init__(self, parent, controller): Frame.__init__(self, parent) - self.parent = parent + self.parent = parent # parent UI object, e.g. the mainArea frame self.controller = controller # object on which mainloop() is running, usually main window + # Functional init: - self.csv_thread = None # the thread object for executing csv + self.csv_thread = None # the thread object for executing a csv sequence self.sequence_array = None # array containing the values from the csv file # Build UI: - self.grid_rowconfigure(ALL, weight=1) + self.grid_rowconfigure(ALL, weight=1) # configure rows and columns of the Tkinter grid to expand with window self.grid_columnconfigure(ALL, weight=1) - row_counter = 0 - self.row_elements = [] # make list of elements in rows to calculate height available for plot + row_counter = 0 # keep track of which grid row we are in - # setup heading + self.row_elements = [] # make list of elements in rows to later calculate height available for plot + + # setup headline header = Label(self, text="Execute CSV Mode", font=HEADER_FONT, pady=3) header.grid(row=row_counter, column=0, padx=100, sticky=W) - self.row_elements.append(header) + self.row_elements.append(header) # add to list of row elements row_counter += 1 @@ -346,38 +350,46 @@ class ExecuteCSVMode(Frame): self.top_buttons_frame.grid_columnconfigure(ALL, weight=1) self.top_buttons_frame.grid(row=row_counter, column=0, sticky=W, padx=20) - self.row_elements.append(self.top_buttons_frame) + self.row_elements.append(self.top_buttons_frame) # add frame to list of row elements - # Create and place buttons + # Create and place buttons: + # add button for selecting a csv file to execute: self.select_file_button = Button(self.top_buttons_frame, text="Select csv file...", command=self.load_csv, pady=5, padx=5, font=SMALL_BUTTON_FONT) self.select_file_button.grid(row=0, column=0, padx=5) + # add button to start running the sequence from the file: self.execute_button = Button(self.top_buttons_frame, text="Run Sequence", command=self.run_sequence, pady=5, padx=5, font=SMALL_BUTTON_FONT, state="disabled") self.execute_button.grid(row=0, column=1, padx=5) + # add button to stop/interrupt the sequence: self.stop_button = Button(self.top_buttons_frame, text="Stop Run", command=self.stop_run, pady=5, padx=5, font=SMALL_BUTTON_FONT, state="disabled") self.stop_button.grid(row=0, column=2, padx=5) - # add button for reinitialization + # add button to reinitialize devices (e.g. after reconnecting a device) self.reinit_button = Button(self.top_buttons_frame, text="Reinitialize Devices", command=self.reinitialize, pady=5, padx=5, font=SMALL_BUTTON_FONT) self.reinit_button.grid(row=0, column=3, padx=5) row_counter += 1 - # setup testing checkboxes + # setup checkboxes to disable checks if devices are connected (enables testing with some devices missing) + # setup frame to house checkboxes: self.checkbox_frame = Frame(self) self.checkbox_frame.grid_rowconfigure(ALL, weight=1) self.checkbox_frame.grid_columnconfigure(ALL, weight=1) self.checkbox_frame.grid(row=row_counter, column=0, sticky=W, padx=20) - self.row_elements.append(self.checkbox_frame) + self.row_elements.append(self.checkbox_frame) # add frame to list of row elements + # setup label to explain checkbox function: checkbox_label = Label(self.checkbox_frame, text="Disable device connection checks:") checkbox_label.grid(row=0, column=0, sticky=W, padx=3) - self.xy_override = BooleanVar(value=False) - self.z_override = BooleanVar(value=False) - self.arduino_override = BooleanVar(value=False) + + # create variables for the checkboxes: + self.xy_override = BooleanVar(value=False) # True to disable connection check for XY PSU + self.z_override = BooleanVar(value=False) # True to disable connection check for Z PSU + self.arduino_override = BooleanVar(value=False) # True to disable connection check for arduino + # create checkboxes: xy_checkbox = Checkbutton(self.checkbox_frame, text="XY PSU", variable=self.xy_override, onvalue=True, offvalue=False) xy_checkbox.grid(row=0, column=1, padx=3) @@ -390,7 +402,7 @@ class ExecuteCSVMode(Frame): row_counter += 1 - # make frame for plot of csv values + # make frame for plot of csv values (plot is generated and placed in display_plot() method) self.plotFrame = Frame(self) self.plotFrame.grid_rowconfigure(0, weight=1) self.plotFrame.grid_columnconfigure(0, weight=1) @@ -402,44 +414,50 @@ class ExecuteCSVMode(Frame): def load_csv(self): # load in csv file to be executed directory = os.path.abspath(os.getcwd()) # get project directory - # open file selection dialogue and save path of selected file + # open file selection dialogue and store path of selected file filename = filedialog.askopenfilename(initialdir=directory, title="Select CSV File", filetypes=(("Comma Separated Values", "*.csv*"), ("All Files", "*.*"))) if exists(filename): # does the file exist? ui_print("File selected:", filename) - try: + try: # try to read data to an array self.sequence_array = csv.read_csv_to_array(filename) # read array from csv - except BaseException as e: + except BaseException as e: # something went wrong, probably wrong format in csv + # display error messages: ui_print("Error while opening file:", e) messagebox.showerror("Error!", "Error while opening file: \n%s" % e) - try: + try: # try to check the values and display the plot csv.check_array_ok(self.sequence_array) # check for values exceeding limits self.display_plot() # plot data and display - except BaseException as e: + except BaseException as e: # something went wrong, probably wrong format in csv + # display error messages: ui_print("Error while processing data from file:", e) messagebox.showerror("Error!", "Error while processing data from file: \n%s" % e) - else: - self.execute_button["state"] = "normal" # activate run button + else: # nothing went wrong + self.execute_button["state"] = "normal" # activate run button --> enable execution elif filename == '': # this happens when file selection window is closed without selecting a file ui_print("No file selected, could not load.") - else: + else: # file does not exist + # display error messages: ui_print("Selected file", filename, "does not exist, could not load.") + messagebox.showerror("File not found", "Selected file %s does not exist, could not load." % filename) - def run_sequence(self): + def run_sequence(self): # called on run button press, starts thread for executing the sequence # (de)activate buttons as needed: self.select_file_button["state"] = "disabled" self.execute_button["state"] = "disabled" self.stop_button["state"] = "normal" self.reinit_button["state"] = "disabled" + # setup thread for running the sequence: + # More info: https://www.tutorialspoint.com/python/python_multithreading.htm g.threadLock = threading.Lock() # create thread locking object, used to ensure all devices switch at once later - # create separate thread to run sequence execution in: + # create thread object: self.csv_thread = csv.ExecCSVThread(self.sequence_array, self, self.controller) self.csv_thread.start() # start thread - def stop_run(self): - self.csv_thread.stop() # this will cause the csv loop to end + def stop_run(self): # called on stop button press, interrupts sequence execution + self.csv_thread.stop() # call stop method of thread object, this will cause the csv loop to end # (de)activate buttons as needed: self.select_file_button["state"] = "normal" self.execute_button["state"] = "normal" @@ -459,19 +477,20 @@ class ExecuteCSVMode(Frame): if logger.event_logging: # data should be logged when test stand is commanded logger.log_datapoint() # log data - def display_plot(self): + def display_plot(self): # generate and display a plot of the data loaded from a csv file # calculate available height for plot (in pixels): - height_others = 0 + height_others = 0 # initialize variable to calculate height of other widgets for element in self.row_elements: # go through all rows in the widget except the plot frame height_others += element.winfo_height() # add up heights - height = self.parent.winfo_height() - height_others - 50 # set to height of parent frame - other rows - margin + # calculate available plot height: + height = self.parent.winfo_height() - height_others - 50 # height of parent frame - other widgets - margin width = min(self.parent.winfo_width() - 100, 1100) # set width to available space but max. 1100 figure = csv.plot_field_sequence(self.sequence_array, width, height) # create figure to be displayed plotCanvas = FigureCanvasTkAgg(figure, self.plotFrame) # create canvas to draw figure on plotCanvas.draw() # equivalent to matplotlib.show() - plotCanvas.get_tk_widget().grid(row=0, column=0, sticky="nesw") # place canvas in UI + plotCanvas.get_tk_widget().grid(row=0, column=0, sticky="nesw") # place canvas in the UI class Configuration(Frame): diff --git a/csv_threading.py b/csv_threading.py index a6963c0..3dbd1cb 100644 --- a/csv_threading.py +++ b/csv_threading.py @@ -36,6 +36,7 @@ class ExecCSVThread(Thread): self.parent.stop_button["state"] = "disabled" self.parent.reinit_button["state"] = "normal" + # setup ability to interrupt thread (https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread) def stop(self): # stop thread execution, can be called from another thread to kill this one self.__stop_event.set()