diff --git a/User_Interface.py b/User_Interface.py index 9bbb263..a056c59 100644 --- a/User_Interface.py +++ b/User_Interface.py @@ -10,6 +10,7 @@ from os.path import exists import globals as g import cage_func as func import csv_threading as csv +import config_handling as config NORM_FONT = () HEADER_FONT = ("Arial", 13, "bold") @@ -328,7 +329,7 @@ class Configuration(Frame): "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. Current:": [[DoubleVar() for _ in range(3)], "A", "Max. allowed current", "max_amps", 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] @@ -387,7 +388,7 @@ class Configuration(Frame): self.update_fields() def restore_defaults(self): # restore all default settings - func.reset_config_to_default(g.CONFIG_FILE) # overwrite config file with default + config.reset_config_to_default(config.CONFIG_FILE) # overwrite config file with default func.setup_all() # setup everything with the defaults self.update_fields() # update fields in config window @@ -398,8 +399,8 @@ class Configuration(Frame): 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 + value = config.read_from_config(g.AXIS_NAMES[i], self.entries[key][3], + config.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 @@ -415,8 +416,8 @@ class Configuration(Frame): 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()) + config.edit_config("PORTS", "xy_port", self.XY_port.get()) + config.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 @@ -438,7 +439,7 @@ class Configuration(Frame): 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 + config.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 @@ -462,7 +463,7 @@ class Configuration(Frame): # 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) + config.edit_config(g.AXIS_NAMES[i], config_key, value, True) # if user chooses 'no' nothing happens, old value is kept def implement(self): # executed on button press @@ -471,14 +472,15 @@ class Configuration(Frame): self.update_fields() # update entry fields to show new values def load_config(self): # load configuration from some config file - directory = os.path.dirname(os.path.abspath(g.CONFIG_FILE)) # get directory of current config file + directory = os.path.dirname(os.path.abspath(config.CONFIG_FILE)) # get directory of current config file # open file selection dialogue and save path of selected file filename = filedialog.askopenfilename(initialdir=directory, title="Select Config File", filetypes=(("Config File", "*.ini*"), ("All Files", "*.*"))) if exists(filename): # does the file exist? - g.CONFIG_FILE = filename # set global config file to the new file - g.CONFIG_OBJECT = func.get_config_from_file(filename) # load values from config file to config object - func.check_config(g.CONFIG_OBJECT) # check the values and display warnings if values are out of bounds + config.CONFIG_FILE = filename # set global config file to the new file + config.CONFIG_OBJECT = config.get_config_from_file(filename) # load from config file to config object + config.check_config( + config.CONFIG_OBJECT) # check the values and display warnings if values are out of bounds func.setup_all() # reinitialize devices and program with new values self.update_fields() # update entry fields to show new values elif filename == '': # this happens when file selection window is closed without selecting a file @@ -487,24 +489,23 @@ class Configuration(Frame): func.ui_print("Selected file", filename, "does not exist, could not load config.") def save_config_as(self): # save current configuration to a new config file - directory = os.path.dirname(os.path.abspath(g.CONFIG_FILE)) # get directory of current config file + directory = os.path.dirname(os.path.abspath(config.CONFIG_FILE)) # get directory of current config file # open file selection dialogue and save path of selected file filename = filedialog.asksaveasfilename(initialdir=directory, title="Save config to file", filetypes=([("Config File", "*.ini*")]), defaultextension=[("Config File", "*.ini*")]) - if exists(filename): # does the file exist? - g.CONFIG_FILE = filename # set global config file to the new file - self.write_values() # write current entry field values to the config object - func.write_config_to_file(g.CONFIG_OBJECT) # write contents of config object to file - self.update_fields() # update entry fields to show values as they are in the config - elif filename == '': # this happens when file selection window is closed without selecting a file + + if filename == '': # this happens when file selection window is closed without selecting a file func.ui_print("No file selected, could not save config.") - else: - func.ui_print("Selected file", filename, "does not exist, could not save config.") + else: # a file name was entered + config.CONFIG_FILE = filename # set global config file to the new file + self.write_values() # write current entry field values to the config object + config.write_config_to_file(config.CONFIG_OBJECT) # write contents of config object to file + self.update_fields() # update entry fields to show values as they are in the config def save_config(self): # same as save_config_as() but with the current config file self.write_values() - func.write_config_to_file(g.CONFIG_OBJECT) + config.write_config_to_file(config.CONFIG_OBJECT) self.update_fields() @@ -541,18 +542,19 @@ class ExecuteCSVMode(Frame): # Create and place buttons self.select_file_button = Button(self.file_select_frame, text="Select csv file...", command=self.load_csv, - pady=5, padx=5, font=SMALL_BUTTON_FONT) + 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, - pady=5, padx=5, font=SMALL_BUTTON_FONT, state="disabled") + 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, - pady=5, padx=5, font=SMALL_BUTTON_FONT, state="disabled") + pady=5, padx=5, font=SMALL_BUTTON_FONT, state="disabled") self.stop_button.grid(row=0, column=2, padx=5) row_counter += 1 - def page_switch(self): # every class in the UI needs this, even if it doesn't do anything + 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 def load_csv(self): # load in csv file to be executed diff --git a/cage_func.py b/cage_func.py index 6af91b6..a892022 100644 --- a/cage_func.py +++ b/cage_func.py @@ -1,16 +1,13 @@ -from pyps2000b import PS2000B -from Arduino import Arduino -import globals as g -import pandas -import time import numpy as np import serial import traceback -# noinspection PyPep8Naming -import User_Interface as ui from tkinter import * -from tkinter import messagebox -from configparser import ConfigParser + +from pyps2000b import PS2000B +from Arduino import Arduino +# noinspection PyPep8Naming +import config_handling as config +import globals as g class Axis: @@ -24,13 +21,13 @@ class Axis: self.name = g.AXIS_NAMES[index] self.port = g.PORTS[index] - 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_from_config(self.name, "max_volts", g.CONFIG_OBJECT)) + self.resistance = float(config.read_from_config(self.name, "resistance", config.CONFIG_OBJECT)) + self.max_watts = float(config.read_from_config(self.name, "max_watts", config.CONFIG_OBJECT)) + self.max_amps = float(config.read_from_config(self.name, "max_amps", config.CONFIG_OBJECT)) + self.max_volts = float(config.read_from_config(self.name, "max_volts", config.CONFIG_OBJECT)) - 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)) + self.coil_constant = float(config.read_from_config(self.name, "coil_const", config.CONFIG_OBJECT)) + self.ambient_field = float(config.read_from_config(self.name, "ambient_field", config.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 +142,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_from_config(g.AXIS_NAMES[i], "relay_pin", g.CONFIG_OBJECT)) + self.pins[i] = int(config.read_from_config(g.AXIS_NAMES[i], "relay_pin", config.CONFIG_OBJECT)) ui_print("\nConnecting to Arduino...") try: Arduino.__init__(self) # search for connected arduino and connect @@ -180,111 +177,6 @@ class ArduinoCtrl(Arduino): self.digitalWrite(pin, "LOW") -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): # write contents of config object to a config file - 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: - section_obj = config_object[section] # get relevant section - value = section_obj[key] # get relevant value in the section - return value - except KeyError as e: - ui_print("Error while reading config file:", e) - raise KeyError("Could not find key", key, "in config file.") - - -def edit_config(section, key, value, override=False): # edit specific value in config file - 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: - value_ok = 'OK' - if section in g.AXIS_NAMES and not override: # only check numerical values and not if overridden by user - value_ok = value_in_limits(section, key, value) # check if value is ok, too high or too low - max_value = g.default_arrays[key][1][g.AXIS_NAMES.index(section)] # get max value - min_value = g.default_arrays[key][2][g.AXIS_NAMES.index(section)] # get min value - if value_ok == 'HIGH': - message = "Prevented writing too high value for {s} {k} to config file:\n" \ - "{v}, max. {mv} allowed. Erroneous values may damage equipment!" \ - .format(s=section, k=key, v=value, mv=max_value) - raise ValueError(message) - elif value_ok == 'LOW': - message = "Prevented writing too low value for {s} {k} to config file:\n" \ - "{v}, max. {mv} allowed. Erroneous values may damage equipment!" \ - .format(s=section, k=key, v=value, mv=min_value) - raise ValueError(message) - - if value_ok == 'OK' or override: # value is within limits or user has overridden - 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(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 - problem_counter = 0 - 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_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 - - if not min_value <= value <= max_value: # value is not in safe limits - concerns[axis].append(key) # add this entry to the problem dictionary - problem_counter += 1 - - if len(concerns[axis]) == 0: - concerns[axis].append("No problems detected.") - - ui_print(axis, ":", *concerns[axis]) # print out results for this axis - i += 1 - if problem_counter > 0: # some values are not ok - # shop pup-up warning message: - messagebox.showwarning("Warning!", "Found values exceeding limits in config file. Check values " - "to ensure correct operation and avoid equipment damage!") - g.app.show_frame(ui.Configuration) # open configuration window so user can check values - - -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 - config.add_section(axis_name) # add section for this axis - for key in g.default_arrays.keys(): # go through dictionary with default values - config.set(axis_name, key, str(g.default_arrays[key][0][i])) # set value - i += 1 - - config.add_section("PORTS") # add section for PSU serial ports - for key in g.default_ports.keys(): - config.set("PORTS", key, str(g.default_ports[key])) - - def ui_print(*content): # prints text to built in console output = "" for text in content: @@ -331,8 +223,8 @@ def setup_all(): # main initialization function, creates device objects for all g.AXES = [] - 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.XY_PORT = config.read_from_config("PORTS", "xy_port", config.CONFIG_OBJECT) + g.Z_PORT = config.read_from_config("PORTS", "z_port", config.CONFIG_OBJECT) g.PORTS = [g.XY_PORT, g.XY_PORT, g.Z_PORT] ui_print("\nConnecting to XY Device on %s..." % g.XY_PORT) diff --git a/config_handling.py b/config_handling.py new file mode 100644 index 0000000..13ff1ad --- /dev/null +++ b/config_handling.py @@ -0,0 +1,115 @@ +from configparser import ConfigParser +from tkinter import messagebox + +import globals as g +import cage_func as func +# noinspection PyPep8Naming +import User_Interface as ui + +global CONFIG_FILE +global CONFIG_OBJECT + + +def get_config_from_file(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): # write contents of config object to a config file + with open(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: + section_obj = config_object[section] # get relevant section + value = section_obj[key] # get relevant value in the section + return value + except KeyError as e: + func.ui_print("Error while reading config file:", e) + raise KeyError("Could not find key", key, "in config file.") + + +def edit_config(section, key, value, override=False): # edit specific value in config file + config_object = 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: + value_ok = 'OK' + if section in g.AXIS_NAMES and not override: # only check numerical values and not if overridden by user + value_ok = func.value_in_limits(section, key, value) # check if value is ok, too high or too low + max_value = g.default_arrays[key][1][g.AXIS_NAMES.index(section)] # get max value + min_value = g.default_arrays[key][2][g.AXIS_NAMES.index(section)] # get min value + if value_ok == 'HIGH': + message = "Prevented writing too high value for {s} {k} to config file:\n" \ + "{v}, max. {mv} allowed. Erroneous values may damage equipment!" \ + .format(s=section, k=key, v=value, mv=max_value) + raise ValueError(message) + elif value_ok == 'LOW': + message = "Prevented writing too low value for {s} {k} to config file:\n" \ + "{v}, max. {mv} allowed. Erroneous values may damage equipment!" \ + .format(s=section, k=key, v=value, mv=min_value) + raise ValueError(message) + + if value_ok == 'OK' or override: # value is within limits or user has overridden + try: + section_obj = config_object[section] # get relevant section + except KeyError: + func.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: + func.ui_print("Could not find key", key, "in config file, creating new.") + config_object.set(section, key, str(value)) + + except KeyError as e: + func.ui_print("Error while editing config file:", e) + raise KeyError("Could not find key", key, "in config file.") + + +def check_config(config_object): # check all numeric values in the config and see if they are within safe limits + func.ui_print("Checking config file for values exceeding limits:") + i = 0 + concerns = {} # initialize dictionary for found problems + problem_counter = 0 + 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_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 + + if not min_value <= value <= max_value: # value is not in safe limits + concerns[axis].append(key) # add this entry to the problem dictionary + problem_counter += 1 + + if len(concerns[axis]) == 0: + concerns[axis].append("No problems detected.") + + func.ui_print(axis, ":", *concerns[axis]) # print out results for this axis + i += 1 + if problem_counter > 0: # some values are not ok + # shop pup-up warning message: + messagebox.showwarning("Warning!", "Found values exceeding limits in config file. Check values " + "to ensure correct operation and avoid equipment damage!") + g.app.show_frame(ui.Configuration) # open configuration window so user can check values + + +def reset_config_to_default(file): # reset values in config object to defaults (stored in globals.py) + config = ConfigParser() # initialize global config object + config.CONFIG_OBJECT = config + + i = 0 + for axis_name in g.AXIS_NAMES: # go through axes + config.add_section(axis_name) # add section for this axis + for key in g.default_arrays.keys(): # go through dictionary with default values + config.set(axis_name, key, str(g.default_arrays[key][0][i])) # set value + i += 1 + + config.add_section("PORTS") # add section for PSU serial ports + for key in g.default_ports.keys(): + config.set("PORTS", key, str(g.default_ports[key])) \ No newline at end of file diff --git a/globals.py b/globals.py index 631e521..f33f60a 100644 --- a/globals.py +++ b/globals.py @@ -15,9 +15,6 @@ app = None AXIS_NAMES = ["X-Axis", "Y-Axis", "Z-Axis"] -CONFIG_FILE = None -CONFIG_OBJECT = None - global XY_PORT global Z_PORT @@ -37,6 +34,7 @@ default_arrays = { "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] + "max_amps": np.array([[4.5, 4.5, 4.5], [6, 6, 6], [0, 0, 0]], dtype=float), # max. allowed current (A) "relay_pin": [[15, 16, 17], [15, 16, 17], [15, 16, 17]] # pins on the arduino for reversing [x,y,z] polarity } default_ports = { diff --git a/main.py b/main.py index 7067a1a..d1dc877 100644 --- a/main.py +++ b/main.py @@ -1,20 +1,22 @@ +from os.path import exists + from User_Interface import HelmholtzGUI import cage_func as func import traceback import globals as g -from os.path import exists +import config_handling as config + try: # start normal operations - g.CONFIG_FILE = 'config.ini' + config.CONFIG_FILE = 'config.ini' # ToDo: remember what the last config file was - if not exists(g.CONFIG_FILE): + if not exists(config.CONFIG_FILE): print("Config file not found, creating new from defaults.") - func.reset_config_to_default(g.CONFIG_FILE) - func.write_config_to_file(g.CONFIG_OBJECT) + config.reset_config_to_default(config.CONFIG_FILE) + config.write_config_to_file(config.CONFIG_OBJECT) - g.CONFIG_OBJECT = func.get_config_from_file(g.CONFIG_FILE) - print(g.CONFIG_OBJECT) + config.CONFIG_OBJECT = config.get_config_from_file(config.CONFIG_FILE) print("Starting setup...") func.setup_all() # initiate communication, set handles @@ -23,7 +25,7 @@ try: # start normal operations g.app = HelmholtzGUI() func.ui_print("Program Initialized") - func.check_config(g.CONFIG_OBJECT) # check config for values exceeding limits + config.check_config(config.CONFIG_OBJECT) # check config 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