347 lines
15 KiB
Python
347 lines
15 KiB
Python
from PyQt6.QtGui import *
|
|
from PyQt6.QtWidgets import *
|
|
from PyQt6.QtCore import *
|
|
|
|
import multiprocessing
|
|
import multiprocessing.managers
|
|
|
|
import time
|
|
import traceback
|
|
import sys
|
|
import os
|
|
from timeit import default_timer as timer
|
|
import collections
|
|
import itertools
|
|
import numpy as np
|
|
import pyqtgraph as pg
|
|
import import_txt
|
|
from random import random
|
|
|
|
# Get the current script's directory
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
# Get the parent directory by going one level up
|
|
parent_dir = os.path.dirname(current_dir)
|
|
# Add the parent directory to sys.path
|
|
sys.path.append(parent_dir)
|
|
from drivers import Agilent_304_FS_AG
|
|
|
|
from design_files.Agilent_304_FS_AG_design import Ui_MainWindow
|
|
|
|
|
|
class WorkerSignals(QObject):
|
|
'''
|
|
Defines the signals available from a running worker thread.
|
|
Supported signals are:
|
|
finished: No data
|
|
error: tuple (exctype, value, traceback.format_exc() )
|
|
result: object data returned from processing, anything
|
|
progress: int indicating % progress
|
|
'''
|
|
finished = pyqtSignal()
|
|
error = pyqtSignal(tuple)
|
|
result = pyqtSignal(object)
|
|
progress = pyqtSignal(list)
|
|
|
|
|
|
class Worker(QRunnable):
|
|
'''
|
|
Worker thread
|
|
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
|
|
:param callback: The function callback to run on this worker thread. Supplied args and
|
|
kwargs will be passed through to the runner.
|
|
:type callback: function
|
|
:param args: Arguments to pass to the callback function
|
|
:param kwargs: Keywords to pass to the callback function
|
|
'''
|
|
|
|
def __init__(self, fn, *args, **kwargs):
|
|
super(Worker, self).__init__()
|
|
|
|
# Store constructor arguments (re-used for processing)
|
|
self.fn = fn
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
self.signals = WorkerSignals()
|
|
|
|
# Add the callback to our kwargs
|
|
self.kwargs['progress_callback'] = self.signals.progress
|
|
|
|
@pyqtSlot()
|
|
def run(self):
|
|
'''
|
|
Initialise the runner function with passed args, kwargs.
|
|
'''
|
|
|
|
# Retrieve args/kwargs here; and fire processing using them
|
|
try:
|
|
result = self.fn(*self.args, **self.kwargs)
|
|
except:
|
|
traceback.print_exc()
|
|
exctype, value = sys.exc_info()[:2]
|
|
self.signals.error.emit((exctype, value, traceback.format_exc()))
|
|
else:
|
|
self.signals.result.emit(result) # Return the result of the processing
|
|
finally:
|
|
self.signals.finished.emit() # Done
|
|
|
|
def get_float(Qline,default = 0):
|
|
'''gets value from QLineEdit and converts it to float. If text is empty or
|
|
cannot be converted, it returns "default" which is 0, if not specified'''
|
|
try:
|
|
out = float(Qline.text())
|
|
except:
|
|
out = default
|
|
return(out)
|
|
|
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
def __init__(self, *args, **kwargs):
|
|
# Get the current script's directory
|
|
self.current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
# Get the parent directory by going one level up
|
|
self.parent_dir = os.path.dirname(current_dir)
|
|
|
|
#establish connection to global variables
|
|
try: #try to connect to global variables
|
|
manager = multiprocessing.managers.BaseManager(address=('localhost',5001), authkey=b'')
|
|
manager.connect()
|
|
manager.register('sync_AG_304')
|
|
self.sync_AG_304 = manager.sync_AG_304()
|
|
except: #open global variables, if no connection can be made (i.e. it is not running). Then connect to it
|
|
# subprocess.call(['D:\\Python instrument drivers\\env\\Scripts\\python.exe', 'D:\\Python instrument drivers\\StandAlones\\global_variables.py'])
|
|
self.global_vars = QProcess()
|
|
self.global_vars.start(self.current_dir+"\\env\\Scripts\\python.exe", [self.current_dir+'\\global_variables.py'])
|
|
manager.connect()
|
|
manager.register('sync_AG_304')
|
|
self.sync_AG_304 = manager.sync_AG_304()
|
|
print('!!!\nI opened global variables myself. If you close me, global variables will shut down too. Consider starting global variables in own instance for more security\n!!!')
|
|
|
|
#fill in variables, if they are not defined in global variables
|
|
self.sync_AG_304.update({'P':0, 'T':0, 'f':0, 'Status': 'Stop'})
|
|
|
|
#import Gui from QT designer file
|
|
super(MainWindow, self).__init__(*args, **kwargs)
|
|
self.setupUi(self)
|
|
|
|
#setup plot
|
|
self.graphWidget.setBackground('w')
|
|
self.graphWidget.setTitle("Pressure")
|
|
self.graphWidget.setLabel('left', 'Pressure [mnbar]')
|
|
self.graphWidget.setLabel('bottom', 'Time (H)')
|
|
axis = pg.DateAxisItem()
|
|
self.graphWidget.setAxisItems({'bottom':axis})
|
|
self.graphWidget.setLogMode(False,True)
|
|
|
|
temp = [time.time(),time.time()-1]
|
|
pen1 = pg.mkPen(color=(255, 0, 0), width=2)
|
|
self.plot_1 = self.graphWidget.plot(temp,[1,0],pen = pen1)
|
|
|
|
#set up pyQT threadpool
|
|
self.threadpool = QThreadPool()
|
|
|
|
#define and standard threads.
|
|
worker_save = Worker(self.save)
|
|
self.threadpool.start(worker_save)
|
|
|
|
#define signals and slots
|
|
self.actionSet_default.triggered.connect(self.set_default)
|
|
self.actionReset_default.triggered.connect(self.read_default)
|
|
self.button_connect.clicked.connect(self.start_meas)
|
|
self.SB_N_points.valueChanged.connect(self.set_Npoints)
|
|
self.dSB_save_intervall.valueChanged.connect(self.change_timing)
|
|
self.checkBox_disableplots.stateChanged.connect(self.set_displot)
|
|
self.button_buffer_size.clicked.connect(self.change_buffer_size)
|
|
self.Button_start.clicked.connect(self.start_pump)
|
|
self.Button_stop.clicked.connect(self.stop_pump)
|
|
|
|
#define constants
|
|
self.P = 0 #store Pressure data
|
|
self.start = False #start Pump? Is set to true be self.start_pump
|
|
self.stop = False #Stop Pump? Is set to true be self.stop_pump
|
|
self.Npoints = 200 #number of point to plot
|
|
self.running = True #true while app is running
|
|
self.disable_plot = False #constant to disable plot to improve performance. Is changed by checkbox checkBox_disableplots
|
|
self.timing_save = 5 #save intervall [s]
|
|
self.buffer_size = 36000 #size of ringbuffers for plotting. 36000 is roughly 1 hour, if timng_main is 0.1 s
|
|
self.buffer = collections.deque(maxlen=self.buffer_size) #ringbuffer for plot data
|
|
self.lines_config_strings = [self.line_devAdr,self.line_filePath]#is used for config file
|
|
self.Spinboxes_config = [self.SB_N_points, self.dSB_save_intervall, self.dSB_timing,
|
|
self.SB_Buffer_size]#is used for config file
|
|
self.checkboxes_config = [self.checkBox_disableplots,self.checkBox_save]#is used for config file
|
|
|
|
#read default values from config and set them in gui
|
|
self.read_default()
|
|
#update save intervall to the gui value
|
|
self.change_timing()
|
|
|
|
|
|
def start_meas(self):
|
|
#Connect to device
|
|
address = self.line_devAdr.text()
|
|
self.ag = Agilent_304_FS_AG.AG_304_FS(address)
|
|
#start thread for communication with device
|
|
self.worker = Worker(self.update_all)
|
|
self.worker.signals.progress.connect(self.update_gui)
|
|
self.threadpool.start(self.worker)
|
|
|
|
def update_all(self, progress_callback):
|
|
'''get values from device and write them to global variables.'''
|
|
while self.running == True:
|
|
start = timer()
|
|
P = self.ag.read_pressure()
|
|
T = self.ag.read_temp()
|
|
f = self.ag.read_freq()
|
|
status = self.ag.read_status()
|
|
self.P = P #store pressure as global variable for save thread
|
|
#Write measurement values into global variables
|
|
self.sync_AG_304.update({'P':P})
|
|
self.sync_AG_304.update({'T':T})
|
|
self.sync_AG_304.update({'f':f})
|
|
self.sync_AG_304.update({'Status':status})
|
|
#Append Ringbuffer
|
|
self.buffer.append([time.time(),P])
|
|
#emit signal to trigger self.update_gui
|
|
progress_callback.emit([P,T,f,status])
|
|
end = timer()
|
|
if self.start == True: #check if pump should start or stop
|
|
self.ag.start_pump()
|
|
time.sleep(2)
|
|
self.start = False
|
|
elif self.stop == True:
|
|
self.ag.stop_pump()
|
|
time.sleep(2)
|
|
self.stop = False
|
|
try:
|
|
time.sleep(self.dSB_timing.value()-(end-start))
|
|
except:
|
|
print(f"Iteration took {end-start} seconds.")
|
|
|
|
del(self.ag) #disconnect device when self.running is set to False
|
|
|
|
def update_gui(self, List):
|
|
#Convert List into original format
|
|
P = List[0]
|
|
T = List[1]
|
|
f = List[2]
|
|
status = List[3]
|
|
#set numbers
|
|
self.line_P.setText(f"{P:.2e}")
|
|
self.line_T.setText(f"{T:.2f}")
|
|
self.line_f.setText(f"{f}")
|
|
self.line_status.setText(status)
|
|
|
|
#plot
|
|
#calculate index in buffer where plots start. (Needed for itertools.islice)
|
|
I_s = len(self.buffer) - self.Npoints
|
|
if I_s < 0:
|
|
I_s = 0
|
|
if self.disable_plot == False:
|
|
self.plot_1.setData(np.array(list(itertools.islice(self.buffer,I_s,None)))[:,0],
|
|
np.array(list(itertools.islice(self.buffer,I_s,None)))[:,1])
|
|
|
|
def start_pump(self):
|
|
'''generates prompt to make shure pump should start and, depending on
|
|
the input, it sets self.start to True. It is done this way so the controller does not
|
|
get multiple inputs at once from different threads.'''
|
|
button = QMessageBox.question(self, 'Start Pump','Start Pump?')
|
|
if button == QMessageBox.StandardButton.Yes:
|
|
print('start')
|
|
self.start = True
|
|
else:
|
|
print('no start')
|
|
|
|
def stop_pump(self):
|
|
'''generates prompt to make shure pump should stop and, depending on
|
|
the input, it sets self.stop to True. It is done this way so the controller does not
|
|
get multiple inputs at once from different threads.'''
|
|
button = QMessageBox.question(self, 'Stop Pump','Stop Pump?')
|
|
if button == QMessageBox.StandardButton.Yes:
|
|
print('stop')
|
|
self.stop = True
|
|
else:
|
|
print('no stop')
|
|
|
|
def change_buffer_size(self):
|
|
new_len = self.SB_Buffer_size.value()
|
|
data = list(self.buffer)
|
|
self.buffer = collections.deque(data[-new_len:], maxlen = new_len)
|
|
|
|
def set_Npoints(self):
|
|
#sets the number of points to plot
|
|
self.Npoints = self.SB_N_points.value()
|
|
|
|
def set_displot(self):
|
|
#sets variable to disable plot so checkbox state does not need be read out every iteration
|
|
self.disable_plot = self.checkBox_disableplots.isChecked()
|
|
|
|
def change_timing(self):
|
|
#updates the timing which is used for "save". If no value it given, it is set to 1 s
|
|
self.timing_save = self.dSB_save_intervall.value()
|
|
|
|
def save(self, progress_callback):
|
|
#if save checkbox is checked it writes measurement values to file specified in line.filePath. There the full path including file extension must be given.
|
|
while self.running == True:
|
|
time.sleep(self.timing_save) #wait is at beginning so first point is not corrupted when app just started.
|
|
if self.checkBox_save.isChecked() == True:
|
|
path = self.line_filePath.text()
|
|
if os.path.isfile(path) == False:
|
|
with open(path,'a') as file:
|
|
file.write('date\tP[mbar]\n')
|
|
file = open(path,'a')
|
|
t = self.buffer[len(self.buffer)-1][0]
|
|
file.write(time.strftime("%Y-%m-%d_%H-%M-%S",time.localtime(t))+'\t')
|
|
file.write(f'{self.P}\n')
|
|
file.close
|
|
|
|
def set_default(self):
|
|
#saves current set values to txt file in subdirectory configs. All entries that are saved are defined in self.lines_config
|
|
#Overwrites old values in config file.
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
path = current_dir+'\\configs\\AG_304_config.txt' #To make shure the config file is at the right place, independent from where the program is started the location of the file is retrieved
|
|
file = open(path,'w')
|
|
for l in self.lines_config_strings:
|
|
file.write(l.text()+'\t')
|
|
for s in self.Spinboxes_config:
|
|
file.write(f"{s.value()}\t")
|
|
for c in self.checkboxes_config:
|
|
file.write(str(c.isChecked())+'\t')
|
|
file.write('\n')
|
|
file.close
|
|
|
|
def read_default(self):
|
|
#reads default values from config file in subdirectory config and sets the values in gui. Then self.change is set to true so values are send
|
|
#to device. (If no config file exists, it does nothing.)
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
path = current_dir+'\\configs\\AG_304_config.txt' #To make shure the config file is read from the right place, independent from where the program is started the location of the file is retrieved
|
|
try: #exit function if config file does not exist
|
|
vals = import_txt.read_raw(path)
|
|
except:
|
|
print('no config file found on')
|
|
print(path)
|
|
return
|
|
|
|
for l,v in zip(self.lines_config_strings,vals[0][:]):
|
|
l.setText(v)
|
|
|
|
for s,v in zip(self.Spinboxes_config,vals[0][len(self.lines_config_strings):]):
|
|
try: #give float or int, depending on dSB of SB
|
|
s.setValue(float(v))
|
|
except TypeError:
|
|
s.setValue(int(v))
|
|
|
|
for c,v in zip(self.checkboxes_config,vals[0][len(self.lines_config_strings):]):
|
|
c.setChecked(v == 'True')
|
|
|
|
self.change = True
|
|
|
|
def closeEvent(self,event): #when window is closed self.running is set to False, so all threads stop
|
|
self.running = False
|
|
time.sleep(1)
|
|
event.accept()
|
|
|
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
window = MainWindow()
|
|
window.show()
|
|
app.exec() |