From 59d0184dd6b561420b240d931e91c5c16ed69dbc Mon Sep 17 00:00:00 2001 From: Martin Zietz Date: Tue, 16 Feb 2021 13:43:55 +0100 Subject: [PATCH] comments and cleanup cage_func --- cage_func.py | 290 ++++++++++++++++++++++++++++----------------------- 1 file changed, 160 insertions(+), 130 deletions(-) diff --git a/cage_func.py b/cage_func.py index 224ed44..f0f64e3 100644 --- a/cage_func.py +++ b/cage_func.py @@ -27,7 +27,7 @@ class Axis: self.name = g.AXIS_NAMES[index] # get name of this axis from list in globals.py (e.g. "X-Axis" self.port = g.PORTS[index] # get serial port of this axis PSU - # read static information from the configuration object (which has read it from the config file or settings) + # read static information from the configuration object (which has read it from the config file or settings): self.resistance = float(config.read_from_config(self.name, "resistance", 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)) @@ -35,11 +35,12 @@ class Axis: 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]) - self.max_comp_field = np.array([self.ambient_field - max_field, self.ambient_field + max_field]) + max_field = self.max_amps * self.coil_constant # calculate max field reachable in this axis + self.max_field = np.array([-max_field, max_field]) # make array with min/max reachable field (w/o compensation) + # calculate max and min field that can be reached after compensating for the ambient field + self.max_comp_field = np.array([self.ambient_field - max_field, self.ambient_field + max_field]) # [min, max] - # dynamic information + # initialize dynamic information, this is updated by self.update_status_info() later self.connected = "Not Connected" self.output_active = "Unknown" # power output on the PSU enabled? self.remote_ctrl_active = "Unknown" # remote control on the PSU enabled? @@ -57,189 +58,211 @@ class Axis: if self.device is not None: self.update_status_info() - def update_status_info(self): # Read out the values of the parameters stored in this class and update them - try: - self.device.update_device_information(self.channel) - device_status = self.device.get_device_status_information(self.channel) - if device_status.output_active: + def update_status_info(self): # Read out the values of the dynamic parameters stored in this object and update them + try: # try to read out the data, this will fail on connection error to PSU + self.device.update_device_information(self.channel) # update the information in the device object + device_status = self.device.get_device_status_information(self.channel) # get object with new status info + + if device_status.output_active: # is the power output active? self.output_active = "Active" else: self.output_active = "Inactive" + + # is remote control active, allowing the device to be controlled by this program? if device_status.remote_control_active: self.remote_ctrl_active = "Active" else: self.remote_ctrl_active = "Inactive" + # get currents and voltages: self.voltage = self.device.get_voltage(self.channel) self.voltage_setpoint = self.device.get_voltage_setpoint(self.channel) self.current = self.device.get_current(self.channel) self.current_setpoint = self.device.get_current_setpoint(self.channel) - except (serial.serialutil.SerialException, IndexError): - if self.connected == "Connected": + + except (serial.serialutil.SerialException, IndexError): # Connection error, usually the PSU is unplugged + if self.connected == "Connected": # only show error messages if the device was connected before this error + # Show error as print-out in console and as pop-up: ui_print("Connection Error with %s PSU on %s" % (self.name, self.port)) messagebox.showerror("PSU Error", "Connection Error with %s PSU on %s" % (self.name, self.port)) + # set status attributes to connection error status: self.connected = "Connection Error" self.output_active = "Unknown" self.remote_ctrl_active = "Unknown" - else: - self.connected = "Connected" + else: # no communications error + self.connected = "Connected" # PSU is connected - def print_status(self): # axis = axis control variable, stored in globals.py + def print_status(self): # print out the current status of the device (not used at the moment) ui_print("%s, %0.2f V, %0.2f A" % (self.device.get_device_status_information(self.channel), self.device.get_voltage(self.channel), self.device.get_current(self.channel))) def power_down(self): # temporary powerdown, set outputs to 0 but keep connections enabled try: + # set class object attributes to 0 to reflect shutdown in status displays, log files etc. self.target_current = 0 self.target_field = 0 self.target_field_comp = 0 - if self.device is not None: - self.device.set_voltage(0, self.channel) - self.device.set_current(0, self.channel) - self.device.disable_output(self.channel) - g.ARDUINO.digitalWrite(self.ardPin, "LOW") - except Exception as e: + + if self.device is not None: # there is a PSU connected for this axis + self.device.set_voltage(0, self.channel) # set voltage on PSU channel to 0 + self.device.set_current(0, self.channel) # set current on PSU channel to 0 + self.device.disable_output(self.channel) # disable power output on PSU channel + g.ARDUINO.digitalWrite(self.ardPin, "LOW") # set arduino pin for polarity switch relay to unpowered state + except Exception as e: # some error was encountered + # show error message: ui_print("Error while powering down %s: %s" % (self.name, e)) messagebox.showerror("PSU Error!", "Error while powering down %s: \n%s" % (self.name, e)) - def set_signed_current(self, value): # sets current with correct polarity on this axis - device = self.device - channel = self.channel - ardPin = self.ardPin + def set_signed_current(self, value): + # sets current with correct polarity on this axis, this is the primary way to control the test stand + # ui_print("Attempting to set current", value, "A") - self.target_current = value - if self.connected == "Connected" or True: # ToDo!: remove True, only for arduino testing! + self.target_current = value # show target value in object attribute for status display, logging etc. - if abs(value) > self.max_amps: # prevent excessive currents - self.power_down() # set output to 0 and deactivate - raise ValueError("Invalid current value. Tried %0.2fA, max. %0.2fA allowed" % (value, self.max_amps)) + if abs(value) > self.max_amps: # prevent excessive currents + self.power_down() # set output to 0 and deactivate + raise ValueError("Invalid current value. Tried %0.2fA, max. %0.2fA allowed" % (value, self.max_amps)) - elif value >= 0: # switch polarity as needed - g.ARDUINO.digitalWrite(ardPin, "LOW") # ToDo: tie to arduino? - elif value < 0: - g.ARDUINO.digitalWrite(ardPin, "HIGH") # ToDo: tie to arduino? - else: - raise Exception("This should be impossible.") + elif value >= 0: # switch the e-box relay to change polarity as needed + g.ARDUINO.digitalWrite(self.ardPin, "LOW") # command the output pin on the arduino in the electronics box + elif value < 0: + g.ARDUINO.digitalWrite(self.ardPin, "HIGH") # command the output pin on the arduino in the electronics box - maxVoltage = min(max(1.1 * self.max_amps * self.resistance, 8), self.max_volts) # limit voltage - # ui_print("sending values to device: U =", maxVoltage, "I =", abs(value)) - if self.connected == "Connected": # ToDo!: remove if clause, only for arduino testing! - device.set_current(abs(value), channel) - device.set_voltage(maxVoltage, channel) - device.enable_output(channel) - else: + # determine voltage limit to be set on PSU, must be high enough to not limit the current: + # min. 8V, max. max_volts, in-between as needed with current value (+margin to not limit current) + maxVoltage = min(max(1.3 * value * self.resistance, 8), self.max_volts) # limit voltage + if self.connected == "Connected": # only try to command the PSU if its actually connected + self.device.set_current(abs(value), self.channel) # set desired current + self.device.set_voltage(maxVoltage, self.channel) # set voltage limit + self.device.enable_output(self.channel) # activate the power output + else: # the PSU is not connected ui_print(self.name, "not connected, can't set current.") def set_field_simple(self, value): # forms magnetic field as specified by value, w/o cancelling ambient field - self.target_field = value - self.target_field_comp = value - current = value / self.coil_constant - self.set_signed_current(current) + self.target_field = value # update object attribute for display + self.target_field_comp = value # same as above, bc no compensation + current = value / self.coil_constant # calculate needed current + self.set_signed_current(current) # command the test stand def set_field(self, value): # forms magnetic field as specified by value, corrected for ambient field - self.target_field = value - field = value - self.ambient_field - self.target_field_comp = field - current = field / self.coil_constant - self.set_signed_current(current) + self.target_field = value # update object attribute for display + field = value - self.ambient_field # calculate needed field after compensation + self.target_field_comp = field # update object attribute for display + current = field / self.coil_constant # calculate needed current + self.set_signed_current(current) # command the test stand class ArduinoCtrl(Arduino): + # main class to control the electronics box (which means commanding the arduino inside) + # inherits from the Arduino library def __init__(self): - self.connected = "Unknown" - self.pins = [0, 0, 0] - for i in range(3): # get correct pins from config file + self.connected = "Unknown" # connection status attribute, nominal "Connected" + self.pins = [0, 0, 0] # initialize list with pins to switch relay of each axis + for i in range(3): # get correct pins from the config 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 + try: # try to set up the arduino + Arduino.__init__(self) # search for connected arduino and connect by initializing arduino library class for pin in self.pins: self.pinMode(pin, "Output") self.digitalWrite(pin, "LOW") - except Exception as e: + except Exception as e: # some error occurred, usually the arduino is not connected ui_print("Connection to Arduino failed.", e) self.connected = "Not Connected" - else: + else: # connection was successfully established self.connected = "Connected" ui_print("Arduino ready.") - def update_status_info(self): - if self.connected == "Connected": - try: - for axis in g.AXES: - if g.ARDUINO.digitalRead(axis.ardPin): - axis.polarity_switched = "True" - else: - axis.polarity_switched = "False" - except Exception as e: + def update_status_info(self): # update the attributes stored in this class object + if self.connected == "Connected": # only do this if arduino is connected (initialize new instance to reconnect) + try: # try to read the status of the pins from the arduino + for axis in g.AXES: # go through all three axes + if g.ARDUINO.digitalRead(axis.ardPin): # pin is HIGH --> relay is switched + axis.polarity_switched = "True" # set attribute in axis object accordingly + else: # pin is LOW --> relay is not switched + axis.polarity_switched = "False" # set attribute in axis object accordingly + except Exception as e: # some error occurred while trying to read status, usually arduino is disconnected + # show warning messages to alert user ui_print("Error with Arduino:", e) messagebox.showerror("Error with Arduino!", "Connection Error with Arduino: \n%s" % e) - for axis in g.AXES: + for axis in g.AXES: # set polarity switch attributes in axis objects to "Unknown" axis.polarity_switched = "Unknown" - self.connected = "Connection Error" - else: - self.connected = "Connected" + self.connected = "Connection Error" # update own connection status + else: # no error occurred --> data was read successfully + self.connected = "Connected" # update own connection status - def safe(self): # sets output pins to low and closes serial connection + def safe(self): # sets relay switching pins to low to depower most of the electronics box for pin in self.pins: self.digitalWrite(pin, "LOW") def value_in_limits(axis, key, value): # Check if value is within safe limits (set in globals.py) - 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 + # axis is string with axis name, e.g. "X-Axis" + # key specifies which value to check, e.g. current + max_value = g.default_arrays[key][1][g.AXIS_NAMES.index(axis)] # get max value from dictionary in globals.py + min_value = g.default_arrays[key][2][g.AXIS_NAMES.index(axis)] # get min value from dictionary in globals.py - if float(value) > float(max_value): + if float(value) > float(max_value): # value is too high return 'HIGH' - elif float(value) < float(min_value): + elif float(value) < float(min_value): # value is too low return 'LOW' - else: + else: # value is within limits return 'OK' -def setup_all(): # main initialization function, creates device objects for all PSUs and Arduino and sets their values - # Connect to Arduino: - try: - if g.ARDUINO is not None: - # ui_print("\nClosing arduino link") +def setup_all(): # main test stand initialization function + # creates device objects for all PSUs and Arduino and sets their values + # initializes an object of class Axis for all three axes (x,y,z) + + # Setup Arduino: + try: # broad error handling for unforeseen errors, handling in ArduinoCtrl should catch most errors + if g.ARDUINO is not None: # the arduino has been initialized before, so we need to first close its connection try: - g.ARDUINO.close() # close serial link before attempting reconnection + g.ARDUINO.close() # close serial link except serial.serialutil.SerialException: pass # serial.flush() in Arduino.close() fails when reconnecting # this ignores it and allows serial.close() to execute (I think) except AttributeError: 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() - except Exception as e: + # when no Arduino is connected but g.ARDUINO has been initialized then there is nothing to close + # this throws an exception, which can be ignored + + g.ARDUINO = ArduinoCtrl() # initialize the arduino object from the control class, connects and sets up + except Exception as e: # some unforeseen error occurred + # show error messages to alert user ui_print("Arduino setup failed:", e) ui_print(traceback.print_exc()) + messagebox.showerror("Error!", "Arduino setup failed:\n%s \nCheck traceback in console." % e) - g.AXES = [] + # Setup PSUs and axis objects: + g.AXES = [] # initialize global list containing the three axis objects + # get serial ports for the PSUs from config 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] + g.PORTS = [g.XY_PORT, g.XY_PORT, g.Z_PORT] # write list with PSU port for each axis (X/Y share PSU) + # setup PSU and axis objects for X and Y axes: ui_print("\nConnecting to XY Device on %s..." % g.XY_PORT) - try: - if g.XY_DEVICE is not None: + try: # try to connect to the PSU + if g.XY_DEVICE is not None: # if PSU has previously been connected we need to close the serial link first ui_print("Closing serial connection on XY device") g.XY_DEVICE.serial.close() g.XY_DEVICE = None g.XY_DEVICE = PS2000B.PS2000B(g.XY_PORT) # setup PSU ui_print("Connection established.") - g.X_AXIS = Axis(0, g.XY_DEVICE, 0, g.ARDUINO.pins[0]) # create axis objects + g.X_AXIS = Axis(0, g.XY_DEVICE, 0, g.ARDUINO.pins[0]) # create axis objects (index, PSU, channel, relay pin) g.Y_AXIS = Axis(1, g.XY_DEVICE, 1, g.ARDUINO.pins[1]) - except serial.serialutil.SerialException: - g.X_AXIS = Axis(0, None, 0, g.ARDUINO.pins[0]) # create axis objects + except serial.serialutil.SerialException: # communications error, usually PSU is not connected or wrong port set + g.X_AXIS = Axis(0, None, 0, g.ARDUINO.pins[0]) # create axis objects without the PSU g.Y_AXIS = Axis(1, None, 1, g.ARDUINO.pins[1]) ui_print("XY Device not connected or incorrect port set.") + # same for the Z axis ui_print("Connecting to Z Device on %s..." % g.Z_PORT) try: if g.Z_DEVICE is not None: @@ -253,16 +276,12 @@ def setup_all(): # main initialization function, creates device objects for all g.Z_AXIS = Axis(2, None, 0, g.ARDUINO.pins[2]) ui_print("Z Device not connected or incorrect port set.") + # put newly created axis objects into a list for access later g.AXES.append(g.X_AXIS) g.AXES.append(g.Y_AXIS) g.AXES.append(g.Z_AXIS) - ui_print("") # new line - - -def activate_all(): # enables remote control and output on all PSUs and channels - g.XY_DEVICE.enable_all() - g.Z_DEVICE.enable_all() + ui_print("") # print new line def set_to_zero(device): # sets voltages and currents to 0 on all channels of a specific PSU @@ -272,27 +291,35 @@ def set_to_zero(device): # sets voltages and currents to 0 on all channels of a device.current2 = 0 -def power_down_all(): # temporary, set all outputs to 0 but keep connections enabled +def power_down_all(): # on all PSUs set all outputs to 0 but keep connections enabled 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 +def shut_down_all(): # safe shutdown at program end or on error + # set outputs to 0 and disable connections on all devices + ui_print("\nAttempting to safely shut down all devices. Check equipment to confirm.") + # start writing string to later show how shutdown on all devices went in a single info pop-up: message = "Tried to shut down all devices. Check equipment to confirm." - if g.XY_DEVICE is not None: - try: - set_to_zero(g.XY_DEVICE) - g.XY_DEVICE.disable_all() - except BaseException as e: - ui_print("Error while deactivating XY PSU:", e) - message += "\nError while deactivating XY PSU: %s" % e - else: + + # Shut down PSUs: + if g.XY_DEVICE is not None: # the PSU has been setup before + try: # try to safe the PSU + set_to_zero(g.XY_DEVICE) # set currents and voltages to 0 for both channels + g.XY_DEVICE.disable_all() # disable power output on both channels + except BaseException as e: # some error occurred, usually device has been disconnected + ui_print("Error while deactivating XY PSU:", e) # print the problem in the console + message += "\nError while deactivating XY PSU: %s" % e # append status to the message to show later + else: # device was successfully deactivated ui_print("XY PSU deactivated.") - message += "\nXY PSU deactivated." - else: + message += "\nXY PSU deactivated." # append message to show later + else: # the device was not connected before + # tell user there was no need/no possibility to deactivate: ui_print("XY PSU not connected, can't deactivate.") message += "\nXY PSU not connected, can't deactivate." + + # same as above if g.Z_DEVICE is not None: try: set_to_zero(g.Z_DEVICE) @@ -307,38 +334,40 @@ def shut_down_all(): # shutdown at program end or on error, set outputs to 0 an ui_print("Z PSU not connected, can't deactivate.") message += "\nZ PSU not connected, can't deactivate." + # Shut down Arduino: try: - g.ARDUINO.safe() - except BaseException as e: + g.ARDUINO.safe() # call safe method in ArduinoCtrl class (all relay pins to LOW) + except BaseException as e: # some error occurred ui_print("Arduino safing unsuccessful:", e) - message += "\nArduino safing unsuccessful: %s" % e + message += "\nArduino safing unsuccessful: %s" % e # append to the message to show later # this throws no exception, even when arduino is not connected # ToDo (optional): figure out error handling for this try: - g.ARDUINO.close() - except BaseException as e: - if g.ARDUINO.connected == "Connected": + g.ARDUINO.close() # close the serial link + except BaseException as e: # something went wrong there + if g.ARDUINO.connected == "Connected": # Arduino was connected, some error occurred ui_print("Closing Arduino connection failed:", e) message += "\nClosing Arduino connection failed: %s" % e - else: + else: # Arduino was not connected, so error is expected ui_print("Arduino not connected, can't close connection.") message += "\nArduino not connected, can't close connection." - else: + else: # no problems, connection was successfully closed ui_print("Serial connection to Arduino closed.") message += "\nSerial connection to Arduino closed." - messagebox.showinfo("Program ended", message) + messagebox.showinfo("Program ended", message) # Show a unified pop-up with how the shutdown on each device went def set_field_simple(vector): # forms magnetic field as specified by vector, w/o cancelling ambient field for i in [0, 1, 2]: try: - g.AXES[i].set_field_simple(vector[i]) - except ValueError as e: - ui_print(e) + g.AXES[i].set_field_simple(vector[i]) # try to set the field on each axis + except ValueError as e: # a limit was violated, usually the needed current was too high + ui_print(e) # let the user know def set_field(vector): # forms magnetic field as specified by vector, corrected for ambient field + # same as set_field_simple(), but with compensation for i in [0, 1, 2]: try: g.AXES[i].set_field(vector[i]) @@ -346,14 +375,15 @@ def set_field(vector): # forms magnetic field as specified by vector, corrected ui_print(e) -def set_current_vec(vector): # sets needed currents on each axis for given vector +def set_current_vec(vector): # sets currents on each axis according to given vector i = 0 for axis in g.AXES: try: - axis.target_field = 0 - axis.target_field_comp = 0 - axis.set_signed_current(vector[i]) - except ValueError as e: + axis.target_field = 0 # set target field attribute to 0 to show that current, not field is controlled atm + axis.target_field_comp = 0 # as above + + axis.set_signed_current(vector[i]) # command test stand to set the current + except ValueError as e: # current was too high ui_print(e) i += 1