forked from zietzm/Helmholtz_Test_Bench
Implemented configuration window
This commit is contained in:
+164
-19
@@ -25,12 +25,12 @@ class HelmholtzGUI(Tk):
|
||||
mainArea.grid_rowconfigure(0, weight=1)
|
||||
mainArea.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.frames = {} # dictionary for storing all pages
|
||||
self.pages = {} # dictionary for storing all pages
|
||||
|
||||
for F in [ManualMode]:
|
||||
frame = F(mainArea, self)
|
||||
self.frames[F] = frame
|
||||
frame.grid(row=0, column=0, sticky="nsew")
|
||||
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)
|
||||
@@ -46,7 +46,8 @@ class HelmholtzGUI(Tk):
|
||||
self.show_frame(ManualMode)
|
||||
|
||||
def show_frame(self, key):
|
||||
frame = self.frames[key] # gets correct page from the dictionary
|
||||
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
|
||||
|
||||
|
||||
@@ -59,15 +60,18 @@ class TopMenu:
|
||||
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="Configuration...", 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):
|
||||
# ToDo: Display maximum values
|
||||
# ToDo: Add option to cancel ambient field
|
||||
# ToDo: Add buttons to safe and set to 0
|
||||
|
||||
def __init__(self, parent, controller):
|
||||
@@ -88,7 +92,6 @@ class ManualMode(Frame):
|
||||
# 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]}
|
||||
# "Raw Current": [self.input_raw_current, "A"]} ToDo (optional): make functions for this
|
||||
self.unit = StringVar()
|
||||
default_mode = list(self.modes.keys())[0]
|
||||
|
||||
@@ -143,7 +146,6 @@ class ManualMode(Frame):
|
||||
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_columnconfigure(2, weight=1, minsize=20)
|
||||
self.buttons_frame.grid(row=row_counter, column=0)
|
||||
|
||||
Label(self.buttons_frame, text="").grid(row=0, column=0) # add spacer
|
||||
@@ -151,25 +153,28 @@ class ManualMode(Frame):
|
||||
# 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)
|
||||
execute_button.grid(row=row_counter, column=0, padx=5)
|
||||
|
||||
# add button for reinitialization
|
||||
reinit_button = Button(self.buttons_frame, text="Reinitialize", command=func.setup_axes,
|
||||
pady=5, padx=5, font=BIG_BUTTON_FONT)
|
||||
reinit_button.grid(row=row_counter, column=1)
|
||||
reinit_button.grid(row=row_counter, column=1, padx=5)
|
||||
|
||||
row_counter = row_counter + 1
|
||||
# Add spacer to Frame below
|
||||
|
||||
Label(self, text="", pady=10).grid(row=row_counter, column=0)
|
||||
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 dropdown change
|
||||
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
|
||||
|
||||
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
|
||||
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)
|
||||
@@ -203,11 +208,16 @@ class ManualMode(Frame):
|
||||
function_to_call(vector) # call function
|
||||
# ToDo: update status display here
|
||||
|
||||
@staticmethod
|
||||
def execute_field(vector):
|
||||
def execute_field(self, vector):
|
||||
func.ui_print("field executing", vector)
|
||||
try:
|
||||
func.set_field_simple(vector * 1e-6) # ToDo: change to set_field
|
||||
comp = self.compensate.get()
|
||||
if comp == 0:
|
||||
func.set_field(vector * 1e-6)
|
||||
elif comp == 1:
|
||||
func.set_field_simple(vector * 1e-6)
|
||||
else:
|
||||
func.ui_print("Unexpected value encountered: compensate =", comp)
|
||||
except ValueError as e:
|
||||
func.ui_print(e)
|
||||
|
||||
@@ -220,6 +230,141 @@ class ManualMode(Frame):
|
||||
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
|
||||
|
||||
# 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 = ttk.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/A", "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.update_fields() # set current values from config file
|
||||
|
||||
# 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():
|
||||
for axis in range(3): # generate entry fields
|
||||
field = ttk.Entry(value_frame, textvariable=self.entries[key][0][axis], width=10)
|
||||
field.grid(row=row, column=axis+1, sticky=W, padx=2)
|
||||
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
|
||||
|
||||
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 Reconnect", 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):
|
||||
func.create_default_config(g.CONFIG_FILE) # overwrite config file with default
|
||||
func.setup_axes() # setup everything with the defaults ToDo: take out?
|
||||
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_config(g.AXIS_NAMES[i], self.entries[key][3]) # 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
|
||||
|
||||
def implement(self): # update config file with user inputs into entry fields and reinitialize
|
||||
# ToDo: Error handling with warning messages to user if wrong data format or high/low value is entered
|
||||
func.edit_config("PORTS", "xy_port", self.XY_port.get())
|
||||
func.edit_config("PORTS", "z_port", self.Z_port.get())
|
||||
|
||||
for key in self.entries.keys():
|
||||
for i in [0, 1, 2]:
|
||||
value = self.entries[key][0][i].get()
|
||||
factor = self.entries[key][4] # get unit conversion factor
|
||||
if factor not in [0, 1]: # prevent conversion of int variables to float representation
|
||||
value = value / factor # implement unit conversion
|
||||
print(key, value)
|
||||
func.edit_config(g.AXIS_NAMES[i], self.entries[key][3], value)
|
||||
|
||||
func.setup_axes()
|
||||
|
||||
|
||||
class StatusDisplay(Frame):
|
||||
|
||||
def __init__(self, parent, controller):
|
||||
@@ -269,7 +414,7 @@ class StatusDisplay(Frame):
|
||||
self.update_labels(controller)
|
||||
|
||||
def update_labels(self, controller):
|
||||
# ToDo: do this with a dictionary
|
||||
# ToDo (optional): do this with a dictionary
|
||||
g.ARDUINO.update_status_info()
|
||||
i = 0
|
||||
for axis in g.AXES:
|
||||
|
||||
+10
-9
@@ -5,7 +5,7 @@ import pandas
|
||||
import time
|
||||
import numpy as np
|
||||
import serial
|
||||
import traceback # ToDo: remove
|
||||
import traceback
|
||||
from tkinter import *
|
||||
from configparser import ConfigParser
|
||||
|
||||
@@ -135,9 +135,11 @@ class Axis:
|
||||
|
||||
class ArduinoCtrl(Arduino):
|
||||
|
||||
def __init__(self, pins):
|
||||
def __init__(self):
|
||||
self.connected = "Unknown"
|
||||
self.pins = pins
|
||||
self.pins = [0, 0, 0]
|
||||
for i in range(3):
|
||||
self.pins[i] = int(read_config(g.AXIS_NAMES[i], "relay_pin"))
|
||||
ui_print("\nConnecting to Arduino...")
|
||||
try:
|
||||
Arduino.__init__(self) # search for connected arduino and connect
|
||||
@@ -155,7 +157,7 @@ class ArduinoCtrl(Arduino):
|
||||
if self.connected == "Connected":
|
||||
try:
|
||||
for axis in g.AXES:
|
||||
if g.ARDUINO.digitalRead(axis.ardPin): # ToDo: Test if this actually works
|
||||
if g.ARDUINO.digitalRead(axis.ardPin):
|
||||
axis.polarity_switched = "True"
|
||||
else:
|
||||
axis.polarity_switched = "False"
|
||||
@@ -209,8 +211,8 @@ def create_default_config(file): # create config file from default values (stor
|
||||
i += 1
|
||||
|
||||
config.add_section("PORTS")
|
||||
for key in g.defaults.keys():
|
||||
config.set("PORTS", key, str(g.defaults[key]))
|
||||
for key in g.default_ports.keys():
|
||||
config.set("PORTS", key, str(g.default_ports[key]))
|
||||
|
||||
with open(file, 'w') as conf:
|
||||
config.write(conf)
|
||||
@@ -230,7 +232,6 @@ def ui_print(*content): # prints text to built in console
|
||||
|
||||
def setup_axes(): # creates device objects for all PSUs and sets their values
|
||||
# Connect to Arduino:
|
||||
arduino_pins = read_config("PORTS", "relay_pins")
|
||||
try:
|
||||
if g.ARDUINO is not None:
|
||||
# ui_print("\nClosing arduino link")
|
||||
@@ -244,7 +245,7 @@ def setup_axes(): # creates device objects for all PSUs and sets their values
|
||||
pass
|
||||
# when no Arduino is connected but g.ARDUINO has been initialized then there is nothing to close
|
||||
# this throws exception, which can be ignored
|
||||
g.ARDUINO = ArduinoCtrl(arduino_pins)
|
||||
g.ARDUINO = ArduinoCtrl()
|
||||
except Exception as e:
|
||||
ui_print("Arduino setup failed:", e)
|
||||
ui_print(traceback.print_exc())
|
||||
@@ -270,7 +271,7 @@ def setup_axes(): # creates device objects for all PSUs and sets their values
|
||||
g.Y_AXIS = Axis(1, None, 1, g.ARDUINO.pins[1])
|
||||
ui_print("XY Device not connected or incorrect port set.")
|
||||
|
||||
ui_print("Connecting to Z Device on %s..." % g.XY_PORT)
|
||||
ui_print("Connecting to Z Device on %s..." % g.Z_PORT)
|
||||
try:
|
||||
g.Z_DEVICE = PS2000B.PS2000B(g.Z_PORT)
|
||||
ui_print("Connection established.")
|
||||
|
||||
+3
-4
@@ -19,7 +19,6 @@ global CONFIG_FILE
|
||||
|
||||
global XY_PORT
|
||||
global Z_PORT
|
||||
global RELAY_PINS
|
||||
|
||||
global PORTS
|
||||
|
||||
@@ -27,12 +26,12 @@ global PORTS
|
||||
default_arrays = {
|
||||
"coil_const": np.array([38.6, 38.45, 37.9]) * 1e-6, # Coil constants [x,y,z] [T/A]
|
||||
"ambient_field": np.array([30, 30, 30]) * 1e-6, # ambient magnetic field in measurement area [T]
|
||||
"resistance": np.array([1.7, 1.7, 1.7]), # resistance of [x,y,z] circuits [Ohm]
|
||||
"resistance": np.array([1.7, 1.7, 1.7], dtype=float), # resistance of [x,y,z] circuits [Ohm]
|
||||
"max_watts": np.array([15, 15, 15], dtype=float), # max. allowed power for [x,y,z] circuits [W]
|
||||
"max_volts": np.array([16, 16, 16], dtype=float), # max. allowed voltage, limited to 16V by used diodes! [V]
|
||||
"relay_pin": [15, 16, 17] # pin on the arduino for reversing [x,y,z] polarity
|
||||
}
|
||||
defaults = {
|
||||
default_ports = {
|
||||
"xy_port": "COM1", # Serial port where PSU for X- and Y-Axes is connected
|
||||
"z_port": "COM2", # Serial port where PSU for Z-Axis is connected
|
||||
"relay_pins": [15, 16, 17] # pin on the arduino for reversing [x,y,z] polarity
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user