Added ability to log data when test stand is commanded

This commit is contained in:
Martin Zietz
2021-02-15 12:56:42 +01:00
parent ab2619b184
commit 24281ad9f3
3 changed files with 124 additions and 34 deletions
+117 -22
View File
@@ -108,6 +108,7 @@ class ManualMode(Frame):
row_counter = 0
# setup title text
header = Label(self, text="Manual Input Mode", font=HEADER_FONT, pady=3)
header.grid(row=row_counter, column=0)
@@ -187,12 +188,12 @@ class ManualMode(Frame):
execute_button.grid(row=row_counter, column=0, padx=5)
# add button for quick power_down
power_down_button = Button(self.buttons_frame, text="Power Down All", command=func.power_down_all,
power_down_button = Button(self.buttons_frame, text="Power Down All", command=self.power_down,
pady=5, padx=5, font=BIG_BUTTON_FONT)
power_down_button.grid(row=row_counter, column=1, padx=5)
# add button for reinitialization
reinit_button = Button(self.buttons_frame, text="Reinitialize", command=func.setup_all,
reinit_button = Button(self.buttons_frame, text="Reinitialize", command=self.reinitialize,
pady=5, padx=5, font=BIG_BUTTON_FONT)
reinit_button.grid(row=row_counter, column=2, padx=5)
@@ -206,10 +207,11 @@ class ManualMode(Frame):
self.compensate.trace_add('write', self.change_mode_callback) # call mode change function on checkbox change
def page_switch(self): # function that is called when switching to this page in the UI
self.modes[self.input_mode.get()][2]() # update max values, e.g. calls update_max_fields function
self.modes[self.input_mode.get()][2]() # update max values and units, e.g. calls update_max_fields function
# noinspection PyUnusedLocal
def change_mode_callback(self, var, index, mode): # not sure what the parameters are for, but they are necessary
# not sure what the parameters are for, but they are necessary
def change_mode_callback(self, var, index, mode): # called input mode dropdown is changed
self.unit.set(self.modes[self.input_mode.get()][1]) # change unit text
self.modes[self.input_mode.get()][2]() # update max values, e.g. calls update_max_fields function
@@ -235,6 +237,22 @@ class ManualMode(Frame):
val.set("(%0.2f to %0.2f A)" % (-g.AXES[i].max_amps, g.AXES[i].max_amps))
i += 1
def reinitialize(self): # called on "Reinitialize!" button press
func.setup_all() # reinitialize all PSUs and the Arduino
# log change to the log file if user has selected event logging in the Configure Logging window
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
if logger.event_logging: # data should be logged when test stand is commanded
logger.log_datapoint() # log data
def power_down(self): # called on "power down" button press
func.power_down_all() # power down outputs on all PSUs and the Arduino
# log change to the log file if user has selected event logging in the Configure Logging window
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
if logger.event_logging: # data should be logged when test stand is commanded
logger.log_datapoint() # log data
def execute(self):
function_to_call = self.modes[self.input_mode.get()][0] # get function of appropriate mode
vector = np.array([0, 0, 0], dtype=float)
@@ -245,6 +263,11 @@ class ManualMode(Frame):
function_to_call(vector) # call function
self.controller.StatusDisplay.update_labels() # update status display after change
# log change to the log file if user has selected event logging in the Configure Logging window
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
if logger.event_logging: # data should be logged when test stand is commanded
logger.log_datapoint() # log data
def execute_field(self, vector):
ui_print("field executing", vector)
comp = self.compensate.get()
@@ -538,6 +561,7 @@ class ExecuteCSVMode(Frame):
row_counter = 0
self.row_elements = [] # make list of elements in rows to calculate height available for plot
# setup heading
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)
@@ -564,7 +588,7 @@ class ExecuteCSVMode(Frame):
pady=5, padx=5, font=SMALL_BUTTON_FONT, state="disabled")
self.stop_button.grid(row=0, column=2, padx=5)
# add button for reinitialization
self.reinit_button = Button(self.top_buttons_frame, text="Reinitialize Devices", command=func.setup_all,
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)
@@ -647,6 +671,19 @@ class ExecuteCSVMode(Frame):
self.stop_button["state"] = "disabled"
self.reinit_button["state"] = "normal"
# log change to the log file if user has selected event logging in the Configure Logging window
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
if logger.event_logging: # data should be logged when test stand is commanded
logger.log_datapoint() # log data
def reinitialize(self): # called on "Reinitialize devices" button press
func.setup_all() # reinitialize all PSUs and the Arduino
# log change to the log file if user has selected event logging in the Configure Logging window
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
if logger.event_logging: # data should be logged when test stand is commanded
logger.log_datapoint() # log data
def display_plot(self):
# calculate available height for plot (in pixels):
height_others = 0
@@ -665,7 +702,6 @@ class ExecuteCSVMode(Frame):
class ConfigureLogging(Frame):
# generate window to configure data logging to csv
# ToDo: support logging of axis-independent info like Arduino status
# ToDo (optional): implement logging of a datapoint every time new commands are sent to the test stand
def __init__(self, parent, controller):
Frame.__init__(self, parent)
@@ -674,18 +710,28 @@ class ConfigureLogging(Frame):
self.log_file = None # string containing path of log file
self.regular_logging = False # True if data should be logged regularly
self.event_logging = False # True if data should be logged every time a command is sent to the test stand
# log_datapoint() has to be called wherever a command is sent to the test stand and data should be logged
# it does not happen automatically whenever something is sent to the test stand
# It is done mainly in the functions for UI buttons, but rather inconsistently ToDo(optional): make consistent
self.grid_rowconfigure(ALL, weight=1)
self.grid_columnconfigure(ALL, weight=1)
row_counter = 0
# setup heading
header = Label(self, text="Configure Data Logging", font=HEADER_FONT, pady=3)
header.grid(row=row_counter, column=0, padx=100, sticky=W)
row_counter += 1
# Create and place buttons
# Setup frame to house buttons:
self.top_buttons_frame = Frame(self)
self.top_buttons_frame.grid_rowconfigure(ALL, weight=1)
self.top_buttons_frame.grid_columnconfigure(ALL, weight=1)
self.top_buttons_frame.grid(row=row_counter, column=0, sticky=W, padx=20, pady=10)
self.top_buttons_frame.grid(row=row_counter, column=0, sticky=W, padx=20, pady=5)
self.stop_logging_button = Button(self.top_buttons_frame, text="Stop Logging", command=self.stop_logging,
pady=5, padx=5, font=SMALL_BUTTON_FONT)
@@ -718,6 +764,34 @@ class ConfigureLogging(Frame):
row_counter += 1
# create checkboxes and entries to set how often data should be logged
self.settings_frame = Frame(self)
self.settings_frame.grid_rowconfigure(ALL, weight=1)
self.settings_frame.grid_columnconfigure(ALL, weight=1)
self.settings_frame.grid(row=row_counter, column=0, sticky=W, padx=20)
self.regular_logging_var = BooleanVar(value=True) # create variable for the regular logging checkbox
self.event_logging_var = BooleanVar(value=True) # create variable for the logging on command checkbox
self.log_interval = DoubleVar(value=1) # create variable for logging interval entry field
# create checkboxes for regular and event logging:
self.regular_logging_checkbox = Checkbutton(self.settings_frame, text="Log in regular intervals",
variable=self.regular_logging_var, onvalue=True, offvalue=False)
self.event_logging_checkbox = Checkbutton(self.settings_frame, text="Log whenever test stand is commanded",
variable=self.event_logging_var, onvalue=True, offvalue=False)
self.regular_logging_checkbox.grid(row=0, column=0, sticky=W)
self.event_logging_checkbox.grid(row=1, column=0, sticky=W, columnspan=3)
# Set up entry field for setting logging interval
# Add description label for logging interval entry:
interval_label = Label(self.settings_frame, text=" Interval (s):")
interval_label.grid(row=0, column=1, sticky=W)
# Add entry field to set interval
self.interval_entry = Entry(self.settings_frame, textvariable=self.log_interval)
self.interval_entry.grid(row=0, column=2, sticky=W)
row_counter += 1
# Create checkboxes to select what data to log
self.checkbox_frame = Frame(self)
self.checkbox_frame.grid_rowconfigure(ALL, weight=1)
@@ -748,26 +822,38 @@ class ConfigureLogging(Frame):
def start_logging(self):
ui_print("Started data logging.")
self.update_choices() # update list with ticked checkboxes
self.regular_logging = True
self.regular_logging = self.regular_logging_var.get() # check if regular logging checkbox is ticked
self.event_logging = self.event_logging_var.get() # check if event logging checkbox is ticked
self.update_datapoint_count() # start regular update of label showing how many datapoints have been collected
if self.logged_datapoints.get() == 0: # no data has been logged so far
# (if condition is here to keep timestamps consistent when repeatedly starting/stopping)
log.zero_time = datetime.now() # set reference time for timestamps in log
self.periodic_log(1000) # start periodic logging ToDo: get interval from entry field
# lock/unlock buttons and checkboxes:
self.write_to_file_button["state"] = "disabled"
self.clear_data_button["state"] = "normal"
self.lock_checkboxes()
self.stop_logging_button.tkraise() # switch button to stop
error = False
if self.regular_logging:
try: # try to get log interval
interval_ms = int(self.log_interval.get() * 1000)
except TclError as e: # invalid entry for log interval
messagebox.showwarning("Wrong entry format!", "Invalid entry for log interval:\n%s" % e)
self.event_logging = False # don't start logging if there is a problem
error = True
else:
self.periodic_log(interval_ms) # start periodic logging
if (self.regular_logging or self.event_logging) and not error: # logging is active and no error during setup
# lock/unlock buttons and checkboxes:
self.write_to_file_button["state"] = "disabled"
self.clear_data_button["state"] = "normal"
self.lock_checkboxes()
self.stop_logging_button.tkraise() # switch button to stop
def stop_logging(self):
ui_print("Stopped data logging. Remember to save data to file!")
self.regular_logging = False
self.regular_logging = False # tell everything its time to stop logging
self.event_logging = False # tell everything its time to stop logging
self.write_to_file_button["state"] = "normal" # enable button
self.unlock_checkboxes() # enable checkboxes
self.start_logging_button.tkraise() # switch button to start
self.start_logging_button.tkraise() # switch start/stop button to start
def write_to_file(self): # lets user select a file and writes logged data to it
filepath = log.select_file() # select a file to write to
@@ -788,6 +874,7 @@ class ConfigureLogging(Frame):
self.write_to_file() # run write to file function to save data
log.clear_logged_data() # delete the logged data
log.unsaved_data = False # tell everything that there is no unsaved data remaining
self.logged_datapoints.set(len(log.log_data)) # update the label showing how much data has been logged
ui_print("Log data cleared.")
def update_choices(self):
@@ -798,21 +885,29 @@ class ConfigureLogging(Frame):
if self.checkbox_vars[key].get(): # box is ticked
self.active_keys.append(key) # add corresponding item to the list
def lock_checkboxes(self):
for checkbox in self.checkboxes:
def lock_checkboxes(self): # lock all checkboxes, so they can not be modified while logging
for checkbox in [*self.checkboxes, self.event_logging_checkbox, self.regular_logging_checkbox]:
checkbox.config(state=DISABLED)
self.interval_entry.config(state=DISABLED)
def unlock_checkboxes(self):
for checkbox in self.checkboxes:
for checkbox in [*self.checkboxes, self.event_logging_checkbox, self.regular_logging_checkbox]:
checkbox.config(state=NORMAL)
self.interval_entry.config(state=NORMAL)
def periodic_log(self, interval): # logs data in regular intervals (ms)
if self.regular_logging: # logging in intervals is active
log.log_datapoint(self.active_keys) # add datapoint with active keys to log data frame
self.controller.after(interval, lambda: self.periodic_log(interval)) # call function again after interval
self.log_datapoint()
self.controller.after(interval, lambda: self.periodic_log(interval)) # call again after time interval
def log_datapoint(self): # log a single datapoint based on which checkboxes are ticked
try:
log.log_datapoint(self.active_keys) # add datapoint with active checkboxes to log data frame
except Exception as e:
messagebox.showerror("Error!", "Error while logging data: \n%s" % e)
def update_datapoint_count(self):
if self.regular_logging: # logging is active
if self.regular_logging or self.event_logging: # logging is active
self.logged_datapoints.set(len(log.log_data)) # update label with number of rows in log_data
self.controller.after(1000, self.update_datapoint_count) # call function again after 1 second
+1 -10
View File
@@ -260,16 +260,7 @@ def activate_all(): # enables remote control and output on all PSUs and channel
g.Z_DEVICE.enable_all()
def print_status_3():
ui_print("X-Axis:")
g.X_AXIS.print_status()
ui_print("Y-Axis:")
g.Y_AXIS.print_status()
ui_print("Z-Axis:")
g.Z_AXIS.print_status()
def set_to_zero(device): # sets voltages and currents to 0
def set_to_zero(device): # sets voltages and currents to 0 on all channels of a specific PSU
device.voltage1 = 0
device.current1 = 0
device.voltage2 = 0
+6 -2
View File
@@ -23,8 +23,6 @@ class ExecCSVThread(Thread):
def run(self):
ui.ui_print("Starting Sequence Execution...")
# g.threadLock.acquire() # Get lock to synchronize threads
# ToDo: add locking/synchronization? Works without so far but might be more robust
self.execute_sequence(self.array, 0.1, self.parent, self.controller) # run sequence
# reset buttons on UI:
if not g.exitFlag: # program window is open
@@ -64,6 +62,12 @@ class ExecCSVThread(Thread):
func.set_field(field_vec) # send field vector to test stand
ui.ui_print(time.time() - t_zero)
controller.StatusDisplay.update_labels() # update status display after change
# log change to the log file if user has selected event logging in the Configure Logging window
logger = controller.pages[ui.ConfigureLogging] # get object of logging configurator
if logger.event_logging: # data should be logged when test stand is commanded
logger.log_datapoint() # log data
i = i + 1 # next row
g.threadLock.release() # allow going back to main thread now