forked from zietzm/Helmholtz_Test_Bench
8d1870956f
file is now only written when explicitly wanted, e.g. on button press. global config stored instead as config object
586 lines
27 KiB
Python
586 lines
27 KiB
Python
from tkinter import *
|
|
from tkinter import ttk
|
|
from tkinter import messagebox
|
|
from tkinter import filedialog
|
|
import globals as g
|
|
import cage_func as func
|
|
import numpy as np
|
|
import os
|
|
from os.path import exists
|
|
|
|
NORM_FONT = ()
|
|
HEADER_FONT = ("Arial", 13, "bold")
|
|
SUB_HEADER_FONT = ("Arial", 9, "bold")
|
|
BIG_BUTTON_FONT = ("Arial", 11, "bold")
|
|
SMALL_BUTTON_FONT = ("Arial", 9)
|
|
|
|
|
|
class HelmholtzGUI(Tk):
|
|
|
|
def __init__(self):
|
|
Tk.__init__(self)
|
|
|
|
Tk.wm_title(self, "Helmholtz Cage Control")
|
|
Tk.wm_iconbitmap(self, "Helmholtz.ico")
|
|
|
|
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.grid_rowconfigure(0, weight=1)
|
|
mainArea.grid_columnconfigure(0, weight=1)
|
|
|
|
self.pages = {} # dictionary for storing all pages
|
|
|
|
for P in [ManualMode, Configuration]:
|
|
page = P(mainArea, self)
|
|
self.pages[P] = page
|
|
page.grid(row=0, column=0, sticky="nsew")
|
|
|
|
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)
|
|
self.StatusDisplay.grid(row=0, column=0, sticky="nesw")
|
|
|
|
self.OutputConsole = OutputConsole(status_frame)
|
|
self.OutputConsole.grid(row=0, column=1, sticky="nesw")
|
|
|
|
self.show_frame(ManualMode)
|
|
|
|
def show_frame(self, key):
|
|
frame = self.pages[key] # gets correct page from the dictionary
|
|
frame.page_switch() # update displays in this page with window-specific update function
|
|
frame.tkraise() # brings this frame to the front
|
|
|
|
|
|
class TopMenu:
|
|
|
|
def __init__(self, window):
|
|
menu = Menu(window)
|
|
window.config(menu=menu)
|
|
|
|
ModeSelector = Menu(menu)
|
|
menu.add_cascade(label="Mode", menu=ModeSelector)
|
|
ModeSelector.add_command(label="Static Manual Input", command=lambda: self.manual_mode(window))
|
|
ModeSelector.add_command(label="Settings...", command=lambda: self.configuration(window))
|
|
|
|
@staticmethod
|
|
def manual_mode(window):
|
|
window.show_frame(ManualMode)
|
|
|
|
@staticmethod
|
|
def configuration(window):
|
|
window.show_frame(Configuration)
|
|
|
|
|
|
class ManualMode(Frame):
|
|
|
|
def __init__(self, parent, controller):
|
|
Frame.__init__(self, parent)
|
|
|
|
self.grid_rowconfigure(ALL, weight=1)
|
|
self.grid_columnconfigure(ALL, weight=1)
|
|
|
|
row_counter = 0
|
|
|
|
header = Label(self, text="Manual Input Mode", font=HEADER_FONT, pady=3)
|
|
header.grid(row=row_counter, column=0)
|
|
|
|
row_counter += 1
|
|
|
|
# Setup Dropdown Menu for input mode
|
|
dropdown_frame = Frame(self)
|
|
dropdown_frame.grid_rowconfigure(ALL, weight=1)
|
|
dropdown_frame.grid_columnconfigure(ALL, weight=1)
|
|
dropdown_frame.grid(row=row_counter, column=0)
|
|
self.input_mode = StringVar()
|
|
# make dictionary with information on all modes.
|
|
# content: [function to call on button press, unit text to be displayed]
|
|
self.modes = {"Magnetic Field": [self.execute_field, "\u03BCT", self.update_max_fields],
|
|
"Current": [self.execute_current, "A", self.update_max_currents]}
|
|
self.unit = StringVar()
|
|
default_mode = list(self.modes.keys())[0]
|
|
|
|
input_mode_selector = ttk.OptionMenu(dropdown_frame, self.input_mode, default_mode, *self.modes.keys())
|
|
|
|
input_mode_selector.grid(row=0, column=1, sticky=W) # place dropdown on the grid
|
|
dropdown_frame.grid_columnconfigure(1, minsize=115) # set size of column with dropdown to keep it from moving
|
|
|
|
selector_label = Label(dropdown_frame, text="Select Input Mode:", padx=10, pady=10)
|
|
selector_label.grid(row=0, column=0)
|
|
|
|
row_counter = row_counter + 1
|
|
|
|
# Setup Entry fields
|
|
self.entries_frame = Frame(self)
|
|
self.entries_frame.grid_rowconfigure(ALL, weight=1)
|
|
self.entries_frame.grid_columnconfigure(ALL, weight=1)
|
|
self.entries_frame.grid_columnconfigure(2, weight=1, minsize=20)
|
|
self.entries_frame.grid_columnconfigure(3, weight=1, minsize=110)
|
|
self.entries_frame.grid(row=row_counter, column=0)
|
|
|
|
entry_texts = ["X-Axis:", "Y-Axis:", "Z-Axis:"]
|
|
self.entry_vars = [StringVar() for _ in range(3)]
|
|
self.max_value_vars = [StringVar() for _ in range(3)]
|
|
row = 0
|
|
for text in entry_texts:
|
|
field = ttk.Entry(self.entries_frame, textvariable=self.entry_vars[row])
|
|
self.entry_vars[row].set(0)
|
|
field.grid(row=row, column=1, sticky=W)
|
|
axis_label = Label(self.entries_frame, text=text, padx=5, pady=10)
|
|
axis_label.grid(row=row, column=0, sticky=W)
|
|
unit_label = Label(self.entries_frame, textvariable=self.unit)
|
|
unit_label.grid(row=row, column=2, sticky=W)
|
|
max_value_label = Label(self.entries_frame, textvariable=self.max_value_vars[row])
|
|
max_value_label.grid(row=row, column=3, sticky=W)
|
|
row = row + 1
|
|
|
|
row_counter += 1
|
|
|
|
# setup checkbox for compensating ambient field
|
|
checkbox_frame = Frame(self, padx=20)
|
|
checkbox_frame.grid(row=row_counter, column=0, sticky=W)
|
|
|
|
self.compensate = IntVar(value=1)
|
|
self.compensate_checkbox = Checkbutton(checkbox_frame, text="Compensate ambient field",
|
|
variable=self.compensate, onvalue=1, offvalue=0)
|
|
self.compensate_checkbox.pack(side="left")
|
|
|
|
row_counter += 1
|
|
|
|
# Setup buttons
|
|
self.buttons_frame = Frame(self)
|
|
self.buttons_frame.grid_rowconfigure(ALL, weight=1)
|
|
self.buttons_frame.grid_columnconfigure(ALL, weight=1)
|
|
self.buttons_frame.grid(row=row_counter, column=0)
|
|
|
|
Label(self.buttons_frame, text="").grid(row=0, column=0) # add spacer
|
|
|
|
# add button for executing the current entries
|
|
execute_button = Button(self.buttons_frame, text="Execute!", command=self.execute,
|
|
pady=5, padx=5, font=BIG_BUTTON_FONT)
|
|
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,
|
|
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,
|
|
pady=5, padx=5, font=BIG_BUTTON_FONT)
|
|
reinit_button.grid(row=row_counter, column=2, padx=5)
|
|
|
|
row_counter = row_counter + 1
|
|
# Add spacer to Frame below
|
|
|
|
Label(self, text="", pady=10).grid(row=row_counter, column=0) # add spacer
|
|
|
|
self.input_mode.trace_add('write', self.change_mode_callback) # call mode change function on dropdown change
|
|
self.input_mode.set(default_mode) # call up default mode at the start
|
|
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
|
|
|
|
# noinspection PyUnusedLocal
|
|
def change_mode_callback(self, var, index, mode): # not sure what the parameters are for, but they are necessary
|
|
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
|
|
|
|
def update_max_fields(self): # update labels with maximum allowable field values
|
|
self.compensate_checkbox.config(state=NORMAL)
|
|
i = 0
|
|
for val in self.max_value_vars:
|
|
comp = self.compensate.get()
|
|
if comp == 0:
|
|
field = g.AXES[i].max_field * 1e6
|
|
elif comp == 1:
|
|
field = g.AXES[i].max_comp_field * 1e6
|
|
else:
|
|
field = [0, 0]
|
|
func.ui_print("Unexpected value encountered: compensate =", comp)
|
|
val.set("(%0.1f to %0.1f \u03BCT)" % (field[0], field[1]))
|
|
i += 1
|
|
|
|
def update_max_currents(self): # update labels with maximum allowable current values
|
|
self.compensate_checkbox.config(state=DISABLED)
|
|
i = 0
|
|
for val in self.max_value_vars:
|
|
val.set("(%0.2f to %0.2f A)" % (-g.AXES[i].max_amps, g.AXES[i].max_amps))
|
|
i += 1
|
|
|
|
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)
|
|
i = 0
|
|
for var in self.entry_vars:
|
|
vector[i] = float(var.get())
|
|
i = i + 1
|
|
function_to_call(vector) # call function
|
|
|
|
def execute_field(self, vector):
|
|
func.ui_print("field executing", vector)
|
|
comp = self.compensate.get()
|
|
if comp == 1:
|
|
func.set_field(vector * 1e-6)
|
|
elif comp == 0:
|
|
func.set_field_simple(vector * 1e-6)
|
|
else:
|
|
func.ui_print("Unexpected value encountered: compensate =", comp)
|
|
|
|
@staticmethod
|
|
def execute_current(vector):
|
|
func.ui_print("current executing:", vector)
|
|
try:
|
|
func.set_current_vec(vector)
|
|
except ValueError as e:
|
|
func.ui_print(e)
|
|
|
|
|
|
class Configuration(Frame):
|
|
# generate configuration window to set program constants
|
|
|
|
def __init__(self, parent, controller):
|
|
Frame.__init__(self, parent)
|
|
|
|
self.grid_rowconfigure(ALL, weight=1)
|
|
self.grid_columnconfigure(ALL, weight=1)
|
|
|
|
row_counter = 0
|
|
|
|
header = Label(self, text="Configuration Window", font=HEADER_FONT, pady=3)
|
|
header.grid(row=row_counter, column=0, padx=100, sticky=W)
|
|
|
|
row_counter += 1
|
|
|
|
# Setup buttons to select config file
|
|
# 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)
|
|
|
|
# Create and place buttons
|
|
# ToDo: comments
|
|
load_file_button = Button(self.file_select_frame, text="Load config file...", command=self.load_config,
|
|
pady=5, padx=5, font=SMALL_BUTTON_FONT)
|
|
load_file_button.grid(row=0, column=0, padx=5)
|
|
save_button = Button(self.file_select_frame, text="Save current config", command=self.save_config,
|
|
pady=5, padx=5, font=SMALL_BUTTON_FONT)
|
|
save_button.grid(row=0, column=1, padx=5)
|
|
save_as_button = Button(self.file_select_frame, text="Save current config as...", command=self.save_config_as,
|
|
pady=5, padx=5, font=SMALL_BUTTON_FONT)
|
|
save_as_button.grid(row=0, column=2, padx=5)
|
|
|
|
row_counter += 1
|
|
|
|
# Serial port settings frame:
|
|
port_frame = Frame(self)
|
|
port_frame.grid_rowconfigure(ALL, weight=1)
|
|
port_frame.grid_columnconfigure(ALL, weight=1)
|
|
port_frame.grid(row=row_counter, column=0, sticky=W)
|
|
|
|
entry_texts = ["XY PSU Serial Port:", "Z PSU Serial Port:"]
|
|
self.XY_port = StringVar(value=g.XY_PORT)
|
|
self.Z_port = StringVar(value=g.Z_PORT)
|
|
port_vars = [self.XY_port, self.Z_port]
|
|
row = 0
|
|
for text in entry_texts:
|
|
field = Entry(port_frame, textvariable=port_vars[row])
|
|
field.grid(row=row, column=1, sticky=W)
|
|
axis_label = Label(port_frame, text=text, padx=5, pady=10)
|
|
axis_label.grid(row=row, column=0, sticky=W)
|
|
unit_label = Label(port_frame, text="e.g. COM10")
|
|
unit_label.grid(row=row, column=2, sticky=W)
|
|
row += 1
|
|
|
|
row_counter += 1
|
|
|
|
Label(self, text="", pady=0).grid(row=row_counter, column=0) # add spacer
|
|
row_counter += 1
|
|
|
|
value_frame = Frame(self)
|
|
value_frame.grid_rowconfigure(ALL, weight=1)
|
|
value_frame.grid_columnconfigure(ALL, weight=1)
|
|
value_frame.grid(row=row_counter, column=0)
|
|
|
|
# Setup dictionary to generate entry table from
|
|
# {Key: [[x-value,y-value,z-value], unit, description, config file key, unit conversion factor]}
|
|
self.entries = {
|
|
"Coil Constants:": [[DoubleVar() for _ in range(3)], "\u03BCT/A", "", "coil_const", 1e6],
|
|
"Ambient Field:": [[DoubleVar() for _ in range(3)], "\u03BCT",
|
|
"Field to be compensated", "ambient_field", 1e6],
|
|
"Resistances:": [[DoubleVar() for _ in range(3)], "\u03A9",
|
|
"Resistance of coils + equipment", "resistance", 1],
|
|
"Max. Power:": [[DoubleVar() for _ in range(3)], "W", "Max. allowed power", "max_watts", 1],
|
|
"Max. Voltage:": [[DoubleVar() for _ in range(3)], "V",
|
|
"Max. allowed voltage, must not exceed 16V!", "max_volts", 1],
|
|
"Arduino Pins:": [[IntVar() for _ in range(3)], "-", "Should be 15, 16, 17", "relay_pin", 1]
|
|
}
|
|
|
|
self.fields = {}
|
|
|
|
# Fill in header (axis names):
|
|
col = 1
|
|
for text in ["X-Axis", "Y-Axis", "Z-Axis"]:
|
|
label = Label(value_frame, text=text, font=SUB_HEADER_FONT)
|
|
label.grid(row=0, column=col, sticky="ew")
|
|
col += 1
|
|
# generate table with entries, unit labels and descriptions:
|
|
row = 1
|
|
for key in self.entries.keys():
|
|
self.fields[key] = []
|
|
for axis in range(3): # generate entry fields
|
|
field = Entry(value_frame, textvariable=self.entries[key][0][axis], width=10)
|
|
field.grid(row=row, column=axis + 1, sticky=W, padx=2)
|
|
self.fields[key].append(field) # safe access to field for use elsewhere
|
|
axis_label = Label(value_frame, text=key, padx=5, pady=5)
|
|
axis_label.grid(row=row, column=0, sticky=W)
|
|
unit_label = Label(value_frame, text=self.entries[key][1])
|
|
unit_label.grid(row=row, column=4, sticky=W)
|
|
description_label = Label(value_frame, text=self.entries[key][2])
|
|
description_label.grid(row=row, column=5, sticky=W)
|
|
row = row + 1
|
|
|
|
row_counter += 1
|
|
|
|
self.update_fields() # set current values from config file
|
|
|
|
Label(self, text="", pady=10).grid(row=row_counter, column=0) # add spacer
|
|
row_counter += 1
|
|
|
|
# Setup buttons
|
|
# Setup frame to house buttons:
|
|
self.buttons_frame = Frame(self)
|
|
self.buttons_frame.grid_rowconfigure(ALL, weight=1)
|
|
self.buttons_frame.grid_columnconfigure(ALL, weight=1)
|
|
self.buttons_frame.grid(row=row_counter, column=0, sticky=W, padx=20)
|
|
|
|
# Create and place buttons
|
|
implement_button = Button(self.buttons_frame, text="Update and Reinitialize", command=self.implement,
|
|
pady=5, padx=5, font=BIG_BUTTON_FONT)
|
|
implement_button.grid(row=0, column=0, padx=5)
|
|
restore_button = Button(self.buttons_frame, text="Restore Defaults", command=self.restore_defaults,
|
|
pady=5, padx=5, font=BIG_BUTTON_FONT)
|
|
restore_button.grid(row=0, column=1, padx=5)
|
|
|
|
row_counter += 1
|
|
Label(self, text="", pady=10).grid(row=row_counter, column=0) # add spacer
|
|
|
|
def page_switch(self): # function that is called when switching to this window
|
|
self.update_fields()
|
|
|
|
def restore_defaults(self): # restore all default settings
|
|
func.reset_config_to_default(g.CONFIG_FILE) # overwrite config file with default
|
|
func.setup_all() # setup everything with the defaults
|
|
self.update_fields() # update fields in config window
|
|
|
|
def update_fields(self):
|
|
# set current values for all entry variables from config file
|
|
self.XY_port.set(g.XY_PORT)
|
|
self.Z_port.set(g.Z_PORT)
|
|
|
|
for key in self.entries.keys():
|
|
for i in [0, 1, 2]:
|
|
value = func.read_from_config(g.AXIS_NAMES[i], self.entries[key][3],
|
|
g.CONFIG_OBJECT) # get value from config file
|
|
self.entries[key][0][i].set(value) # set initial value on variable
|
|
type_value = self.entries[key][0][i].get() # get value with correct data type
|
|
factor = self.entries[key][4] # get unit conversion factor
|
|
self.entries[key][0][i].set(round(type_value * factor, 3)) # set value with correct unit conversion
|
|
|
|
# check if values are within safe limits:
|
|
value_check = func.value_in_limits(g.AXIS_NAMES[i], self.entries[key][3], value)
|
|
if value_check == 'OK': # value is acceptable
|
|
self.fields[key][i].config(background="White") # set colour of this entry to white
|
|
else: # value exceeds limits
|
|
self.fields[key][i].config(background="Red") # set colour of this entry to red to show problem
|
|
|
|
def write_values(self): # update config file with user inputs into entry fields and reinitialize
|
|
|
|
# set serial ports for PSUs:
|
|
func.edit_config("PORTS", "xy_port", self.XY_port.get())
|
|
func.edit_config("PORTS", "z_port", self.Z_port.get())
|
|
|
|
# set numeric values for all axes
|
|
for key in self.entries.keys(): # go through rows of entry table
|
|
for i in [0, 1, 2]: # go through columns of entry table (axes)
|
|
try:
|
|
value = self.entries[key][0][i].get() # get value from field
|
|
except TclError as e: # wrong format entered, e.g. text in number fields
|
|
func.ui_print("Invalid entry for %s %s %s" % (g.AXIS_NAMES[i], key, e))
|
|
|
|
else: # format is ok
|
|
factor = self.entries[key][4] # get unit conversion factor
|
|
if factor not in [0, 1]: # prevent div/0 and conversion of int variables to float
|
|
value = value / factor # do unit conversion
|
|
|
|
# Check if value is within safe limits
|
|
config_key = self.entries[key][3] # handle by which value is indexed in config file
|
|
value_ok = func.value_in_limits(g.AXIS_NAMES[i], config_key, value)
|
|
unit = self.entries[key][1] # get unit string for error messages
|
|
axis = g.AXIS_NAMES[i] # get axis name for error messages
|
|
|
|
if value_ok == 'OK':
|
|
func.edit_config(g.AXIS_NAMES[i], config_key, value) # write new value to config file
|
|
else: # value is not within limits
|
|
if value_ok == 'HIGH':
|
|
max_value = g.default_arrays[config_key][1][i] # get max value
|
|
message = "Attempted to set too high value for {s} {k}\n" \
|
|
"{v} {unit}, max. {mv} {unit} allowed.\n" \
|
|
"Excessive values may damage equipment!\n" \
|
|
"Do you really want to use this value?" \
|
|
.format(s=axis, k=key, v=value * factor, mv=round(max_value * factor, 1), unit=unit)
|
|
elif value_ok == 'LOW':
|
|
min_value = g.default_arrays[config_key][2][i] # get min value
|
|
message = "Attempted to set too low value for {s} {k}\n" \
|
|
"{v} {unit}, min. {mv} {unit} allowed.\n" \
|
|
"Excessive values may damage equipment!\n" \
|
|
"Do you really want to use this value?" \
|
|
.format(s=axis, k=key, v=value * factor, mv=round(min_value * factor, 1), unit=unit)
|
|
else:
|
|
message = "Unknown case, this should not happen."
|
|
|
|
# display pop-up message to ask user if he really wants the value
|
|
answer = messagebox.askquestion("Value out of Bounds", message)
|
|
# becomes 'yes' or 'no' depending on user choice
|
|
if answer == 'yes': # user really wants the value
|
|
# call function to write new value to config file with override=True
|
|
func.edit_config(g.AXIS_NAMES[i], config_key, value, True)
|
|
# if user chooses 'no' nothing happens, old value is kept
|
|
|
|
def implement(self):
|
|
self.write_values()
|
|
func.setup_all() # reinitialize devices and program with new values
|
|
self.update_fields() # update entry fields to show new values
|
|
|
|
def load_config(self): # ToDo: comments
|
|
directory = os.path.dirname(os.path.abspath(g.CONFIG_FILE))
|
|
filename = filedialog.askopenfilename(initialdir=directory, title="Select Config File",
|
|
filetypes=(("Config File", "*.ini*"), ("All Files", "*.*")))
|
|
if exists(filename):
|
|
g.CONFIG_FILE = filename
|
|
g.CONFIG_OBJECT = func.get_config_from_file(filename)
|
|
func.check_config(g.CONFIG_OBJECT)
|
|
func.setup_all()
|
|
self.update_fields()
|
|
elif filename == '':
|
|
func.ui_print("No file selected, could not load config.")
|
|
else:
|
|
func.ui_print("Selected file", filename, "does not seem to exist, could not load config.")
|
|
|
|
def save_config_as(self): # ToDo: comments
|
|
directory = os.path.dirname(os.path.abspath(g.CONFIG_FILE))
|
|
filename = filedialog.asksaveasfilename(initialdir=directory, title="Select Config File",
|
|
filetypes=([("Config File", "*.ini*")]),
|
|
defaultextension=[("Config File", "*.ini*")])
|
|
if exists(filename):
|
|
g.CONFIG_FILE = filename
|
|
self.write_values()
|
|
func.write_config_to_file(g.CONFIG_OBJECT)
|
|
self.update_fields()
|
|
elif filename == '':
|
|
func.ui_print("No file selected, could not save config.")
|
|
else:
|
|
func.ui_print("Selected file", filename, "does not seem to exist, could not save config.")
|
|
|
|
def save_config(self): # ToDo: comments
|
|
self.write_values()
|
|
func.write_config_to_file(g.CONFIG_OBJECT)
|
|
self.update_fields()
|
|
|
|
|
|
class StatusDisplay(Frame):
|
|
|
|
def __init__(self, parent, controller):
|
|
Frame.__init__(self, parent, relief=SUNKEN, bd=1)
|
|
|
|
self.grid_rowconfigure(ALL, weight=1)
|
|
self.grid_columnconfigure(ALL, weight=1)
|
|
|
|
rowCounter = 0 # keep track of which row we are at in the grid layout
|
|
x_pad = 10 # centrally set padding
|
|
|
|
col = 0
|
|
for header in ["", "X-Axis", "Y-Axis", "Z-Axis"]: # create Column headers
|
|
headLabel = Label(self, text=header, font=SUB_HEADER_FONT, borderwidth=1,
|
|
relief="flat", anchor="w", padx=x_pad)
|
|
headLabel.grid(row=rowCounter, column=col, sticky="ew")
|
|
col = col + 1 # move to next column
|
|
rowCounter = rowCounter + 1 # increase row counter to place future stuff below header
|
|
|
|
# define content of row entries
|
|
TextLabels = ["PSU Serial Port:", "PSU Channel:", "PSU Status:", "Arduino Status:", "", "Output:",
|
|
"Remote Control:",
|
|
"Voltage Setpoint:", "Actual Voltage:", "Current Setpoint:", "Actual Current:", "",
|
|
"Target Field:", "Trgt. Field Raw:", "Target Current:", "Inverted:"]
|
|
self.rowNo = len(TextLabels) # get number of label rows
|
|
|
|
self.columnNo = 4 # number of label columns
|
|
# prepare list of lists to contain all labels for row entries in all columns:
|
|
self.Labels = [[] for _ in range(self.columnNo)]
|
|
|
|
self.label_dict = {}
|
|
for name in TextLabels:
|
|
self.label_dict[name] = [StringVar() for _ in range(self.columnNo - 1)]
|
|
# add labels for row titles
|
|
self.Labels[0].append(Label(self, text=name, borderwidth=1, relief="flat", anchor="w", padx=x_pad))
|
|
for col in range(self.columnNo - 1): # add labels vor values
|
|
self.Labels[col + 1].append(Label(self, textvariable=self.label_dict[name][col],
|
|
borderwidth=1, relief="flat", anchor="w", padx=x_pad))
|
|
|
|
col = 0
|
|
for LabelCol in self.Labels: # place row entries in grid layout for all columns
|
|
for row in range(self.rowNo): # place row entries
|
|
LabelCol[row].grid(row=row + rowCounter, column=col, sticky="nsew")
|
|
col = col + 1
|
|
# rowCounter = rowCounter + self.rowNo # increase row counter to place future stuff below this
|
|
|
|
self.update_labels(controller)
|
|
|
|
def update_labels(self, controller):
|
|
g.ARDUINO.update_status_info()
|
|
i = 0
|
|
for axis in g.AXES:
|
|
if axis.device is not None:
|
|
axis.update_status_info()
|
|
self.label_dict["PSU Serial Port:"][i].set(g.PORTS[i])
|
|
self.label_dict["PSU Channel:"][i].set(axis.channel)
|
|
self.label_dict["PSU Status:"][i].set(axis.connected)
|
|
self.label_dict["Arduino Status:"][i].set(g.ARDUINO.connected) # ToDo (optional): make this multicolumn
|
|
self.label_dict["Output:"][i].set(axis.output_active)
|
|
self.label_dict["Remote Control:"][i].set(axis.remote_ctrl_active)
|
|
self.label_dict["Voltage Setpoint:"][i].set("%0.3f V" % axis.voltage_setpoint)
|
|
self.label_dict["Actual Voltage:"][i].set("%0.3f V" % axis.voltage)
|
|
self.label_dict["Current Setpoint:"][i].set("%0.3f A" % axis.current_setpoint)
|
|
self.label_dict["Actual Current:"][i].set("%0.3f A" % axis.current)
|
|
self.label_dict["Target Field:"][i].set("%0.3f \u03BCT" % (axis.target_field * 1e6))
|
|
self.label_dict["Trgt. Field Raw:"][i].set("%0.3f \u03BCT" % (axis.target_field_comp * 1e6))
|
|
self.label_dict["Target Current:"][i].set("%0.3f A" % axis.target_current)
|
|
self.label_dict["Inverted:"][i].set(axis.polarity_switched)
|
|
i = i + 1
|
|
controller.after(500, lambda: self.update_labels(controller))
|
|
|
|
|
|
class OutputConsole(Frame): # console to print stuff in, similar to standard python output
|
|
|
|
def __init__(self, parent):
|
|
Frame.__init__(self, parent, relief=SUNKEN, bd=1)
|
|
|
|
self.grid_rowconfigure(ALL, weight=1)
|
|
self.grid_columnconfigure(0, weight=1, minsize=60)
|
|
|
|
scrollbar = Scrollbar(self)
|
|
self.console = Text(self)
|
|
self.console.bind("<Key>", lambda e: "break") # prevent user input into the console
|
|
|
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
self.console.grid(row=0, column=0, sticky="nesw")
|
|
scrollbar.config(command=self.console.yview)
|
|
self.console.config(yscrollcommand=scrollbar.set)
|