From 1847df859c98d9e67bfd898b4e884f531be8ae99 Mon Sep 17 00:00:00 2001 From: Martin Zietz Date: Wed, 3 Feb 2021 16:40:45 +0100 Subject: [PATCH] more safe value checking --- User_Interface.py | 67 ++++++++++++++++++++++----- cage_func.py | 113 ++++++++++++++++++++++++++++++---------------- main.py | 4 +- 3 files changed, 132 insertions(+), 52 deletions(-) diff --git a/User_Interface.py b/User_Interface.py index 1651e80..b039cd0 100644 --- a/User_Interface.py +++ b/User_Interface.py @@ -1,5 +1,6 @@ from tkinter import * from tkinter import ttk +from tkinter import messagebox import globals as g import cage_func as func import numpy as np @@ -264,7 +265,7 @@ class Configuration(Frame): 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 = 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) @@ -293,7 +294,7 @@ class Configuration(Frame): "Arduino Pins:": [[IntVar() for _ in range(3)], "-", "Should be 15, 16, 17", "relay_pin", 1] } - self.update_fields() # set current values from config file + self.fields = {} # Fill in header (axis names): col = 1 @@ -304,9 +305,11 @@ class Configuration(Frame): # 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 = ttk.Entry(value_frame, textvariable=self.entries[key][0][axis], width=10) + 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]) @@ -317,6 +320,10 @@ class Configuration(Frame): row_counter += 1 + print(self.fields) + + 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 @@ -359,23 +366,61 @@ class Configuration(Frame): 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: Warning messages if too high values are entered + value_check = func.value_in_limits(g.AXIS_NAMES[i], self.entries[key][3], value) + if value_check == 'OK': + self.fields[key][i].config(background="White") + else: + self.fields[key][i].config(background="Red") + def implement(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 + 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 - factor = self.entries[key][4] # get unit conversion factor - if factor not in [0, 1]: # prevent conversion of int variables to float and div/0 - value = value / factor # do unit conversion - func.edit_config(g.AXIS_NAMES[i], self.entries[key][3], value) # write new value to config file - except TclError as e: + 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 + + 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. Excessive values may damage equipment!\n" \ + "Do you really want to use this value?"\ + .format(s=g.AXIS_NAMES[i], 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. Excessive values may damage equipment!\n" \ + "Do you really want to use this value?"\ + .format(s=g.AXIS_NAMES[i], 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 + func.setup_axes() # reinitialize devices and program with new values self.update_fields() # update entry fields to show new values diff --git a/cage_func.py b/cage_func.py index 0b07836..28b9a1a 100644 --- a/cage_func.py +++ b/cage_func.py @@ -6,6 +6,7 @@ import time import numpy as np import serial import traceback +import User_Interface as ui from tkinter import * from tkinter import messagebox from configparser import ConfigParser @@ -177,22 +178,23 @@ class ArduinoCtrl(Arduino): def read_config(section, key): # read specific value from config file # ToDo (optional): better error handling - # ToDo: make pop-up error message for excessive values that can be waived + config_object = ConfigParser() # initialize config parser 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 - # Value checking: - if section in g.AXIS_NAMES: # only check numerical values + + # Value checking: ToDo: decide if we want this + '''if section in g.AXIS_NAMES: # only check numerical values 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)] if float(value) > float(max_value): - ui_print("WARNING: Too high value for", section, key, "read from config file:", - value, "max.", max_value, "allowed. Excessive values may damage equipment!") + ui_print("\nWARNING: Too high value for", section, key, "read from config file:", + value, "max.", max_value, "allowed. Excessive values may damage equipment!\n") elif float(value) < float(min_value): - ui_print("WARNING: Too low value for", section, key, "read from config file:", - value, "max.", max_value, "allowed. Excessive values may damage equipment!") + ui_print("\nWARNING: Too low value for", section, key, "read from config file:", + value, "max.", max_value, "allowed. Excessive values may damage equipment!\n")''' return value except KeyError as e: ui_print("Error while reading config file:", e) @@ -201,64 +203,82 @@ 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 - value_ok = False - # Value checking: - # ToDo: make pop-up warning messages that can be waived + + # Check if value is within acceptable limits (set in globals.py) try: - if section in g.AXIS_NAMES: # only check numerical values + value_ok = True + if section in g.AXIS_NAMES and not override: # only check numerical values and not if overridden by user + value_ok = False 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 > max_value: - message = "Attempted to write too high value for {s} {k} to config file:\n" \ - "{v}, max. {mv} allowed. Excessive values may damage equipment!\n" \ - "Do you really want to use this value?".format(s=section, k=key, v=value, mv=max_value) + 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 < min_value: - message = "Attempted to write too low value for {s} {k} to config file:\n" \ - "{v}, max. {mv} allowed. Excessive values may damage equipment!\n" \ - "Do you really want to use this value?".format(s=section, k=key, v=value, mv=min_value) + 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) else: value_ok = True - except KeyError as e: - ui_print("Error while editing config file:", e) - raise KeyError("Could not find section", section, "in config file.") - except ValueError as e: # value too high/low - value_ok = False - # display pop-up message to ask user if he really wants the value - answer = messagebox.askquestion("Value out of bounds", e) # becomes 'yes' or 'no' depending on user choice - if answer == 'yes': override = True - else: override = False - else: # no errors - value_ok = True - if value_ok or override: # value is ok or user has chosen to use it anyway - try: + + if value_ok or override: 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 back to file config_object.write(conf) - except KeyError as e: - ui_print("Error while editing config file:", e) - raise KeyError("Could not find key", key, "in config file.") + + 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(): + ui_print("Checking config file for values exceeding limits:") + i = 0 + concerns = {} + problem_counter = 0 + for axis in g.AXIS_NAMES: + concerns[axis] = [] + for key in g.default_arrays.keys(): + value = float(read_config(axis, key)) + 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: + concerns[axis].append(key) + problem_counter += 1 + + if len(concerns[axis]) == 0: + concerns[axis].append("No problems detected.") + + ui_print(axis, ":", *concerns[axis]) + i += 1 + if problem_counter > 0: + messagebox.showwarning("Warning!", "Found values exceeding limits in config file. Check values in " + "configuration page to avoid equipment damage!") + g.app.show_frame(ui.Configuration) def create_default_config(file): # create config file from default values (stored in globals.py) - config = ConfigParser() + config = ConfigParser() # initialize config object i = 0 - for axis_name in g.AXIS_NAMES: - config.add_section(axis_name) - for key in g.default_arrays.keys(): - config.set(axis_name, key, str(g.default_arrays[key][0][i])) + 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") + 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])) - with open(file, 'w') as conf: + with open(file, 'w') as conf: # write all we just did to the file config.write(conf) @@ -274,6 +294,19 @@ def ui_print(*content): # prints text to built in console print(output) +def value_in_limits(axis, key, value): # Check if value is within safe limits (set in globals.py) + # ToDo: replace checks everywhere with this + max_value = g.default_arrays[key][1][g.AXIS_NAMES.index(axis)] # get max value + min_value = g.default_arrays[key][2][g.AXIS_NAMES.index(axis)] # get min value + + if float(value) > float(max_value): + return 'HIGH' + elif float(value) < float(min_value): + return 'LOW' + else: + return 'OK' + + def setup_axes(): # creates device objects for all PSUs and sets their values # Connect to Arduino: try: diff --git a/main.py b/main.py index b9fa5fc..ad10971 100644 --- a/main.py +++ b/main.py @@ -19,7 +19,9 @@ try: # start normal operations g.app = HelmholtzGUI() func.ui_print("Program Initialized") - func.ui_print("Starting setup...") # do it again, so it is printed in the UI console + func.check_config() # 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_axes() # initiate communication, set handles g.app.mainloop() g.app = None # reset to None so nothing tries to print in the UI output