forked from zietzm/Helmholtz_Test_Bench
Merge pull request 'Development_zietz' (#1) from Development_zietz into master
Reviewed-on: zietzm/Magnetfeldteststand#1
This commit is contained in:
@@ -98,3 +98,8 @@ ENV/
|
||||
.idea/misc.xml
|
||||
.idea/Python-PS2000B.iml
|
||||
.idea/Python-PS2000B.iml
|
||||
.idea/Python-PS2000B.iml
|
||||
.idea/misc.xml
|
||||
config.ini
|
||||
*.ini
|
||||
log.csv
|
||||
|
||||
Generated
+1
-1
@@ -2,7 +2,7 @@
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (MagnetEnv)" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (venv)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
|
||||
Generated
+1
-1
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (MagnetEnv)" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (venv)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
@@ -1,3 +1,4 @@
|
||||
# This file enables control of a connected Arduino microcontroller.
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
import itertools
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import time
|
||||
|
||||
from Arduino import Arduino
|
||||
|
||||
print("Searching for Arduino...")
|
||||
board = Arduino()
|
||||
print("Arduino found.")
|
||||
board.pinMode(15, "Output")
|
||||
board.pinMode(16, "Output")
|
||||
board.pinMode(17, "Output")
|
||||
board.digitalWrite(15, "LOW")
|
||||
board.digitalWrite(16, "LOW")
|
||||
board.digitalWrite(17, "LOW")
|
||||
|
||||
|
||||
i = 0
|
||||
while i <= 1:
|
||||
print("running: ", i)
|
||||
for var in [15,16,17]:
|
||||
board.digitalWrite(var, "HIGH")
|
||||
time.sleep(0.5)
|
||||
time.sleep(5)
|
||||
for var in [15,16,17]:
|
||||
board.digitalWrite(var, "LOW")
|
||||
time.sleep(0.5)
|
||||
time.sleep(2)
|
||||
i = i + 1
|
||||
|
||||
board.close()
|
||||
@@ -1,72 +0,0 @@
|
||||
# import platform
|
||||
import time as t
|
||||
import numpy as np
|
||||
import settings as g
|
||||
import cage_func as func
|
||||
from pyps2000b import PS2000B
|
||||
|
||||
# User Inputs/Configuration----------------------------------
|
||||
Test1 = 0
|
||||
Test2 = 1
|
||||
Test3 = 0
|
||||
Test4 = 0
|
||||
|
||||
# Constants:
|
||||
g.COIL_CONST = np.array([38.6, 38.45, 37.9]) * 1e-9 # Coil constants [x,y,z] in T/A
|
||||
g.AMBIENT_FIELD = np.array([80]) * 1e-6 # ambient magnetic field in measurement area, to be cancelled out
|
||||
g.RESISTANCES = np.array([3.9, 3.9, 1]) # resistance of [x,y,z] circuits
|
||||
g.MAX_WATTS = np.array([8, 8, 0]) # max. allowed power for [x,y,z] circuits
|
||||
|
||||
# COM-Ports for power supply units:
|
||||
XY_PORT = "COM7"
|
||||
g.XY_DEVICE = PS2000B.PS2000B(XY_PORT)
|
||||
|
||||
g.MAX_AMPS = np.sqrt(g.MAX_WATTS / g.RESISTANCES)
|
||||
#print(g.MAX_AMPS)
|
||||
|
||||
|
||||
'''def print_status():
|
||||
print("Output 1:")
|
||||
func.print_status(g.X_AXIS)
|
||||
print("Output 2:")
|
||||
func.print_status(g.Y_AXIS)'''
|
||||
|
||||
|
||||
func.set_to_zero(g.XY_DEVICE)
|
||||
#print_status()
|
||||
t.sleep(3)
|
||||
|
||||
if Test1 == 1:
|
||||
print("setting")
|
||||
g.XY_DEVICE.voltage1 = 5
|
||||
g.XY_DEVICE.current1 = 1
|
||||
g.XY_DEVICE.enable_all()
|
||||
t.sleep(1)
|
||||
#print_status()
|
||||
t.sleep(5)
|
||||
print("setting to zero")
|
||||
func.set_to_zero(g.XY_DEVICE)
|
||||
|
||||
if Test2 == 1:
|
||||
print("setting current")
|
||||
g.XY_DEVICE.set_current(0.2, 0)
|
||||
g.XY_DEVICE.set_voltage(5, 0)
|
||||
g.XY_DEVICE.enable_all()
|
||||
t.sleep(1)
|
||||
#print_status()
|
||||
t.sleep(5)
|
||||
func.set_to_zero(g.XY_DEVICE)
|
||||
|
||||
if Test4 == 1:
|
||||
func.set_axis_current(g.XY_DEVICE, 0.2)
|
||||
t.sleep(1)
|
||||
print_status()
|
||||
t.sleep(10)
|
||||
func.set_to_zero(g.XY_DEVICE)
|
||||
t.sleep(1)
|
||||
|
||||
#print_status()
|
||||
|
||||
g.XY_DEVICE.disable_all()
|
||||
|
||||
#print_status()
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
Time (s);xField (T);yField (T);zField (T)
|
||||
0;0,0000145;0,0000255;0,0000195
|
||||
0,1;-0,000015;0,000025;0,00002
|
||||
0,2;-0,0000155;-0,0000245;0,0000205
|
||||
0,3;-0,000016;-0,000024;-0,000021
|
||||
0,4;0,0000165;-0,0000235;-0,0000215
|
||||
0,5;0,000017;0,000023;-0,000022
|
||||
0,6;0,0000175;0,0000225;0,0000225
|
||||
0,7;-0,000018;-0,000022;0,000023
|
||||
0,8;0,0000185;-0,0000215;-0,0000235
|
||||
0,9;-0,000019;0,000021;-0,000024
|
||||
1;-0,0000195;-0,0000205;-0,0000245
|
||||
1,1;0,00002;0,00002;0,000025
|
||||
1,2;-0,0000205;-0,0000195;-0,0000245
|
||||
1,3;0,000021;0,000019;0,000024
|
||||
|
@@ -0,0 +1,15 @@
|
||||
Time (s);xField (T);yField (T);zField (T)
|
||||
0;0,0000145;0,0000255;0,0000195
|
||||
1;-0,000015;0,000025;0,00002
|
||||
2;-0,0000155;-0,0000245;0,0000205
|
||||
3;-0,000016;-0,000024;-0,000021
|
||||
4;0,0000165;-0,0000235;-0,0000215
|
||||
5;0,000017;0,000023;-0,000022
|
||||
6;0,0000175;0,0000225;0,0000225
|
||||
7;-0,000018;-0,000022;0,000023
|
||||
8;0,0000185;-0,0000215;-0,0000235
|
||||
9;-0,000019;0,000021;-0,000024
|
||||
10;-0,0000195;-0,0000205;-0,0000245
|
||||
11;0,00002;0,00002;0,000025
|
||||
12;-0,0000205;-0,0000195;-0,0000245
|
||||
13;0,000021;0,000019;0,000024
|
||||
|
@@ -0,0 +1,12 @@
|
||||
Time (s);xField (T);yField (T);zField (T);
|
||||
0;0,00015;-0,00015;0,00002;150
|
||||
1;0,00017;-0,00017;0,00002;170
|
||||
2;0,00018;-0,00018;0,00002;180
|
||||
3;0,00019;-0,00019;0,00002;190
|
||||
4;0,0002;-0,0002;0,00002;200
|
||||
5;0,00021;0,00021;0,00002;210
|
||||
6;0,00022;-0,00022;0,00002;220
|
||||
7;0,0002;-0,0002;0,00002;200
|
||||
8;0,00018;-0,00018;0,00002;180
|
||||
9;0,00005;-0,00005;0,00002;50
|
||||
10;-0,00004;0,00004;0,00002;-40
|
||||
|
+1004
-135
File diff suppressed because it is too large
Load Diff
+321
-210
@@ -1,34 +1,48 @@
|
||||
from pyps2000b import PS2000B
|
||||
from Arduino import Arduino
|
||||
import settings as g
|
||||
import pandas
|
||||
import time
|
||||
# This file contains all classes and functions directly related to the operation of the helmholtz test stand.
|
||||
# The two main classes are Axis and ArduinoCtrl, see their definitions for details.
|
||||
|
||||
# import packages:
|
||||
import numpy as np
|
||||
import serial
|
||||
import traceback
|
||||
from tkinter import messagebox
|
||||
|
||||
# import other project files
|
||||
from User_Interface import ui_print
|
||||
from pyps2000b import PS2000B
|
||||
from Arduino import Arduino
|
||||
import config_handling as config
|
||||
import globals as g
|
||||
|
||||
|
||||
class Axis:
|
||||
# Main class representing an axis (x,y,z) of the test stand
|
||||
# contains static and dynamic status information about this axis and methods to control it
|
||||
|
||||
def __init__(self, index, device, PSU_channel, arduino_pin):
|
||||
# static information
|
||||
self.index = index
|
||||
self.device = device # power supply object (PS2000B class)
|
||||
self.channel = PSU_channel # power supply unit channel (1 or 2)
|
||||
self.index = index # index of this axis, 0->X, 1->Y, 2->Z
|
||||
self.device = device # power supply object for this axis (PS2000B class)
|
||||
self.channel = PSU_channel # power supply unit channel (0 or 1)
|
||||
self.ardPin = arduino_pin # output pin on the arduino for switching polarity on this axis
|
||||
|
||||
self.name = g.AXIS_NAMES[index]
|
||||
self.port = g.ports[index]
|
||||
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
|
||||
|
||||
self.resistance = 0 # [Ohm]
|
||||
# maximum allowable values to pass through this circuit
|
||||
self.max_watts = 0 # [W]
|
||||
self.max_amps = 0 # [A]
|
||||
self.max_volts = 0 # [V]
|
||||
# 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))
|
||||
|
||||
self.coil_constant = 0 # coil constant of this axis [T/A]
|
||||
self.ambient_field = 0 # ambient field in this axis [T]
|
||||
# ToDo: get this info from settings file
|
||||
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))
|
||||
|
||||
# dynamic information
|
||||
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]
|
||||
|
||||
# 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?
|
||||
@@ -46,265 +60,362 @@ 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"
|
||||
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"
|
||||
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):
|
||||
# print("Connection Error with %s PSU on %s" % (self.name, self.port))
|
||||
|
||||
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
|
||||
|
||||
if g.ARDUINO.connected == "Connected":
|
||||
try:
|
||||
if g.ARDUINO.digitalRead(self.ardPin): # ToDo: Test if this actually works
|
||||
self.polarity_switched = "True"
|
||||
else: self.polarity_switched = "False"
|
||||
except Exception:
|
||||
print("Error with Arduino")
|
||||
self.polarity_switched = "Unknown"
|
||||
g.ARDUINO.connected = "Connection Error"
|
||||
else:
|
||||
g.ARDUINO.connected = "Connected"
|
||||
|
||||
def print_status(self): # axis = axis control variable, stored in settings.py
|
||||
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 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
|
||||
self.target_current = 0
|
||||
self.target_field = 0
|
||||
self.target_field_comp = 0
|
||||
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")
|
||||
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
|
||||
|
||||
def set_signed_current(self, value): # sets current with correct polarity on this axis
|
||||
device = self.device
|
||||
channel = self.channel
|
||||
ardPin = self.ardPin
|
||||
# print("Attempting to set current", value, "A")
|
||||
self.target_current = value
|
||||
if self.connected == "Connected":
|
||||
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
|
||||
pass # g.ARDUINO.digitalWrite(ardPin, "LOW") ToDo: reactivate and tie to arduino
|
||||
elif value < 0:
|
||||
pass # g.ARDUINO.digitalWrite(ardPin, "HIGH") ToDo: reactivate
|
||||
else:
|
||||
raise Exception("This should be impossible.")
|
||||
maxVoltage = min(max(1.1 * self.max_amps * self.resistance, 8), self.max_volts) # limit voltage#
|
||||
# print("sending values to device: U =", maxVoltage, "I =", abs(value))
|
||||
device.set_current(abs(value), channel)
|
||||
device.set_voltage(maxVoltage, channel)
|
||||
device.enable_output(channel)
|
||||
else:
|
||||
print(self.name, "not connected, can't set current.")
|
||||
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, this is the primary way to control the test stand
|
||||
|
||||
# ui_print("Attempting to set current", value, "A")
|
||||
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 on %s. Tried %0.2fA, max. %0.2fA allowed"
|
||||
% (self.name, value, self.max_amps))
|
||||
|
||||
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
|
||||
|
||||
# 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, pins):
|
||||
self.connected = "Unknown"
|
||||
self.pins = pins
|
||||
print("Connecting to Arduino...")
|
||||
try:
|
||||
Arduino.__init__(self) # search for connected arduino and connect
|
||||
def __init__(self):
|
||||
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: # 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:
|
||||
g.ARDUINO.pinMode(pin, "Output")
|
||||
g.ARDUINO.digitalWrite(pin, "LOW")
|
||||
except Exception:
|
||||
print("Connection to Arduino failed.")
|
||||
self.pinMode(pin, "Output")
|
||||
self.digitalWrite(pin, "LOW")
|
||||
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:
|
||||
g.arduino_connected = "Connected"
|
||||
print("Arduino ready.")
|
||||
else: # connection was successfully established
|
||||
self.connected = "Connected"
|
||||
ui_print("Arduino ready.")
|
||||
|
||||
def safe(self): # sets output pins to low and closes serial connection
|
||||
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: # set polarity switch attributes in axis objects to "Unknown"
|
||||
axis.polarity_switched = "Unknown"
|
||||
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 relay switching pins to low to depower most of the electronics box
|
||||
for pin in self.pins:
|
||||
g.ARDUINO.digitalWrite(pin, "LOW")
|
||||
self.digitalWrite(pin, "LOW")
|
||||
|
||||
|
||||
def setup_axes(): # creates device objects for all PSUs and sets their values
|
||||
g.AXES = []
|
||||
def value_in_limits(axis, key, value): # Check if value is within safe limits (set in globals.py)
|
||||
# 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
|
||||
|
||||
print("Connecting to XY Device on %s..." % g.XY_PORT)
|
||||
try:
|
||||
if g.XY_DEVICE is not None:
|
||||
print("closing serial connection on XY device")
|
||||
if float(value) > float(max_value): # value is too high
|
||||
return 'HIGH'
|
||||
elif float(value) < float(min_value): # value is too low
|
||||
return 'LOW'
|
||||
else: # value is within limits
|
||||
return 'OK'
|
||||
|
||||
|
||||
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
|
||||
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 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)
|
||||
|
||||
# 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] # 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: # 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
|
||||
print("Connection established.")
|
||||
g.X_AXIS = Axis(0, g.XY_DEVICE, 0, g.ARDUINO.pins[0]) # create axis objects
|
||||
ui_print("Connection established.")
|
||||
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])
|
||||
print("XY Device not connected or incorrect port set.")
|
||||
ui_print("XY Device not connected or incorrect port set.")
|
||||
|
||||
print("Connecting to Z Device on %s..." % g.XY_PORT)
|
||||
# same for the Z axis
|
||||
ui_print("Connecting to Z Device on %s..." % g.Z_PORT)
|
||||
try:
|
||||
if g.Z_DEVICE is not None:
|
||||
ui_print("Closing serial connection on Z device")
|
||||
g.Z_DEVICE.serial.close()
|
||||
g.Z_DEVICE = None
|
||||
g.Z_DEVICE = PS2000B.PS2000B(g.Z_PORT)
|
||||
print("Connection established.")
|
||||
ui_print("Connection established.")
|
||||
g.Z_AXIS = Axis(2, g.Z_DEVICE, 0, g.ARDUINO.pins[2])
|
||||
except serial.serialutil.SerialException:
|
||||
g.Z_AXIS = Axis(2, None, 0, g.ARDUINO.pins[2])
|
||||
print("Z Device not connected or incorrect port set.")
|
||||
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)
|
||||
|
||||
i = 0
|
||||
for axis in g.AXES: # ToDo: move to axis init
|
||||
axis.resistance = g.RESISTANCES[i]
|
||||
axis.max_watts = g.MAX_WATTS[i]
|
||||
axis.max_amps = np.sqrt(axis.max_watts / axis.resistance)
|
||||
print(axis.name, "max Current:", axis.max_amps)
|
||||
axis.max_volts = g.MAX_VOLTS[i]
|
||||
|
||||
axis.coil_constant = g.COIL_CONST[i]
|
||||
axis.ambient_field = g.AMBIENT_FIELD[i]
|
||||
i = i+1
|
||||
ui_print("") # 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()
|
||||
|
||||
|
||||
def deactivate_all(): # disables remote control and output on all PSUs and channels
|
||||
# ToDo: add check if device is connected
|
||||
try:
|
||||
g.XY_DEVICE.disable_all()
|
||||
except BaseException:
|
||||
print("XY PSU deactivation unsuccessful.")
|
||||
else:
|
||||
print("XY PSU deactivated.")
|
||||
try:
|
||||
g.Z_DEVICE.disable_all()
|
||||
except BaseException:
|
||||
print("Z PSU deactivation unsuccessful.")
|
||||
else:
|
||||
print("Z PSU deactivated.")
|
||||
|
||||
|
||||
def print_status_3():
|
||||
print("X-Axis:")
|
||||
g.X_AXIS.print_status()
|
||||
print("Y-Axis:")
|
||||
g.Y_AXIS.print_status()
|
||||
print("Z-Axis:")
|
||||
g.Z_AXIS.print_status()
|
||||
|
||||
|
||||
def set_to_zero(device): # sets voltages and currents to 0
|
||||
def set_to_zero(device): # sets voltages and currents to 0 on all channels of a specific PSU
|
||||
device.voltage1 = 0
|
||||
device.current1 = 0
|
||||
device.voltage2 = 0
|
||||
device.current2 = 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()
|
||||
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
|
||||
# ToDo: better messages, check if things are connected first
|
||||
print("\nAttempting to safely shut down all devices. Check equipment to confirm.")
|
||||
try: set_to_zero(g.XY_DEVICE)
|
||||
except:
|
||||
print("XY PSU set to 0 unsuccessful.")
|
||||
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."
|
||||
|
||||
# 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." # 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)
|
||||
g.Z_DEVICE.disable_all()
|
||||
except BaseException as e:
|
||||
ui_print("Error while deactivating Z PSU:", e)
|
||||
message += "\nError while deactivating Z PSU: %s" % e
|
||||
else:
|
||||
ui_print("Z PSU deactivated.")
|
||||
message += "\nZ PSU deactivated."
|
||||
else:
|
||||
print("XY PSU currents and voltages set to 0.")
|
||||
try: set_to_zero(g.Z_DEVICE)
|
||||
except:
|
||||
print("Z PSU set to 0 unsuccessful.")
|
||||
else:
|
||||
print("Z PSU currents and voltages set to 0.")
|
||||
deactivate_all()
|
||||
try: g.ARDUINO.safe()
|
||||
except:
|
||||
print("Arduino safing unsuccessful.")
|
||||
# else: # commented out bc this throws no exception, even when arduino is not connected
|
||||
# print("Arduino pins set to LOW.") # ToDo: figure out error handling for this
|
||||
try: g.ARDUINO.close()
|
||||
except:
|
||||
print("Closing Arduino connection failed.")
|
||||
else:
|
||||
print("Serial connection to Arduino closed.")
|
||||
ui_print("Z PSU not connected, can't deactivate.")
|
||||
message += "\nZ PSU not connected, can't deactivate."
|
||||
|
||||
# Shut down Arduino:
|
||||
try:
|
||||
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 # 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() # 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: # 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: # no problems, connection was successfully closed
|
||||
ui_print("Serial connection to Arduino closed.")
|
||||
message += "\nSerial connection to Arduino closed."
|
||||
|
||||
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]:
|
||||
g.AXES[i].set_field_simple(vector[i])
|
||||
try:
|
||||
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]:
|
||||
g.AXES[i].set_field(vector[i])
|
||||
try:
|
||||
g.AXES[i].set_field(vector[i])
|
||||
except ValueError as e:
|
||||
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:
|
||||
axis.set_signed_current(vector[i])
|
||||
i = i + 1
|
||||
try:
|
||||
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) # print out the error message
|
||||
i += 1
|
||||
|
||||
|
||||
def execute_csv(filepath, printing=0): # runs through csv file containing times and desired field vectors
|
||||
# csv format: time (s); xField (T); yField (T); zField (T)
|
||||
# decimal commas
|
||||
print("Reading File:", filepath)
|
||||
file = pandas.read_csv(filepath, sep=';', decimal=',', header=0) # read csv file
|
||||
array = file.to_numpy() # convert csv to array
|
||||
t_zero = time.time()
|
||||
t_ref = t_zero
|
||||
i = 0
|
||||
print("Starting Execution...")
|
||||
while i < len(array):
|
||||
t = time.time() - t_zero
|
||||
if t >= array[i, 0]:
|
||||
field_vec = array[i, 1:4]
|
||||
print("t = %0.2f s, target field vector = " % (array[i, 0]), field_vec)
|
||||
set_field(field_vec)
|
||||
i = i + 1
|
||||
if t - t_ref >= 1 and printing == 1: # print status every second
|
||||
print_status_3()
|
||||
t_ref = t
|
||||
print("File executed, powering down channels.")
|
||||
power_down_all() # set currents and voltages to 0, set arduino pins to low
|
||||
def devices_ok(xy_off=False, z_off=False, arduino_off=False):
|
||||
# check if all devices are connected, return True if yes
|
||||
# checks for individual devices can be disabled by parameters above (default not disabled)
|
||||
try: # handle errors while checking connections
|
||||
if not xy_off: # if check for this device is not disabled
|
||||
if g.XY_DEVICE is not None: # has the handle for this device been set?
|
||||
g.X_AXIS.update_status_info() # update info --> this actually communicates with the device
|
||||
if g.X_AXIS.connected != "Connected": # if not connected
|
||||
return False # return and exit function
|
||||
else: # if handle has not been set the device is inactive --> not ok
|
||||
return False
|
||||
if not z_off: # same as above
|
||||
if g.Z_DEVICE is not None:
|
||||
g.Z_AXIS.update_status_info()
|
||||
if g.Z_AXIS.connected != "Connected":
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
if not arduino_off: # check not disabled
|
||||
g.ARDUINO.update_status_info() # update status info --> attempts communication
|
||||
if g.ARDUINO.connected != "Connected":
|
||||
return False
|
||||
except Exception as e: # if an error is encountered while checking the devices
|
||||
messagebox.showerror("Error!", "Error while checking devices: \n%s" % e) # show error pop-up
|
||||
return False # clearly something is not ok
|
||||
else: # if nothing has triggered so far all devices are ok --> return True
|
||||
return True
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
# This file contains functions and variables related to reading and writing configuration files.
|
||||
# The configparser module is used for processing. Config files are of type .ini
|
||||
|
||||
# import packages:
|
||||
from configparser import ConfigParser
|
||||
from tkinter import messagebox
|
||||
|
||||
# import other project files:
|
||||
import globals as g
|
||||
import cage_func as func
|
||||
# noinspection PyPep8Naming
|
||||
import User_Interface as ui
|
||||
|
||||
global CONFIG_FILE # string storing the path of the used config file
|
||||
global CONFIG_OBJECT # object of type ConfigParser(), storing all configuration information
|
||||
# CONFIG_OBJECT is what is mostly read/written by the program
|
||||
# CONFIG_FILE is only used to export/import to/from a file
|
||||
|
||||
|
||||
def get_config_from_file(file): # read a config file to a config_object
|
||||
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 a specific value from a 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: # a section or key was used, that does not exist
|
||||
ui.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 a specific value in the CONFIG_OBJECT
|
||||
# section: Section of the config, e.g. "X-Axis" or "PORTS"
|
||||
# key: name of the value in the section, e.g. max_amps
|
||||
# value: new value to be written into the config
|
||||
# override: Bool to allow user to force writing a value into the config, even if it exceeds the safe limit
|
||||
|
||||
global CONFIG_OBJECT # get the global config object to edit it
|
||||
|
||||
# ToDo (optional): add check for data types, e.g. int for arduino ports
|
||||
# Check if value to write is within acceptable limits (set in dictionary in globals.py):
|
||||
try:
|
||||
value_ok = 'OK'
|
||||
if section in g.AXIS_NAMES and not override: # only check values in axis sections and not if check overridden
|
||||
value_ok = func.value_in_limits(section, key, value) # check if value is ok, too high or too low
|
||||
|
||||
if value_ok == 'HIGH': # value is too high
|
||||
max_value = g.default_arrays[key][1][g.AXIS_NAMES.index(section)] # get max value for message printing
|
||||
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) # return an error with the message attached
|
||||
|
||||
elif value_ok == 'LOW': # value is too low
|
||||
min_value = g.default_arrays[key][2][g.AXIS_NAMES.index(section)] # get min value for message printing
|
||||
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) # return an error with the message attached
|
||||
|
||||
if value_ok == 'OK' or override: # value is within limits or user has overridden the checks
|
||||
try:
|
||||
section_obj = CONFIG_OBJECT[section] # get relevant section in the config
|
||||
except KeyError: # there is no such section
|
||||
ui.ui_print("Could not find section", section, "in config file, creating new.")
|
||||
CONFIG_OBJECT.add_section(section) # create the missing section
|
||||
section_obj = CONFIG_OBJECT[section] # get the object of the section
|
||||
try:
|
||||
section_obj[key] = str(value) # set value for correct entry in the section
|
||||
except KeyError: # there is no entry with this key
|
||||
ui.ui_print("Could not find key", key, "in config file, creating new.")
|
||||
CONFIG_OBJECT.set(section, key, str(value)) # create the entry and set the value
|
||||
|
||||
except KeyError as e: # key for section or specific value does not exist in the dictionary for max/min values
|
||||
ui.ui_print("Error while editing config file:", e)
|
||||
raise KeyError("Could not find key", key, "in config file.") # return an error
|
||||
|
||||
|
||||
def check_config(config_object): # check all numeric values in the config and see if they are within safe limits
|
||||
ui.ui_print("Checking config file for values exceeding limits:")
|
||||
|
||||
concerns = {} # initialize dictionary for found problems
|
||||
problem_counter = 0 # count the number of values that exceed limits
|
||||
i = 0
|
||||
for axis in g.AXIS_NAMES: # go through all 3 axes
|
||||
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: # no problems were found for this axis
|
||||
concerns[axis].append("No problems detected.")
|
||||
|
||||
ui.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 %i value(s) exceeding limits in config file. Check values "
|
||||
"to ensure correct operation and avoid equipment damage!" % problem_counter)
|
||||
g.app.show_frame(ui.Configuration) # open configuration window so user can check values
|
||||
|
||||
|
||||
def reset_config_to_default(): # reset values in config object to defaults (set in globals.py)
|
||||
config = ConfigParser() # reinitialize empty config object
|
||||
global CONFIG_OBJECT # get the global config object
|
||||
CONFIG_OBJECT = config # reset it to the empty object
|
||||
|
||||
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 values
|
||||
i += 1
|
||||
|
||||
config.add_section("PORTS") # add section for PSU serial ports
|
||||
for key in g.default_ports.keys(): # go through dictionary of default serial ports
|
||||
config.set("PORTS", key, str(g.default_ports[key])) # set the value for each axis
|
||||
@@ -0,0 +1,99 @@
|
||||
# This file contains functions related to logging data from the program to a CSV file.
|
||||
# They are mainly but not only called by the ConfigureLogging class in User_Interface.py.
|
||||
|
||||
# import packages
|
||||
import pandas as pd
|
||||
import globals as g
|
||||
from datetime import datetime
|
||||
import os
|
||||
from tkinter import filedialog
|
||||
from tkinter import messagebox
|
||||
|
||||
# import other project files
|
||||
import User_Interface as ui
|
||||
|
||||
log_data = pd.DataFrame() # pandas data frame containing the logged data, in-program representation of csv/excel data
|
||||
unsaved_data = False # Bool to indicate if there is unsaved data, set to True each time a datapoint is logged
|
||||
zero_time = datetime.now() # set reference for timestamps in log file, reset when log_data is cleared and restarted
|
||||
|
||||
# create dictionary with all value handles that could be logged
|
||||
# Key: String that is displayed in UI and column headers. Also serves as handle to access dictionary elements.
|
||||
# Keys are the same as the rows in the status display ToDo (optional): use this for the status display
|
||||
# Content: name of the corresponding attribute in the Axis class (in cage_func.py).
|
||||
# Important: attribute handle must match definition in Axis class exactly, used with axis.getattr() to get values.
|
||||
axis_data_dict = {
|
||||
'PSU Status': 'connected',
|
||||
'Voltage Setpoint': 'voltage_setpoint',
|
||||
'Actual Voltage': 'voltage',
|
||||
'Current Setpoint': 'current_setpoint',
|
||||
'Actual Current': 'current',
|
||||
'Target Field': 'target_field_comp',
|
||||
'Trgt. Field Raw': 'target_field_comp',
|
||||
'Target Current': 'target_current',
|
||||
'Inverted': 'polarity_switched'
|
||||
}
|
||||
|
||||
|
||||
def triple_list(key_list): # creates list with each entry of key_list tripled with axis names before it
|
||||
new_list = [] # initialize list
|
||||
for key in key_list: # go through the given list
|
||||
for axis_name in ['X', 'Y', 'Z']: # per given list entry create three, one for each axis
|
||||
new_list.append(' '.join((axis_name, key))) # put axis_name before the given entry and append to new list
|
||||
return new_list
|
||||
|
||||
|
||||
def log_datapoint(key_list): # logs a single row of data into the log_data DataFrame
|
||||
# key_list determines what data is logged
|
||||
global log_data # get global dataframe with logged data
|
||||
global unsaved_data # get global variable that indicates if there is unsaved data
|
||||
date = datetime.now().date() # get current date
|
||||
time = datetime.now().strftime("%H:%M:%S,%f") # get string with current time in correct format
|
||||
t = (datetime.now() - zero_time).total_seconds() # calculate timestamp relative to the start of the logging
|
||||
data = [[date, time, t]] # initialize new data row with timestamps
|
||||
for key in key_list: # go through the list telling us what data to log
|
||||
for axis in g.AXES: # log this data for each axis
|
||||
# get relevant value from the correct AXIS object and append to new data row:
|
||||
data[0].append(getattr(axis, axis_data_dict[key]))
|
||||
|
||||
column_names = ["Date", "Time", "t (s)", *triple_list(key_list)] # create list with the correct column headers
|
||||
new_row = pd.DataFrame(data, columns=column_names) # create data frame containing the new row
|
||||
log_data = log_data.append(new_row, ignore_index=True) # append the new data frame to the logged data
|
||||
unsaved_data = True # tell other program parts that there is now unsaved data
|
||||
|
||||
|
||||
def select_file(): # select a file to write logs to
|
||||
directory = os.path.abspath(os.getcwd()) # get project directory
|
||||
# open file selection dialogue and save path of selected file
|
||||
filepath = filedialog.asksaveasfilename(initialdir=directory, title="Set log file",
|
||||
filetypes=([("Comma Separated Values", "*.csv*")]),
|
||||
defaultextension=[("Comma Separated Values", "*.csv*")])
|
||||
|
||||
if filepath == '': # this happens when file selection window is closed without selecting a file
|
||||
ui.ui_print("No file selected, can not save logged data.")
|
||||
return None
|
||||
else: # a valid file name was entered
|
||||
return filepath
|
||||
|
||||
|
||||
def write_to_file(dataframe, filepath):
|
||||
# get global variables for use in this function:
|
||||
global unsaved_data
|
||||
if filepath is not None: # user has selected a file and no errors occurred
|
||||
ui.ui_print("Writing logged data to file", filepath)
|
||||
try:
|
||||
# write data collected in log_data DataFrame to csv file in german excel format:
|
||||
dataframe.to_csv(filepath, index=False, sep=';', decimal=',')
|
||||
except PermissionError:
|
||||
message = "No permission to write to: \n%s. \nFile may be open in another program." % filepath
|
||||
messagebox.showerror("Permission Error", message)
|
||||
except BaseException as e:
|
||||
message = "Error while trying to write to file \n%s.\n%s" % (filepath, e)
|
||||
messagebox.showerror("Error!", message)
|
||||
else: # no exceptions occurred
|
||||
unsaved_data = False # tell everything that there is no unsaved data remaining
|
||||
ui.ui_print("Log data saved to", filepath)
|
||||
|
||||
|
||||
def clear_logged_data(): # clears all logged data from data frame
|
||||
global log_data # get global variable
|
||||
log_data = pd.DataFrame() # reset to an empty data frame, i.e. clearing all logged data
|
||||
@@ -0,0 +1,166 @@
|
||||
# tThis file contains code for executing a sequence of magnetic fields from a csv file.
|
||||
# To do this without crashing the UI it has to run in a separate thread using the threading module.
|
||||
|
||||
# import packages:
|
||||
import time
|
||||
import pandas
|
||||
from threading import *
|
||||
from tkinter import messagebox
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# import other project files:
|
||||
import User_Interface as ui
|
||||
import cage_func as func
|
||||
import globals as g
|
||||
|
||||
|
||||
class ExecCSVThread(Thread):
|
||||
# main class for executing a CSV sequence
|
||||
# it inherits the threading.Thread class, enabling sequence execution in a separate thread
|
||||
|
||||
def __init__(self, array, parent, controller):
|
||||
Thread.__init__(self)
|
||||
|
||||
self.array = array # numpy array containing data from csv to be executed
|
||||
self.parent = parent # object from which this class is called, here the ExecuteCSVMode object of the UI
|
||||
self.controller = controller # object on which mainloop() is running, usually the main UI window
|
||||
|
||||
self.__stop_event = Event() # event which can be set to stop the thread execution if needed
|
||||
|
||||
def run(self): # called to start the execution of the thread
|
||||
ui.ui_print("Starting Sequence Execution...")
|
||||
self.execute_sequence(self.array, 0.1, self.parent, self.controller) # run sequence
|
||||
# when the sequence has ended, reset buttons on the UI:
|
||||
if not g.exitFlag: # main window is open
|
||||
self.parent.select_file_button["state"] = "normal"
|
||||
self.parent.execute_button["state"] = "normal"
|
||||
self.parent.stop_button["state"] = "disabled"
|
||||
self.parent.reinit_button["state"] = "normal"
|
||||
|
||||
# setup ability to interrupt thread (https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread)
|
||||
def stop(self): # stop thread execution, can be called from another thread to kill this one
|
||||
self.__stop_event.set()
|
||||
|
||||
def stopped(self): # returns true if the thread has been stopped, used to check if a run should continue
|
||||
return self.__stop_event.is_set()
|
||||
|
||||
def execute_sequence(self, array, delay, parent, controller):
|
||||
# main execution method of the class
|
||||
# runs through array with times and desired fields and commands test stand accordingly
|
||||
|
||||
# array format: [time (s), xField (T), yField (T), zField (T)]
|
||||
func.power_down_all() # sets outputs on PSUs to 0 and Arduino pins to LOW before starting
|
||||
t_zero = time.time() # set reference time for start of run
|
||||
|
||||
# Check if everything is properly connected:
|
||||
all_connected = func.devices_ok(parent.xy_override.get(), parent.z_override.get(),
|
||||
parent.arduino_override.get())
|
||||
# True or False depending on devices status, checks for some devices may be overridden by user
|
||||
|
||||
i = 0 # index of the current array row
|
||||
while i < len(array) and all_connected and not self.stopped() and not g.exitFlag:
|
||||
# while array is not finished, devices are connected, user has not cancelled and application is running
|
||||
|
||||
t = time.time() - t_zero # get time relative to start of run
|
||||
if t >= array[i, 0]: # time for this row has come
|
||||
g.threadLock.acquire() # execute all lines until threadLock.release() before going back to main thread
|
||||
|
||||
# check if everything is still connected before sending commands:
|
||||
all_connected = func.devices_ok(parent.xy_override.get(), parent.z_override.get(),
|
||||
parent.arduino_override.get())
|
||||
if all_connected:
|
||||
field_vec = array[i, 1:4] # extract desired field vector
|
||||
ui.ui_print("%0.5f s: t = %0.2f s, target field vector ="
|
||||
% (time.time() - t_zero, array[i, 0]), field_vec * 1e6, "\u03BCT")
|
||||
func.set_field(field_vec) # send field vector to test stand
|
||||
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 = controller.pages[ui.ConfigureLogging] # get object of logging configurator
|
||||
if logger.event_logging: # data should be logged when test stand is commanded
|
||||
logger.log_datapoint() # log data
|
||||
|
||||
i = i + 1 # next row
|
||||
|
||||
g.threadLock.release() # allow going back to main thread now
|
||||
|
||||
elif t <= array[i, 0] - delay - 0.02: # is there enough time to sleep before the next row?
|
||||
time.sleep(delay) # sleep to give other threads time to run
|
||||
|
||||
if not self.stopped() and not g.exitFlag and all_connected: # sequence ended without interruption
|
||||
ui.ui_print("Sequence executed, powering down channels.")
|
||||
elif all_connected: # interrupted by user
|
||||
ui.ui_print("Sequence cancelled, powering down channels.")
|
||||
elif not all_connected: # interrupted by device error
|
||||
ui.ui_print("Error with at least one device, sequence aborted.")
|
||||
messagebox.showwarning("Device Error!", "Error with at least one device, sequence aborted.")
|
||||
else: # if this happens there is a mistake in the logic above, it really should not
|
||||
# tell the user something weird happened:
|
||||
ui.ui_print("Encountered unexpected sequence end state:"
|
||||
"\nThread Stopped:", self.stopped(), ", Application Closed:", g.exitFlag,
|
||||
", Devices connected:", all_connected)
|
||||
messagebox.showwarning("Unexpected state",
|
||||
"Encountered unexpected sequence end state, see console output for details.")
|
||||
|
||||
func.power_down_all() # set currents and voltages to 0, set arduino pins to low
|
||||
|
||||
|
||||
def read_csv_to_array(filepath): # convert a given csv file to a numpy array
|
||||
# csv format: time (s); xField (T); yField (T); zField (T) (german excel)
|
||||
# decimal commas
|
||||
file = pandas.read_csv(filepath, sep=';', decimal=',', header=0) # read csv file without column headers
|
||||
array = file.to_numpy() # convert csv to array
|
||||
return array
|
||||
|
||||
|
||||
def check_array_ok(array):
|
||||
# check if any magnetic fields in an array exceed the test stand limits and if so display a warning message
|
||||
values_ok = True
|
||||
for i in [0, 1, 2]: # go through axes/columns
|
||||
max_val = g.AXES[i].max_comp_field[1] # get limits the test stand can do
|
||||
min_val = g.AXES[i].max_comp_field[0]
|
||||
data = array[:, i + 1] # extract data for this axis from array
|
||||
# noinspection PyTypeChecker
|
||||
if any(data > max_val) or any(data < min_val): # if any datapoint is out of bounds
|
||||
values_ok = False
|
||||
if not values_ok: # show warning pop-up if values are exceeding limits
|
||||
messagebox.showwarning("Value Limits Warning!", "Found field values exceeding limits of test stand."
|
||||
"\nSee plot and check values in csv.")
|
||||
|
||||
|
||||
def plot_field_sequence(array, width, height): # create plot of fixed size (pixels) from array
|
||||
# ToDo (optional): polar plots, plots of angle...
|
||||
# ToDo (optional): show graphs as steps (as performed by test stand)
|
||||
fig_dpi = 100 # set figure resolution (dots per inch)
|
||||
px = 1/fig_dpi # get pixel to inch size conversion
|
||||
figure = plt.Figure(figsize=(width*px, height*px), dpi=fig_dpi) # create figure with correct size
|
||||
|
||||
# noinspection PyTypeChecker,SpellCheckingInspection
|
||||
axes = figure.subplots(3, sharex=True, sharey=True, gridspec_kw={'hspace': 0.4}) # create subplots with shared axes
|
||||
|
||||
figure.suptitle("Magnetic Field Sequence") # set figure title
|
||||
|
||||
t = array[:, 0] # extract time column
|
||||
for i in [0, 1, 2]: # go through all three axes
|
||||
data = array[:, i + 1] * 1e6 # extract field column of this axis and convert to microtesla
|
||||
max_val = g.AXES[i].max_comp_field[1] * 1e6 # get limits of achievable field
|
||||
min_val = g.AXES[i].max_comp_field[0] * 1e6
|
||||
plot = axes[i] # get appropriate subplot
|
||||
|
||||
plot.plot(t, data, linestyle='solid', marker='.') # plot data
|
||||
|
||||
if any(data > max_val): # if any value is higher than the maximum
|
||||
plot.axhline(y=max_val, linestyle='dashed', color='r') # plot horizontal line to show maximum
|
||||
# add label to line:
|
||||
plot.text(t[-1], max_val, "max", horizontalalignment='center', verticalalignment='top', color='r')
|
||||
if any(data < min_val): # same as above
|
||||
plot.axhline(y=min_val, linestyle='dashed', color='r')
|
||||
plot.text(t[-1], min_val, "min", horizontalalignment='center', color='r')
|
||||
|
||||
plot.set_title(g.AXIS_NAMES[i], size=10) # set subplot title (e.g. "X-Axis")
|
||||
|
||||
# set shared axis labels:
|
||||
axes[2].set_xlabel("Time (s)")
|
||||
axes[1].set_ylabel("Magnetic Field (\u03BCT)")
|
||||
|
||||
return figure # return the created figure to be inserted somewhere
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# This file is used to hold global variables that are used by more than one file of the program.
|
||||
# Instead of always passing all variables to functions, this file can simply be imported to get them.
|
||||
|
||||
import numpy as np
|
||||
|
||||
XY_DEVICE = None # XY PSU object will be stored here (class PS2000B)
|
||||
Z_DEVICE = None # Z PSU object will be stored here (class PS2000B)
|
||||
ARDUINO = None # Arduino object will be stored here (class ArduinoCtrl)
|
||||
|
||||
# Axis objects will be stored here (class Axis)
|
||||
X_AXIS = None
|
||||
Y_AXIS = None
|
||||
Z_AXIS = None
|
||||
|
||||
AXES = None # list containing [X_AXIS, Y_AXIS, Z_AXIS]
|
||||
|
||||
app = None # Main Tkinter application object will be stored here (class HelmholtzGUI)
|
||||
|
||||
AXIS_NAMES = ["X-Axis", "Y-Axis", "Z-Axis"] # list with the names of each axis, used mainly for printing functions
|
||||
|
||||
global XY_PORT # serial port for XY PSU will be stored here (string)
|
||||
global Z_PORT # serial port for Z PSU will be stored here (string)
|
||||
|
||||
global PORTS # list containing [XY_PORT, XY_PORT, Z_PORT], used in loops where info on each axis is needed
|
||||
|
||||
global threadLock # thread locking object, used to force threads to perform actions in a certain order (threading.Lock)
|
||||
|
||||
exitFlag = True # False when main window is open, True otherwise
|
||||
|
||||
# Create dictionaries with default Constants and maximum/minimum values
|
||||
# Used to create default configs and to check if user inputs are within safe limits
|
||||
# ToDo: check actual maximum ratings (or refine after testing)
|
||||
# ToDo: put this into a config file
|
||||
|
||||
# Dictionary for numerical values:
|
||||
# format: key: [default values], [maximum values], [minimum values]
|
||||
default_arrays = {
|
||||
"coil_const": np.array([[38.6, 38.45, 37.9], [50, 50, 50], [0, 0, 0]]) * 1e-6, # Coil constants [x,y,z] [T/A]
|
||||
"ambient_field": np.array([[30, 30, 30], [200, 200, 200], [-200, -200, -200]]) * 1e-6, # ambient magnetic field [T]
|
||||
"resistance": np.array([[1.7, 1.7, 1.7], [5, 5, 5], [1, 1, 1]], dtype=float), # resistance of circuits [Ohm]
|
||||
"max_volts": np.array([[14, 14, 14], [16, 16, 16], [0, 0, 0]], dtype=float), # max. 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
|
||||
}
|
||||
# Dictionary for PSU serial ports:
|
||||
default_ports = {
|
||||
"xy_port": "COM1", # Default serial port where PSU for X- and Y-Axes is connected
|
||||
"z_port": "COM2", # Default serial port where PSU for Z-Axis is connected
|
||||
}
|
||||
@@ -1,28 +1,78 @@
|
||||
import User_Interface as ui
|
||||
import cage_func as func
|
||||
# Main file of the program. Run this file to start the application.
|
||||
|
||||
# import packages:
|
||||
from os.path import exists
|
||||
import traceback
|
||||
import settings as g
|
||||
from tkinter import messagebox
|
||||
|
||||
# import other project files:
|
||||
import cage_func as func
|
||||
from User_Interface import HelmholtzGUI
|
||||
from User_Interface import ui_print
|
||||
import User_Interface as ui
|
||||
import globals as g
|
||||
import config_handling as config
|
||||
import csv_logging as log
|
||||
|
||||
|
||||
def program_end(): # called on exception or when user closes application
|
||||
# safely shuts everything down and saves any unsaved data
|
||||
|
||||
g.exitFlag = True # tell everything else the application has been closed
|
||||
if g.app is not None: # the main Tkinter app object has been initialized before
|
||||
if g.app.pages[ui.ExecuteCSVMode].csv_thread is not None: # check if a thread for executing CSVs exists
|
||||
g.app.pages[ui.ExecuteCSVMode].csv_thread.stop() # stop the thread
|
||||
|
||||
func.shut_down_all() # shut down devices
|
||||
|
||||
if log.unsaved_data: # Check if there is logged data that has not been saved yet
|
||||
# open pop-up to ask user if he wants to save the data:
|
||||
save_log = messagebox.askquestion("Save log data?", "There seems to be unsaved logging data. "
|
||||
"Do you wish to write it to a file now?")
|
||||
if save_log == 'yes': # user has chosen yes
|
||||
filepath = log.select_file() # let user select a file to write to
|
||||
log.write_to_file(log.log_data, filepath) # write the data to the chosen file
|
||||
|
||||
if g.app is not None:
|
||||
g.app.destroy() # close application
|
||||
|
||||
|
||||
try: # start normal operations
|
||||
# Connect to Arduino:
|
||||
g.ARDUINO = func.ArduinoCtrl(g.RELAY_PINS)
|
||||
|
||||
print("Connecting to PSUs...")
|
||||
func.setup_axes() # initiate communication, set handles
|
||||
config.CONFIG_FILE = 'config.ini' # set the config file path
|
||||
# ToDo: remember what the last config file was
|
||||
if not exists(config.CONFIG_FILE): # config file does not exist yet
|
||||
print("Config file not found, creating new from defaults.")
|
||||
config.reset_config_to_default() # create configuration object from defaults
|
||||
config.write_config_to_file(config.CONFIG_OBJECT) # write the configuration object to a new file
|
||||
|
||||
config.CONFIG_OBJECT = config.get_config_from_file(config.CONFIG_FILE) # read configuration data from config file
|
||||
|
||||
print("Starting setup...")
|
||||
func.setup_all() # initiate communication with devices and initialize all major program objects
|
||||
|
||||
print("\nOpening User Interface...")
|
||||
'''g.TestValuesX = ui.TestValues()
|
||||
g.TestValuesY = ui.TestValues()
|
||||
#g.TestValuesZ = ui.TestValues()
|
||||
g.TestValues = [g.TestValuesX, g.TestValuesY]#, g.TestValuesZ]'''
|
||||
|
||||
application = ui.HelmholtzGUI()
|
||||
application.mainloop()
|
||||
g.app = HelmholtzGUI() # initialize user interface
|
||||
g.exitFlag = False # tell all functions that the user interface is now running
|
||||
g.app.state('zoomed') # open UI in maximized window
|
||||
g.app.StatusDisplay.continuous_label_update(g.app, 500) # initiate regular Status Display updates (ms)
|
||||
ui_print("Program Initialized")
|
||||
config.check_config(config.CONFIG_OBJECT) # check config for values exceeding limits
|
||||
|
||||
except BaseException as e: # if there is an error, print what happened
|
||||
ui_print("\nStarting setup...") # do setup again, so it is printed in the UI console ToDo: do it only once
|
||||
func.setup_all() # initiate communication with devices and initialize all major program objects
|
||||
|
||||
g.app.protocol("WM_DELETE_WINDOW", program_end) # call program_end function if user closes the application
|
||||
|
||||
g.app.mainloop() # start main program loop
|
||||
|
||||
|
||||
except Exception as e: # An error has occurred somewhere in the program
|
||||
print("\nAn error occurred, Shutting down.")
|
||||
print(e)
|
||||
print(traceback.print_exc())
|
||||
|
||||
finally: # safely shut everything down at the end
|
||||
func.shut_down_all()
|
||||
# shop pup-up error message:
|
||||
message = "%s.\nSee python console traceback for more details. " \
|
||||
"\nShutting down devices, check equipment to confirm." % e
|
||||
messagebox.showerror("Error!", message)
|
||||
print(traceback.print_exc()) # print error traceback in the python console
|
||||
program_end() # safely close everything and shut down devices
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import numpy as np
|
||||
import settings as g
|
||||
import cage_func as func
|
||||
# from pyps2000b import PS2000B
|
||||
from Arduino import Arduino
|
||||
|
||||
# User Inputs/Configuration----------------------------------
|
||||
# Desired output:
|
||||
mag_vec1 = np.array([10, 10, 5])*1e-6
|
||||
|
||||
# Constants:
|
||||
g.COIL_CONST = np.array([38.6, 38.45, 37.9]) * 1e-9 # Coil constants [x,y,z] in T/A
|
||||
g.AMBIENT_FIELD = np.array([80]) * 1e-6 # ambient magnetic field in measurement area, to be cancelled out
|
||||
g.RESISTANCES = np.array([3.9, 1, 1]) # resistance of [x,y,z] circuits
|
||||
g.MAX_WATTS = np.array([8, 0, 0]) # max. allowed power for [x,y,z] circuits
|
||||
g.MAX_VOLTS = 16 # max. allowed voltage, limited to 16V by used diodes!
|
||||
|
||||
# COM-Ports for power supply units:
|
||||
g.XY_PORT = "COM1" # placeholders
|
||||
g.Z_PORT = "COM2"
|
||||
|
||||
# Code starts here------------------------------------------
|
||||
g.MAX_AMPS = np.sqrt(g.MAX_WATTS / g.RESISTANCES) # calculate maximum currents in each axis
|
||||
|
||||
print("Connecting to PSUs...")
|
||||
func.setup_axes() # initiate communication, set handles
|
||||
print("Connecting to Arduino...")
|
||||
g.ARDUINO = Arduino() # search for connected arduino and set handle
|
||||
print("Arduino found, configuring pins.")
|
||||
func.setup_arduino()
|
||||
print("Activating PSU outputs...")
|
||||
func.activate_all() # activate remote control and outputs on PSUs
|
||||
|
||||
func.set_field_simple(mag_vec1)
|
||||
|
||||
func.deactivate_all()
|
||||
@@ -1,3 +1,5 @@
|
||||
# This file enables communication with PS2000B Power Supply Units.
|
||||
|
||||
#!/usr/bin/env python3
|
||||
# coding=utf-8
|
||||
# Python access to Elektro Automatik PS 2000 B devices via USB/serial
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
numpy==1.19.3 # bug in numpy 1.19.4, 1.19.3 used as workaround
|
||||
pyserial~=3.5
|
||||
future~=0.18.2
|
||||
pandas
|
||||
pandas~=1.1.5
|
||||
matplotlib~=3.3.2
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
XY_DEVICE = None
|
||||
Z_DEVICE = None
|
||||
|
||||
X_AXIS = None # object structure: (device, channel, arduino pin, axis index)
|
||||
Y_AXIS = None
|
||||
Z_AXIS = None
|
||||
|
||||
AXES = None # list containing [X_AXIS, Y_AXIS, Z_AXIS]
|
||||
|
||||
# Constants:
|
||||
COIL_CONST = np.array([38.6, 38.45, 37.9]) * 1e-6 # Coil constants [x,y,z] in T/A
|
||||
AMBIENT_FIELD = np.array([80, 80, 80]) * 1e-6 # ambient magnetic field in measurement area, to be cancelled out
|
||||
RESISTANCES = np.array([4.5, 8, 1]) # resistance of [x,y,z] circuits
|
||||
MAX_WATTS = np.array([8, 25, 0]) # max. allowed power for [x,y,z] circuits
|
||||
MAX_VOLTS = [16, 16, 16] # max. allowed voltage, limited to 16V by used diodes!
|
||||
|
||||
# COM-Ports for power supply units:
|
||||
XY_PORT = "COM7" # placeholders
|
||||
Z_PORT = "COM11"
|
||||
|
||||
AXIS_NAMES = ["X-Axis", "Y-Axis", "Z-Axis"]
|
||||
ports = [XY_PORT, XY_PORT, Z_PORT]
|
||||
|
||||
global ARDUINO
|
||||
|
||||
RELAY_PINS = [15, 16, 17] # pin on the Arduino for switching relay of each axis [x,y,z]
|
||||
|
||||
# ToDo: make proper settings file to read from and write to
|
||||
|
||||
global TestValuesX
|
||||
global TestValuesY
|
||||
global TestValuesZ
|
||||
global TestValues
|
||||
Reference in New Issue
Block a user