Implemented configuration window

This commit is contained in:
Martin Zietz
2021-02-02 13:11:27 +01:00
parent 4dc296a6f0
commit 27c804904b
3 changed files with 177 additions and 32 deletions
+164 -19
View File
@@ -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
View File
@@ -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
View File
@@ -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
}