forked from zietzm/Helmholtz_Test_Bench
reworked config handling
file is now only written when explicitly wanted, e.g. on button press. global config stored instead as config object
This commit is contained in:
@@ -101,3 +101,4 @@ ENV/
|
||||
.idea/Python-PS2000B.iml
|
||||
.idea/misc.xml
|
||||
config.ini
|
||||
*.ini
|
||||
|
||||
+79
-11
@@ -1,14 +1,18 @@
|
||||
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):
|
||||
@@ -74,7 +78,6 @@ class TopMenu:
|
||||
|
||||
|
||||
class ManualMode(Frame):
|
||||
# ToDo: Add buttons to safe and set to 0
|
||||
|
||||
def __init__(self, parent, controller):
|
||||
Frame.__init__(self, parent)
|
||||
@@ -162,10 +165,15 @@ class ManualMode(Frame):
|
||||
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=1, padx=5)
|
||||
reinit_button.grid(row=row_counter, column=2, padx=5)
|
||||
|
||||
row_counter = row_counter + 1
|
||||
# Add spacer to Frame below
|
||||
@@ -250,6 +258,27 @@ class Configuration(Frame):
|
||||
|
||||
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)
|
||||
@@ -308,7 +337,7 @@ class Configuration(Frame):
|
||||
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)
|
||||
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)
|
||||
@@ -347,7 +376,7 @@ class Configuration(Frame):
|
||||
self.update_fields()
|
||||
|
||||
def restore_defaults(self): # restore all default settings
|
||||
func.create_default_config(g.CONFIG_FILE) # overwrite config file with default
|
||||
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
|
||||
|
||||
@@ -358,7 +387,8 @@ class Configuration(Frame):
|
||||
|
||||
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
|
||||
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
|
||||
@@ -371,7 +401,7 @@ class Configuration(Frame):
|
||||
else: # value exceeds limits
|
||||
self.fields[key][i].config(background="Red") # set colour of this entry to red to show problem
|
||||
|
||||
def implement(self): # update config file with user inputs into entry fields and reinitialize
|
||||
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())
|
||||
@@ -404,16 +434,17 @@ class Configuration(Frame):
|
||||
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)
|
||||
"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."
|
||||
"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)
|
||||
@@ -423,9 +454,46 @@ class Configuration(Frame):
|
||||
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):
|
||||
|
||||
|
||||
+39
-27
@@ -24,13 +24,13 @@ class Axis:
|
||||
self.name = g.AXIS_NAMES[index]
|
||||
self.port = g.PORTS[index]
|
||||
|
||||
self.resistance = float(read_config(self.name, "resistance"))
|
||||
self.max_watts = float(read_config(self.name, "max_watts"))
|
||||
self.resistance = float(read_from_config(self.name, "resistance", g.CONFIG_OBJECT))
|
||||
self.max_watts = float(read_from_config(self.name, "max_watts", g.CONFIG_OBJECT))
|
||||
self.max_amps = np.sqrt(self.max_watts / self.resistance)
|
||||
self.max_volts = float(read_config(self.name, "max_volts"))
|
||||
self.max_volts = float(read_from_config(self.name, "max_volts", g.CONFIG_OBJECT))
|
||||
|
||||
self.coil_constant = float(read_config(self.name, "coil_const"))
|
||||
self.ambient_field = float(read_config(self.name, "ambient_field"))
|
||||
self.coil_constant = float(read_from_config(self.name, "coil_const", g.CONFIG_OBJECT))
|
||||
self.ambient_field = float(read_from_config(self.name, "ambient_field", g.CONFIG_OBJECT))
|
||||
|
||||
max_field = self.max_amps * self.coil_constant # max field reachable in this axis
|
||||
self.max_field = np.array([-max_field, max_field])
|
||||
@@ -145,7 +145,7 @@ class ArduinoCtrl(Arduino):
|
||||
self.connected = "Unknown"
|
||||
self.pins = [0, 0, 0]
|
||||
for i in range(3): # get correct pins from config file
|
||||
self.pins[i] = int(read_config(g.AXIS_NAMES[i], "relay_pin"))
|
||||
self.pins[i] = int(read_from_config(g.AXIS_NAMES[i], "relay_pin", g.CONFIG_OBJECT))
|
||||
ui_print("\nConnecting to Arduino...")
|
||||
try:
|
||||
Arduino.__init__(self) # search for connected arduino and connect
|
||||
@@ -180,10 +180,19 @@ class ArduinoCtrl(Arduino):
|
||||
self.digitalWrite(pin, "LOW")
|
||||
|
||||
|
||||
def read_config(section, key): # read specific value from config file
|
||||
def get_config_from_file(file=g.CONFIG_FILE):
|
||||
config_object = ConfigParser() # initialize config parser
|
||||
config_object.read(file) # open config file
|
||||
return config_object # return config object, that contains all info from the file
|
||||
|
||||
|
||||
def write_config_to_file(config_object): # ToDo: comments
|
||||
with open(g.CONFIG_FILE, 'w') as conf: # Write changes to config file
|
||||
config_object.write(conf)
|
||||
|
||||
|
||||
def read_from_config(section, key, config_object): # read specific value from config object
|
||||
try:
|
||||
config_object.read(g.CONFIG_FILE) # open config file
|
||||
section_obj = config_object[section] # get relevant section
|
||||
value = section_obj[key] # get relevant value in the section
|
||||
return value
|
||||
@@ -193,7 +202,8 @@ def read_config(section, key): # read specific value from config file
|
||||
|
||||
|
||||
def edit_config(section, key, value, override=False): # edit specific value in config file
|
||||
config_object = ConfigParser() # initialize config parser
|
||||
config_object = g.CONFIG_OBJECT
|
||||
# ToDo: add check for data types, e.g. int for arduino ports
|
||||
|
||||
# Check if value to write is within acceptable limits (set in globals.py)
|
||||
try:
|
||||
@@ -214,19 +224,24 @@ def edit_config(section, key, value, override=False): # edit specific value in
|
||||
raise ValueError(message)
|
||||
|
||||
if value_ok == 'OK' or override: # value is within limits or user has overridden
|
||||
config_object.read(g.CONFIG_FILE) # open config file
|
||||
section_obj = config_object[section] # get relevant section
|
||||
section_obj[key] = str(value) # get relevant value in the section
|
||||
|
||||
with open(g.CONFIG_FILE, 'w') as conf: # Write changes to config file
|
||||
config_object.write(conf)
|
||||
try:
|
||||
section_obj = config_object[section] # get relevant section
|
||||
except KeyError:
|
||||
ui_print("Could not find section", section, "in config file, creating new.")
|
||||
config_object.add_section(section)
|
||||
section_obj = config_object[section]
|
||||
try:
|
||||
section_obj[key] = str(value) # set relevant value in the section
|
||||
except KeyError:
|
||||
ui_print("Could not find key", key, "in config file, creating new.")
|
||||
config_object.set(section, key, str(value))
|
||||
|
||||
except KeyError as e:
|
||||
ui_print("Error while editing config file:", e)
|
||||
raise KeyError("Could not find key", key, "in config file.")
|
||||
|
||||
|
||||
def check_config(): # check all numeric values in the config file and see if they are within safe limits
|
||||
def check_config(config_object): # check all numeric values in the config and see if they are within safe limits
|
||||
ui_print("Checking config file for values exceeding limits:")
|
||||
i = 0
|
||||
concerns = {} # initialize dictionary for found problems
|
||||
@@ -234,7 +249,7 @@ def check_config(): # check all numeric values in the config file and see if th
|
||||
for axis in g.AXIS_NAMES:
|
||||
concerns[axis] = [] # create dictionary entry for this axis
|
||||
for key in g.default_arrays.keys(): # go over entries in this axis
|
||||
value = float(read_config(axis, key)) # read value to check from config file
|
||||
value = float(read_from_config(axis, key, config_object)) # read value to check from config file
|
||||
max_value = g.default_arrays[key][1][i] # get max value
|
||||
min_value = g.default_arrays[key][2][i] # get min value
|
||||
|
||||
@@ -254,8 +269,9 @@ def check_config(): # check all numeric values in the config file and see if th
|
||||
g.app.show_frame(ui.Configuration) # open configuration window so user can check values
|
||||
|
||||
|
||||
def create_default_config(file): # create config file from default values (stored in globals.py)
|
||||
config = ConfigParser() # initialize config object
|
||||
def reset_config_to_default(file): # reset values in config object to defaults (stored in globals.py)
|
||||
config = ConfigParser() # initialize global config object
|
||||
g.CONFIG_OBJECT = config
|
||||
|
||||
i = 0
|
||||
for axis_name in g.AXIS_NAMES: # go through axes
|
||||
@@ -268,9 +284,6 @@ def create_default_config(file): # create config file from default values (stor
|
||||
for key in g.default_ports.keys():
|
||||
config.set("PORTS", key, str(g.default_ports[key]))
|
||||
|
||||
with open(file, 'w') as conf: # write all we just did to the file
|
||||
config.write(conf)
|
||||
|
||||
|
||||
def ui_print(*content): # prints text to built in console
|
||||
output = ""
|
||||
@@ -318,8 +331,8 @@ def setup_all(): # main initialization function, creates device objects for all
|
||||
|
||||
g.AXES = []
|
||||
|
||||
g.XY_PORT = read_config("PORTS", "xy_port")
|
||||
g.Z_PORT = read_config("PORTS", "z_port")
|
||||
g.XY_PORT = read_from_config("PORTS", "xy_port", g.CONFIG_OBJECT)
|
||||
g.Z_PORT = read_from_config("PORTS", "z_port", g.CONFIG_OBJECT)
|
||||
g.PORTS = [g.XY_PORT, g.XY_PORT, g.Z_PORT]
|
||||
|
||||
ui_print("\nConnecting to XY Device on %s..." % g.XY_PORT)
|
||||
@@ -379,9 +392,8 @@ def set_to_zero(device): # sets voltages and currents to 0
|
||||
|
||||
|
||||
def power_down_all(): # temporary, set all outputs to 0 but keep connections enabled
|
||||
set_to_zero(g.XY_DEVICE)
|
||||
set_to_zero(g.Z_DEVICE)
|
||||
g.ARDUINO.safe()
|
||||
for axis in g.AXES:
|
||||
axis.power_down() # set outputs to 0 and pin to low on this axis
|
||||
|
||||
|
||||
def shut_down_all(): # shutdown at program end or on error, set outputs to 0 and disable connections
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ app = None
|
||||
|
||||
AXIS_NAMES = ["X-Axis", "Y-Axis", "Z-Axis"]
|
||||
|
||||
global CONFIG_FILE
|
||||
CONFIG_FILE = None
|
||||
CONFIG_OBJECT = None
|
||||
|
||||
global XY_PORT
|
||||
global Z_PORT
|
||||
|
||||
@@ -7,10 +7,14 @@ from os.path import exists
|
||||
try: # start normal operations
|
||||
|
||||
g.CONFIG_FILE = 'config.ini'
|
||||
|
||||
# ToDo: remember what the last config file was
|
||||
if not exists(g.CONFIG_FILE):
|
||||
print("Config file not found, creating new from defaults.")
|
||||
func.create_default_config(g.CONFIG_FILE)
|
||||
func.reset_config_to_default(g.CONFIG_FILE)
|
||||
func.write_config_to_file(g.CONFIG_OBJECT)
|
||||
|
||||
g.CONFIG_OBJECT = func.get_config_from_file(g.CONFIG_FILE)
|
||||
print(g.CONFIG_OBJECT)
|
||||
|
||||
print("Starting setup...")
|
||||
func.setup_all() # initiate communication, set handles
|
||||
@@ -19,7 +23,7 @@ try: # start normal operations
|
||||
|
||||
g.app = HelmholtzGUI()
|
||||
func.ui_print("Program Initialized")
|
||||
func.check_config() # check config file for values exceeding limits
|
||||
func.check_config(g.CONFIG_OBJECT) # check config file for values exceeding limits
|
||||
|
||||
func.ui_print("\nStarting setup...") # do it again, so it is printed in the UI console ToDo: do it only once
|
||||
func.setup_all() # initiate communication, set handles
|
||||
|
||||
Reference in New Issue
Block a user