forked from zietzm/Helmholtz_Test_Bench
Partially completed front-end reintegration.
This commit is contained in:
+110
-68
@@ -3,6 +3,8 @@
|
||||
|
||||
# ToDo: optimize layout for smaller screen (like on IRS clean room PC)
|
||||
# import packages for user interface:
|
||||
|
||||
from queue import Queue, Empty
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox
|
||||
@@ -18,11 +20,11 @@ from datetime import datetime
|
||||
|
||||
# import other project files:
|
||||
import src.globals as g
|
||||
import src.cage_func as func
|
||||
import src.csv_threading as csv
|
||||
import src.config_handling as config
|
||||
import src.csv_logging as log
|
||||
from src.utility import ui_print
|
||||
import src.helmholtz_cage_device as helmholtz_cage_device
|
||||
|
||||
# define font styles:
|
||||
HEADER_FONT = ("Arial", 13, "bold")
|
||||
@@ -245,30 +247,28 @@ class ManualMode(Frame):
|
||||
|
||||
# update the labels showing the min/max achievable values
|
||||
compensate = self.compensate.get() # read out if compensate field checkbox is checked (True or False)
|
||||
i = 0
|
||||
for var in self.max_value_vars: # go through the max value labels for each axis
|
||||
for i, var in enumerate(self.max_value_vars): # go through the max value labels for each axis
|
||||
if not compensate: # ambient field should not be compensated
|
||||
field = g.AXES[i].max_field * 1e6 # get max values from the axis object
|
||||
field = g.CAGE_DEVICE.axes[i].max_field * 1e6 # get max values from the axis object
|
||||
elif compensate: # ambient field should be compensated
|
||||
field = g.AXES[i].max_comp_field * 1e6
|
||||
field = g.CAGE_DEVICE.axes[i].max_comp_field * 1e6
|
||||
else: # this really should never happen
|
||||
field = [0, 0]
|
||||
ui_print("Unexpected value encountered: compensate =", compensate)
|
||||
messagebox.showerror("Unexpected Value!", ("Unexpected value encountered: compensate =", compensate))
|
||||
var.set("(%0.1f to %0.1f \u03BCT)" % (field[0], field[1])) # update the label text with the new values
|
||||
i += 1
|
||||
|
||||
def switch_to_current_mode(self): # called when switching to the input current mode
|
||||
self.compensate_checkbox.config(state=DISABLED) # disable the compensate ambient field checkbox
|
||||
|
||||
# update the labels showing the min/max achievable values
|
||||
i = 0
|
||||
for var in self.max_value_vars: # go through the max value labels for each axis
|
||||
var.set("(%0.2f to %0.2f A)" % (-g.AXES[i].max_amps, g.AXES[i].max_amps)) # update the label
|
||||
i += 1
|
||||
for i, var in enumerate(self.max_value_vars): # go through the max value labels for each axis
|
||||
# update the label
|
||||
var.set("(%0.2f to %0.2f A)" % (-g.CAGE_DEVICE.axes[i].max_amps, g.CAGE_DEVICE.axes[i].max_amps))
|
||||
|
||||
def reinitialize(self): # called on "Reinitialize!" button press
|
||||
func.setup_all() # reinitialize all PSUs and the Arduino
|
||||
# reinitialize all PSUs and the Arduino
|
||||
g.CAGE_DEVICE.reconnect_hardware()
|
||||
|
||||
# log change to the log file if user has selected event logging in the Configure Logging window
|
||||
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
|
||||
@@ -276,7 +276,7 @@ class ManualMode(Frame):
|
||||
logger.log_datapoint() # log data
|
||||
|
||||
def power_down(self): # called on "power down" button press
|
||||
func.power_down_all() # power down outputs on all PSUs and the Arduino
|
||||
g.CAGE_DEVICE.shutdown() # power down outputs on all PSUs and the Arduino
|
||||
|
||||
# log change to the log file if user has selected event logging in the Configure Logging window
|
||||
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
|
||||
@@ -297,7 +297,6 @@ class ManualMode(Frame):
|
||||
else: # no issues while reading entries (user entered correct format)
|
||||
function_to_call = self.modes[self.input_mode.get()][0] # get function of appropriate mode
|
||||
function_to_call(vector) # call function (self.execute_field() or self.execute_current())
|
||||
self.controller.StatusDisplay.update_labels() # update status display after change
|
||||
|
||||
# log change to the log file if user has selected event logging in the Configure Logging window
|
||||
logger = self.controller.pages[ConfigureLogging] # get object of logging configurator
|
||||
@@ -305,20 +304,34 @@ class ManualMode(Frame):
|
||||
logger.log_datapoint() # log data
|
||||
|
||||
def execute_field(self, vector): # convert magnetic field vector and send to test bench
|
||||
ui_print("Field executing:", vector, "\u03BCT")
|
||||
compensate = self.compensate.get() # read out if compensate ambient field checkbox is ticked
|
||||
if compensate: # ambient field should be compensated
|
||||
func.set_field(vector * 1e-6) # convert to Tesla and send to test bench
|
||||
elif not compensate: # ambient field should not be compensated
|
||||
func.set_field_simple(vector * 1e-6) # convert to Tesla and send to test bench
|
||||
else: # this really should never happen
|
||||
ui_print("Unexpected value encountered: compensate =", compensate)
|
||||
messagebox.showerror("Unexpected Value!", ("Unexpected value encountered: compensate =", compensate))
|
||||
ui_print("\nField executing:", vector, "\u03BCT")
|
||||
# Acquire a proxy to the helmholtz cage:
|
||||
# This can fail if already in use
|
||||
try:
|
||||
with g.CAGE_DEVICE as cage_dev:
|
||||
compensate = self.compensate.get() # read out if compensate ambient field checkbox is ticked
|
||||
if compensate: # ambient field should be compensated
|
||||
cage_dev.set_field_compensated(vector * 1e-6) # convert to Tesla and send to test bench
|
||||
pass
|
||||
elif not compensate: # ambient field should not be compensated
|
||||
cage_dev.set_field_raw(vector * 1e-6) # convert to Tesla and send to test bench
|
||||
pass
|
||||
else: # this really should never happen
|
||||
ui_print("Unexpected value encountered: compensate =", compensate)
|
||||
messagebox.showerror("Unexpected Value!", ("Unexpected value encountered: compensate =", compensate))
|
||||
except helmholtz_cage_device.DeviceBusy:
|
||||
ui_print("Error: Could not acquire control. Is the HW already in use?")
|
||||
|
||||
@staticmethod
|
||||
def execute_current(vector): # send current vector to the test bench
|
||||
ui_print("Current executing:", vector, "A")
|
||||
func.set_current_vec(vector) # command test bench
|
||||
ui_print("\nCurrent executing:", vector, "A")
|
||||
with g.CAGE_DEVICE as cage_dev:
|
||||
# This can fail if already in use
|
||||
if cage_dev is None:
|
||||
ui_print("Error: Could not acquire control. Is the HW already in use?")
|
||||
return
|
||||
|
||||
cage_dev.set_signed_currents(vector) # command test bench
|
||||
|
||||
|
||||
class ExecuteCSVMode(Frame):
|
||||
@@ -552,8 +565,8 @@ class HardwareConfiguration(Frame):
|
||||
# text for the description labels:
|
||||
entry_texts = ["XY PSU Serial Port:", "Z PSU Serial Port:"]
|
||||
# create variables to store the port names and set to current names
|
||||
self.XY_port = StringVar(value=g.XY_PORT)
|
||||
self.Z_port = StringVar(value=g.Z_PORT)
|
||||
self.XY_port = StringVar(value=g.CAGE_DEVICE.com_port_psu1)
|
||||
self.Z_port = StringVar(value=g.CAGE_DEVICE.com_port_psu2)
|
||||
port_vars = [self.XY_port, self.Z_port] # list to store both port variables
|
||||
row = 0
|
||||
for text in entry_texts: # do this for both ports
|
||||
@@ -596,7 +609,7 @@ class HardwareConfiguration(Frame):
|
||||
|
||||
# Fill in header (axis names):
|
||||
col = 1
|
||||
for text in ["X-Axis", "Y-Axis", "Z-Axis"]:
|
||||
for text in g.AXIS_NAMES:
|
||||
label = Label(value_frame, text=text, font=SUB_HEADER_FONT)
|
||||
label.grid(row=0, column=col, sticky="ew")
|
||||
col += 1
|
||||
@@ -649,16 +662,16 @@ class HardwareConfiguration(Frame):
|
||||
def restore_defaults(self): # restore default settings
|
||||
config.reset_config_to_default() # overwrite config file with default
|
||||
ui_print("\nReinitializing devices...")
|
||||
func.setup_all() # setup everything with the defaults
|
||||
g.CAGE_DEVICE.reconnect_hardware() # setup everything with the defaults
|
||||
self.update_fields() # update fields in config window
|
||||
|
||||
def update_fields(self): # set current values for all entry variables from config file
|
||||
# set values for PSU serial ports:
|
||||
self.XY_port.set(g.XY_PORT)
|
||||
self.Z_port.set(g.Z_PORT)
|
||||
self.XY_port.set(g.CAGE_DEVICE.com_port_psu1)
|
||||
self.Z_port.set(g.CAGE_DEVICE.com_port_psu2)
|
||||
|
||||
for key in self.entries.keys(): # go through the main value table
|
||||
for i in [0, 1, 2]: # go through all three axes
|
||||
for i in range(3): # go through all three axes
|
||||
# get value from config file:
|
||||
value = config.read_from_config(g.AXIS_NAMES[i], self.entries[key][3], config.CONFIG_OBJECT)
|
||||
self.entries[key][0][i].set(value) # set initial value on the entry field variable
|
||||
@@ -667,7 +680,7 @@ class HardwareConfiguration(Frame):
|
||||
self.entries[key][0][i].set(round(type_value * factor, 3)) # set value with correct unit conversion
|
||||
|
||||
# check if value is within safe limits:
|
||||
value_check = func.value_in_limits(g.AXIS_NAMES[i], self.entries[key][3], value)
|
||||
value_check = helmholtz_cage_device.value_in_limits(g.AXIS_NAMES[i], self.entries[key][3], value)
|
||||
if value_check == 'OK': # value is acceptable
|
||||
self.fields[key][i].config(background="White") # set colour of this entry to white
|
||||
else: # value exceeds limits
|
||||
@@ -676,8 +689,8 @@ class HardwareConfiguration(Frame):
|
||||
def write_values(self): # update config object with user inputs into entry fields and reinitialize devices
|
||||
|
||||
# set serial ports for PSUs:
|
||||
config.edit_config("PORTS", "xy_port", self.XY_port.get())
|
||||
config.edit_config("PORTS", "z_port", self.Z_port.get())
|
||||
config.edit_config("Supplies", "xy_port", self.XY_port.get())
|
||||
config.edit_config("Supplies", "z_port", self.Z_port.get())
|
||||
|
||||
# set numeric values for all axes
|
||||
for key in self.entries.keys(): # go through rows of entry table
|
||||
@@ -695,7 +708,7 @@ class HardwareConfiguration(Frame):
|
||||
|
||||
# Check if value is within safe limits
|
||||
config_key = self.entries[key][3] # get handle by which value is indexed in config file
|
||||
value_ok = func.value_in_limits(g.AXIS_NAMES[i], config_key, value) # perform value check
|
||||
value_ok = helmholtz_cage_device.value_in_limits(g.AXIS_NAMES[i], config_key, value) # perform value check
|
||||
|
||||
if value_ok == 'OK': # value is within safe limits
|
||||
config.edit_config(g.AXIS_NAMES[i], config_key, value) # write new value to config file
|
||||
@@ -723,7 +736,7 @@ class HardwareConfiguration(Frame):
|
||||
message = "Unknown case for value limits check, 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)
|
||||
answer = messagebox.askquestion("Value out of bounds", message)
|
||||
# answer 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
|
||||
@@ -733,7 +746,7 @@ class HardwareConfiguration(Frame):
|
||||
def implement(self): # "Update and Reinitialize" button, update config with new values and reinitialize devices
|
||||
self.write_values() # write current values from entry fields to config object
|
||||
ui_print("\nReinitializing devices...")
|
||||
func.setup_all() # reinitialize devices and program with new values
|
||||
g.CAGE_DEVICE.reconnect_hardware() # reinitialize devices and program with new values
|
||||
self.update_fields() # update entry fields to show new values
|
||||
|
||||
def load_config(self): # load configuration from some config file
|
||||
@@ -747,7 +760,7 @@ class HardwareConfiguration(Frame):
|
||||
config.check_config(config.CONFIG_OBJECT) # check and display warnings if values are out of bounds
|
||||
|
||||
ui_print("\nReinitializing devices...")
|
||||
func.setup_all() # reinitialize devices and program with new values
|
||||
g.CAGE_DEVICE.reconnect_hardware() # 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
|
||||
ui_print("No file selected, could not load config.")
|
||||
@@ -768,14 +781,14 @@ class HardwareConfiguration(Frame):
|
||||
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
|
||||
ui_print("\nReinitializing devices...")
|
||||
func.setup_all() # reinitialize devices and program with new values
|
||||
g.CAGE_DEVICE.reconnect_hardware() # reinitialize devices and program with new values
|
||||
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() # write current entry field values to the config object
|
||||
config.write_config_to_file(config.CONFIG_OBJECT) # write contents of config object to file
|
||||
ui_print("\nReinitializing devices...")
|
||||
func.setup_all() # reinitialize devices and program with new values
|
||||
g.CAGE_DEVICE.reconnect_hardware() # reinitialize devices and program with new values
|
||||
self.update_fields() # update entry fields to show values as they are in the config
|
||||
|
||||
|
||||
@@ -1026,6 +1039,10 @@ class StatusDisplay(Frame):
|
||||
# noinspection PyUnusedLocal
|
||||
def __init__(self, parent, controller):
|
||||
Frame.__init__(self, parent, relief=SUNKEN, bd=1)
|
||||
self.controller = controller
|
||||
|
||||
# Queue to store status updates which arrive from another thread by callback
|
||||
self.update_label_queue = Queue()
|
||||
|
||||
# configure Tkinter grid
|
||||
self.grid_rowconfigure(ALL, weight=1)
|
||||
@@ -1075,38 +1092,63 @@ class StatusDisplay(Frame):
|
||||
LabelCol[row].grid(row=row + rowCounter, column=col, sticky="nsew") # place label
|
||||
col += 1
|
||||
|
||||
self.update_labels() # fill in all values
|
||||
# Register callback to populate new data:
|
||||
g.CAGE_DEVICE.subscribe_status_updates(self.enqueue_new_status)
|
||||
# Starts polling loop for status display
|
||||
self.update_label_poll_method()
|
||||
|
||||
def continuous_label_update(self, controller, interval): # update display values in regular intervals (ms)
|
||||
if not g.exitFlag: # application ist still running
|
||||
self.update_labels() # update the label values
|
||||
# call function again after time interval:
|
||||
controller.after(interval, lambda: self.continuous_label_update(controller, interval))
|
||||
def update_labels(self, status): # update all values in the status display
|
||||
for i in range(3): # go through all three axes
|
||||
# update all label variables with the new values:
|
||||
axis = status['axes'][i]
|
||||
if axis['connected']:
|
||||
# Deal with variables that are dependent on the current hardware state
|
||||
active = "True" if axis['active'] else "False"
|
||||
remote_active = "True" if axis['active'] else "False"
|
||||
voltage_setpoint = "%0.3f V" % axis['voltage_setpoint']
|
||||
voltage = "%0.3f V" % axis['voltage']
|
||||
current_setpoint = "%0.3f A" % axis['current_setpoint']
|
||||
current = "%0.3f A" % axis['current']
|
||||
polarity = axis['polarity']
|
||||
else:
|
||||
active = "N/A"
|
||||
remote_active = "N/A"
|
||||
voltage_setpoint = "N/A"
|
||||
voltage = "N/A"
|
||||
current_setpoint = "N/A"
|
||||
current = "N/A"
|
||||
polarity = "N/A"
|
||||
psu_connected = "Connected" if axis['connected'] else "Not Connected"
|
||||
arduino_connected = "Connected" if status['arduino_connected'] else "Not Connected"
|
||||
|
||||
def update_labels(self): # update all values in the status display
|
||||
g.ARDUINO.update_status_info() # get latest status info from arduino
|
||||
i = 0
|
||||
for axis in g.AXES: # go through all three axes
|
||||
if axis.device is not None: # there is a PSU for this axis connected
|
||||
axis.update_status_info() # get latest status info from PSU (Takes very long...)
|
||||
# update all label variables with current values:
|
||||
# ToDo (optional): Use the central dictionary currently defined in csv_logging.py for this
|
||||
self.label_dict["PSU Serial Port:"][i].set(g.PORTS[i])
|
||||
self.label_dict["PSU Channel:"][i].set(axis.channel)
|
||||
self.label_dict["PSU Status:"][i].set(axis.connected)
|
||||
self.label_dict["Arduino Status:"][i].set(g.ARDUINO.connected) # ToDo (optional): make this multicolumn
|
||||
self.label_dict["Output:"][i].set(axis.output_active)
|
||||
self.label_dict["Remote Control:"][i].set(axis.remote_ctrl_active)
|
||||
self.label_dict["Voltage Setpoint:"][i].set("%0.3f V" % axis.voltage_setpoint)
|
||||
self.label_dict["Actual Voltage:"][i].set("%0.3f V" % axis.voltage)
|
||||
self.label_dict["Current Setpoint:"][i].set("%0.3f A" % axis.current_setpoint)
|
||||
self.label_dict["Actual Current:"][i].set("%0.3f A" % axis.current)
|
||||
self.label_dict["Target Field:"][i].set("%0.3f \u03BCT" % (axis.target_field * 1e6))
|
||||
self.label_dict["Trgt. Field Raw:"][i].set("%0.3f \u03BCT" % (axis.target_field_comp * 1e6))
|
||||
self.label_dict["Target Current:"][i].set("%0.3f A" % axis.target_current)
|
||||
self.label_dict["Inverted:"][i].set(axis.polarity_switched)
|
||||
i += 1
|
||||
self.label_dict["PSU Serial Port:"][i].set(axis['port'])
|
||||
self.label_dict["PSU Channel:"][i].set(axis['channel'])
|
||||
self.label_dict["PSU Status:"][i].set(psu_connected)
|
||||
self.label_dict["Arduino Status:"][i].set(arduino_connected) # ToDo (optional): make this multicolumn
|
||||
self.label_dict["Output:"][i].set(active)
|
||||
self.label_dict["Remote Control:"][i].set(remote_active)
|
||||
self.label_dict["Voltage Setpoint:"][i].set(voltage_setpoint)
|
||||
self.label_dict["Actual Voltage:"][i].set(voltage)
|
||||
self.label_dict["Current Setpoint:"][i].set(current_setpoint)
|
||||
self.label_dict["Actual Current:"][i].set(current)
|
||||
self.label_dict["Target Field:"][i].set("%0.3f \u03BCT" % (axis['target_field'] * 1e6))
|
||||
self.label_dict["Trgt. Field Raw:"][i].set("%0.3f \u03BCT" % (axis['target_field_raw'] * 1e6))
|
||||
self.label_dict["Target Current:"][i].set("%0.3f A" % axis['target_current'])
|
||||
self.label_dict["Inverted:"][i].set(polarity)
|
||||
|
||||
def enqueue_new_status(self, status):
|
||||
"""Runs in caller thread and places status onto queue to display when polled in update_label_poll_method"""
|
||||
self.update_label_queue.put(status)
|
||||
|
||||
def update_label_poll_method(self):
|
||||
"""Infinite loop to poll for status updates to display"""
|
||||
try:
|
||||
new_status = self.update_label_queue.get(block=False) # Blocks until new data is available.
|
||||
self.update_labels(new_status)
|
||||
except Empty:
|
||||
pass
|
||||
self.controller.after(200, self.update_label_poll_method)
|
||||
|
||||
class OutputConsole(Frame):
|
||||
# console to print information to user in, similar to standard python output
|
||||
|
||||
Reference in New Issue
Block a user