forked from zietzm/Helmholtz_Test_Bench
many things
This commit is contained in:
Binary file not shown.
+62
-18
@@ -3,6 +3,8 @@ from tkinter import ttk
|
||||
from tkinter import messagebox
|
||||
from tkinter import filedialog
|
||||
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
|
||||
import numpy as np
|
||||
import os
|
||||
from os.path import exists
|
||||
@@ -29,8 +31,8 @@ class HelmholtzGUI(Tk):
|
||||
|
||||
self.Menu = TopMenu(self) # displays menu bar at the top
|
||||
|
||||
mainArea = Frame(self, padx=20, pady=20)
|
||||
mainArea.pack(side="top", fill="both", expand=False)
|
||||
mainArea = Frame(self, padx=10, pady=10)
|
||||
mainArea.pack(side="top", fill="both", expand=True)
|
||||
|
||||
mainArea.grid_rowconfigure(0, weight=1)
|
||||
mainArea.grid_columnconfigure(0, weight=1)
|
||||
@@ -44,7 +46,6 @@ class HelmholtzGUI(Tk):
|
||||
|
||||
status_frame = Frame(self)
|
||||
status_frame.pack(side="bottom", fill="x", expand=False)
|
||||
status_frame.grid_rowconfigure(ALL, weight=1)
|
||||
status_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
self.StatusDisplay = StatusDisplay(status_frame, self)
|
||||
@@ -514,7 +515,6 @@ class Configuration(Frame):
|
||||
class ExecuteCSVMode(Frame):
|
||||
# generate configuration window to set program constants
|
||||
# ToDo (optional): Generate graph to show sequence to be executed
|
||||
# ToDo: add button for reinit
|
||||
|
||||
def __init__(self, parent, controller):
|
||||
Frame.__init__(self, parent)
|
||||
@@ -537,24 +537,57 @@ class ExecuteCSVMode(Frame):
|
||||
|
||||
# Setup buttons
|
||||
# Setup frame to house buttons:
|
||||
self.file_select_frame = Frame(self)
|
||||
self.file_select_frame.grid_rowconfigure(ALL, weight=1)
|
||||
self.file_select_frame.grid_columnconfigure(ALL, weight=1)
|
||||
self.file_select_frame.grid(row=row_counter, column=0, sticky=W, padx=20)
|
||||
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)
|
||||
|
||||
# Create and place buttons
|
||||
self.select_file_button = Button(self.file_select_frame, text="Select csv file...", command=self.load_csv,
|
||||
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)
|
||||
self.execute_button = Button(self.file_select_frame, text="Run Sequence", command=self.run_sequence,
|
||||
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)
|
||||
self.stop_button = Button(self.file_select_frame, text="Stop Run", command=self.stop_run,
|
||||
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
|
||||
self.reinit_button = Button(self.top_buttons_frame, text="Reinitialize Devices", command=func.setup_all,
|
||||
pady=5, padx=5, font=SMALL_BUTTON_FONT)
|
||||
self.reinit_button.grid(row=0, column=3, padx=5)
|
||||
|
||||
row_counter += 1
|
||||
|
||||
# setup testing 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)
|
||||
|
||||
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)
|
||||
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)
|
||||
z_checkbox = Checkbutton(self.checkbox_frame, text="Z PSU",
|
||||
variable=self.z_override, onvalue=True, offvalue=False)
|
||||
z_checkbox.grid(row=0, column=2, padx=3)
|
||||
arduino_checkbox = Checkbutton(self.checkbox_frame, text="Arduino",
|
||||
variable=self.arduino_override, onvalue=True, offvalue=False)
|
||||
arduino_checkbox.grid(row=0, column=3, padx=3)
|
||||
|
||||
row_counter += 1
|
||||
|
||||
# make frame for plot of csv values
|
||||
self.plotFrame = Frame(self)
|
||||
self.plotFrame.grid_rowconfigure(0, weight=1)
|
||||
self.plotFrame.grid_columnconfigure(0, weight=1)
|
||||
self.plotFrame.grid(row=row_counter, column=0, sticky="nsw", padx=10, pady=10)
|
||||
|
||||
def page_switch(self): # function that is called when switching to this window
|
||||
# every class in the UI needs this, even if it doesn't do anything
|
||||
pass
|
||||
@@ -570,9 +603,11 @@ class ExecuteCSVMode(Frame):
|
||||
self.sequence_array = csv.read_csv_to_array(filename)
|
||||
except BaseException as e:
|
||||
ui_print("Error while opening file:", e)
|
||||
# ToDo: make error a popup
|
||||
messagebox.showerror("Error!", "Error while opening file: \n%s" % e)
|
||||
|
||||
csv.check_array(self.sequence_array)
|
||||
self.display_plot()
|
||||
|
||||
# ToDo: check for excessive values
|
||||
self.execute_button["state"] = "normal" # activate run button
|
||||
elif filename == '': # this happens when file selection window is closed without selecting a file
|
||||
ui_print("No file selected, could not load.")
|
||||
@@ -584,6 +619,7 @@ class ExecuteCSVMode(Frame):
|
||||
self.select_file_button["state"] = "disabled"
|
||||
self.execute_button["state"] = "disabled"
|
||||
self.stop_button["state"] = "normal"
|
||||
self.reinit_button["state"] = "disabled"
|
||||
|
||||
# g.threadLock = threading.Lock()
|
||||
# create separate thread to run sequence execution in:
|
||||
@@ -592,11 +628,18 @@ class ExecuteCSVMode(Frame):
|
||||
csv_thread.start() # start thread
|
||||
|
||||
def stop_run(self):
|
||||
g.running = False
|
||||
g.running = False # this will cause the csv loop to end
|
||||
# (de)activate buttons as needed:
|
||||
self.select_file_button["state"] = "normal"
|
||||
self.execute_button["state"] = "normal"
|
||||
self.stop_button["state"] = "disabled"
|
||||
self.reinit_button["state"] = "normal"
|
||||
|
||||
def display_plot(self): # ToDo: comments
|
||||
figure = csv.plot_field_sequence(self.sequence_array)
|
||||
plotCanvas = FigureCanvasTkAgg(figure, self.plotFrame)
|
||||
plotCanvas.draw()
|
||||
plotCanvas.get_tk_widget().grid(row=0, column=0, sticky="nesw")
|
||||
|
||||
|
||||
class StatusDisplay(Frame):
|
||||
@@ -645,11 +688,12 @@ class StatusDisplay(Frame):
|
||||
col = col + 1
|
||||
# rowCounter = rowCounter + self.rowNo # increase row counter to place future stuff below this
|
||||
|
||||
self.continuous_label_update(controller, 500) # initiate regular value updates (ms)
|
||||
self.update_labels()
|
||||
|
||||
def continuous_label_update(self, controller, interval): # update display values in regular intervals
|
||||
self.update_labels()
|
||||
if g.app is not None:
|
||||
if g.app is not None: # app ist still running
|
||||
# ToDo (optional): prevent call after program close
|
||||
controller.after(interval, lambda: self.continuous_label_update(controller, interval))
|
||||
|
||||
def update_labels(self):
|
||||
@@ -697,9 +741,9 @@ def ui_print(*content): # prints text to built in console
|
||||
output = ""
|
||||
for text in content:
|
||||
output = " ".join((output, str(text))) # append content
|
||||
if g.app is not None:
|
||||
if g.app is not None: # if main window is still open
|
||||
output = "".join(("\n", output)) # begin new line each time
|
||||
g.app.OutputConsole.console.insert(END, output) # print to console
|
||||
g.app.OutputConsole.console.see(END) # scroll to bottom
|
||||
else: # if window is not open, do normal print
|
||||
print(output)
|
||||
print(output)
|
||||
|
||||
+36
-3
@@ -70,7 +70,9 @@ class Axis:
|
||||
self.current = self.device.get_current(self.channel)
|
||||
self.current_setpoint = self.device.get_current_setpoint(self.channel)
|
||||
except (serial.serialutil.SerialException, IndexError):
|
||||
# ui_print("Connection Error with %s PSU on %s" % (self.name, self.port))
|
||||
if self.connected == "Connected":
|
||||
ui_print("Connection Error with %s PSU on %s" % (self.name, self.port))
|
||||
messagebox.showerror("PSU Error", "Connection Error with %s PSU on %s" % (self.name, self.port))
|
||||
self.connected = "Connection Error"
|
||||
self.output_active = "Unknown"
|
||||
self.remote_ctrl_active = "Unknown"
|
||||
@@ -94,6 +96,7 @@ class Axis:
|
||||
g.ARDUINO.digitalWrite(self.ardPin, "LOW")
|
||||
except Exception as e:
|
||||
ui_print("Error while powering down %s: %s" % (self.name, e))
|
||||
messagebox.showerror("PSU Error!", "Error while powering down %s: \n%s" % (self.name, e))
|
||||
|
||||
def set_signed_current(self, value): # sets current with correct polarity on this axis
|
||||
device = self.device
|
||||
@@ -167,6 +170,7 @@ class ArduinoCtrl(Arduino):
|
||||
axis.polarity_switched = "False"
|
||||
except Exception as e:
|
||||
ui_print("Error with Arduino:", e)
|
||||
messagebox.showerror("Error with Arduino!", "Connection Error with Arduino: \n%s" % e)
|
||||
for axis in g.AXES:
|
||||
axis.polarity_switched = "Unknown"
|
||||
self.connected = "Connection Error"
|
||||
@@ -280,7 +284,7 @@ def power_down_all(): # temporary, set all outputs to 0 but keep connections en
|
||||
def shut_down_all(): # shutdown at program end or on error, set outputs to 0 and disable connections
|
||||
# ToDo: remove checks if connected or make them only for printing
|
||||
ui_print("\nAttempting to safely shut down all devices. Check equipment to confirm.")
|
||||
message = "Tried to safely shut down all devices. Check equipment to confirm."
|
||||
message = "Tried to shut down all devices. Check equipment to confirm."
|
||||
if g.XY_DEVICE is not None:
|
||||
try:
|
||||
set_to_zero(g.XY_DEVICE)
|
||||
@@ -356,4 +360,33 @@ def set_current_vec(vector): # sets needed currents on each axis for given vect
|
||||
axis.set_signed_current(vector[i])
|
||||
except ValueError as e:
|
||||
ui_print(e)
|
||||
i += 1
|
||||
i += 1
|
||||
|
||||
|
||||
def devices_ok(xy_off=False, z_off=False, arduino_off=False):
|
||||
# ToDo: comments
|
||||
try:
|
||||
if not xy_off:
|
||||
if g.XY_DEVICE is not None:
|
||||
g.X_AXIS.update_status_info()
|
||||
if g.X_AXIS.connected != "Connected":
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
if not z_off:
|
||||
if g.Z_DEVICE is not None:
|
||||
g.Z_AXIS.update_status_info()
|
||||
if g.Z_AXIS.connected != "Connected":
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
if not arduino_off:
|
||||
g.ARDUINO.update_status_info()
|
||||
if g.ARDUINO.connected != "Connected":
|
||||
return False
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error!", "Error while checking devices: \n%s" % e)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
+66
-9
@@ -1,12 +1,17 @@
|
||||
import User_Interface as ui
|
||||
import cage_func as func
|
||||
import time
|
||||
import pandas
|
||||
from threading import *
|
||||
from tkinter import messagebox
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import User_Interface as ui
|
||||
import cage_func as func
|
||||
import globals as g
|
||||
|
||||
|
||||
class ExecCSVThread(Thread):
|
||||
# ToDo: handling for disconnected devices
|
||||
def __init__(self, threadID, array, parent, controller):
|
||||
Thread.__init__(self)
|
||||
|
||||
@@ -19,28 +24,36 @@ class ExecCSVThread(Thread):
|
||||
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
|
||||
execute_sequence(self.array, 0.1, self.controller) # run sequence
|
||||
execute_sequence(self.array, 0.1, self.parent, self.controller) # run sequence
|
||||
self.parent.running = False # sequence finished --> no longer running
|
||||
# reset buttons on UI:
|
||||
self.parent.select_file_button["state"] = "normal"
|
||||
self.parent.execute_button["state"] = "normal"
|
||||
self.parent.stop_button["state"] = "disabled"
|
||||
self.parent.reinit_button["state"] = "normal"
|
||||
|
||||
|
||||
def execute_sequence(array, delay, controller): # runs through array containing times and desired field vectors
|
||||
def execute_sequence(array, delay, parent, controller): # runs through array containing times and desired field vectors
|
||||
# array format: [time (s), xField (T), yField (T), zField (T)]
|
||||
# decimal commas
|
||||
# all times in seconds
|
||||
func.power_down_all() # sets outputs to 0 before starting
|
||||
t_zero = time.time() # set reference time for start of run
|
||||
|
||||
# Check if everything is properly connected:
|
||||
all_connected = func.devices_ok(parent.xy_override, parent.z_override, parent.arduino_override)
|
||||
# True or False depending on devices status, checks for some devices may be overridden by user
|
||||
|
||||
i = 0
|
||||
while i < len(array) and g.running: # while array is not finished and user has not cancelled
|
||||
while i < len(array) and g.running and all_connected:
|
||||
# while array is not finished, user has not cancelled and devices are connected
|
||||
|
||||
t = time.time() - t_zero # get relative time
|
||||
if t >= array[i, 0]: # time for this row has come
|
||||
field_vec = array[i, 1:4] # extract desired field vector
|
||||
ui.ui_print("%f s: t = %0.2f s, target field vector = "
|
||||
% (time.time()-t_zero, array[i, 0]), field_vec * 1e6, "\u03BCT")
|
||||
func.set_field_simple(field_vec) # send field vector to test stand # ToDo!: reset to set_field()
|
||||
% (time.time() - t_zero, array[i, 0]), field_vec * 1e6, "\u03BCT")
|
||||
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
|
||||
i = i + 1 # next row
|
||||
@@ -49,10 +62,17 @@ def execute_sequence(array, delay, controller): # runs through array containing
|
||||
pass
|
||||
else: # sleep to give other threads time to run
|
||||
time.sleep(delay)
|
||||
if g.running: # sequence ended without interruption
|
||||
|
||||
# check again if everything is connected before starting next loop run:
|
||||
all_connected = func.devices_ok(parent.xy_override, parent.z_override, parent.arduino_override)
|
||||
|
||||
if g.running and all_connected: # sequence ended without interruption
|
||||
ui.ui_print("Sequence executed, powering down channels.")
|
||||
else: # interrupted by user
|
||||
elif all_connected: # interrupted by user
|
||||
ui.ui_print("Sequence cancelled, powering down channels.")
|
||||
elif g.running: # interrupted by device error
|
||||
ui.ui_print("Error with at least one device, sequence aborted.")
|
||||
messagebox.showinfo("Device Error!", "Error with at least one device, sequence aborted.")
|
||||
func.power_down_all() # set currents and voltages to 0, set arduino pins to low
|
||||
|
||||
|
||||
@@ -63,3 +83,40 @@ def read_csv_to_array(filepath):
|
||||
file = pandas.read_csv(filepath, sep=';', decimal=',', header=0) # read csv file
|
||||
array = file.to_numpy() # convert csv to array
|
||||
return array
|
||||
|
||||
|
||||
def check_array(array):
|
||||
# ToDo: message formatting, pop up warning
|
||||
# ToDo: comments
|
||||
concerns = []
|
||||
for row in array:
|
||||
i = 1
|
||||
for axis in g.AXES:
|
||||
value = row[i]
|
||||
if value > axis.max_comp_field[1]:
|
||||
concerns.append(row)
|
||||
elif value < axis.max_comp_field[0]:
|
||||
concerns.append(row)
|
||||
i += 1
|
||||
ui.ui_print("Checked csv, found %i concerns." % len(concerns))
|
||||
if len(concerns) > 0:
|
||||
ui.ui_print(concerns)
|
||||
|
||||
|
||||
def plot_field_sequence(array): # ToDo: comments
|
||||
# ToDo: make pretty
|
||||
figure = plt.Figure(figsize=(8, 10), dpi=100)
|
||||
|
||||
# noinspection PyTypeChecker,SpellCheckingInspection
|
||||
axes = figure.subplots(3, sharex=True, sharey=True, gridspec_kw={'hspace': 0.4})
|
||||
|
||||
figure.suptitle("Magnetic Field Sequence")
|
||||
t = array[:, 0]
|
||||
|
||||
for i in [0, 1, 2]:
|
||||
data = array[:, i + 1] * 1e6
|
||||
plot = axes[i]
|
||||
plot.plot(t, data)
|
||||
plot.set_title(g.AXIS_NAMES[i])
|
||||
|
||||
return figure
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ running = False
|
||||
# ToDo: put this into a config file
|
||||
default_arrays = {
|
||||
"coil_const": np.array([[38.6, 38.45, 37.9], [50, 50, 50], [0, 0, 0]]) * 1e-6, # Coil constants [x,y,z] [T/A]
|
||||
"ambient_field": np.array([[30, 30, 30], [200, 200, 200], [0, 0, 0]]) * 1e-6, # background magnetic field [T]
|
||||
"ambient_field": np.array([[30, 30, 30], [200, 200, 200], [-200, -200, -200]]) * 1e-6, # background magnetic field [T]
|
||||
"resistance": np.array([[1.7, 1.7, 1.7], [5, 5, 5], [1, 1, 1]], dtype=float), # resistance of circuits [Ohm]
|
||||
"max_watts": np.array([[15, 15, 15], [50, 50, 50], [0, 0, 0]], dtype=float), # max. allowed power for circuits [W]
|
||||
"max_volts": np.array([[14, 14, 14], [16, 16, 16], [0, 0, 0]], dtype=float), # max. allowed voltage, limited to 16V by used diodes! [V]
|
||||
|
||||
@@ -26,6 +26,8 @@ try: # start normal operations
|
||||
print("\nOpening User Interface...")
|
||||
|
||||
g.app = HelmholtzGUI()
|
||||
g.app.state('zoomed') # open maximized
|
||||
g.app.StatusDisplay.continuous_label_update(g.app, 500) # initiate regular Status Display updates (ms)
|
||||
ui_print("Program Initialized")
|
||||
config.check_config(config.CONFIG_OBJECT) # check config for values exceeding limits
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
numpy==1.19.3 # bug in numpy 1.19.4, 1.19.3 used as workaround
|
||||
pyserial~=3.5
|
||||
future~=0.18.2
|
||||
pandas
|
||||
pandas~=1.1.5
|
||||
matplotlib~=3.3.2
|
||||
Reference in New Issue
Block a user