from PyQt6.QtGui import * from PyQt6.QtWidgets import * from PyQt6.QtCore import * import multiprocessing import multiprocessing.managers import time import traceback, sys, os import numpy as np # 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 Tabor_LS6081B import import_txt from design_files.Tabor_LS6081B_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_T_LS6081B') self.sync_T_LS6081B = manager.sync_T_LS6081B() 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_T_LS6081B') self.sync_T_LS6081B = manager.sync_T_LS6081B() 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_T_LS6081B.update({'freq':4.8e9, 'Power':-10,'RF':False, 'Pulse':False, 'Pulsewidth':1e-3, 'DC':0.5}) #import Gui from QT designer file super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) #setup plot #set up pyQT threadpool self.threadpool = QThreadPool() #start standard threads #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.button_RF.clicked.connect(self.update_RF) self.button_Pulse.clicked.connect(self.update_Pulse) self.line_DC.editingFinished.connect(self.update_DC) self.line_Freq.editingFinished.connect(self.update_f) self.line_Power.editingFinished.connect(self.update_P) self.line_width.editingFinished.connect(self.update_w) #define constants self.running = True #true while app is running self.set_new = [4.8e9, -10, False, False, 1e-3, 0.5] #variable to save the 'new' set values to compare them to the global variables self.set_old = [0, 0, False, False, 0, 0] #variable to save the 'old' set values to compare them to the global variables self.change = False self.lines_config_float = [self.line_Freq, self.line_Power,self.line_width, self.line_DC] #is used for config file self.lines_config_strings = [self.line_devAdr] #is used for config file #read default values from config and set values in them in gui self.read_default() #write gui values in global variables. RF and Pulse is just kept off self.update_DC() self.update_f() self.update_P() self.update_w() def start_meas(self): #Connect to device address = self.line_devAdr.text() self.gen = Tabor_LS6081B.TaborLS6081B(address) #start thread for communication with device worker = Worker(self.update_output) self.threadpool.start(worker) print('connected') def update_output(self,progress_callback): #check if global variables changed. If so, send new values to device while self.running == True: for i,n in enumerate(['freq', 'Power','RF', 'Pulse', 'Pulsewidth', 'DC']): #get new set values from global variables to compare them to old ones. self.set_new[i] = self.sync_T_LS6081B.get(n) if self.set_new != self.set_old: f = self.set_new[0] P = self.set_new[1] RF = self.set_new[2] Pulse = self.set_new[3] w = self.set_new[4] DC = self.set_new[5] #turn RF ON or OFF with settings in gui if RF == 1: if Pulse == 0: #Check if pulse is activated or not self.gen.CW(P, f, 1) else: self.gen.Pulse(P, f, w, DC, 1) else: self.gen.CW(P, f, 0) #set gui to values in global variables self.update_gui(self.set_new) self.set_old = self.set_new.copy() #.copy() is needer, otherwise it sets just a pointer to self.set_new time.sleep(0.1) del(self.gen) #disconnect device when self.running is set to False def update_gui(self,setV): #set values given in setV in the corresponding lineEdits self.line_Freq.setText(f"{setV[0]:.6e}") self.line_Power.setText(str(setV[1])) self.line_width.setText(f"{setV[4]:.3e}") self.line_DC.setText(str(setV[5])) if setV[2] == 1: #set RF button in the correct state self.button_RF.setText('RF ON/off') self.label_RF.setStyleSheet("background-color: green") else: self.button_RF.setText('RF on/OFF') self.label_RF.setStyleSheet("background-color: red") if setV[3] == 1: #set Pulse button in the correct state self.button_Pulse.setText('Pulse ON/off') self.label_Pulse.setStyleSheet("background-color: green") else: self.button_Pulse.setText('Pulse on/OFF') self.label_Pulse.setStyleSheet("background-color: red") def update_f(self): #changes global variable. The change will be detected by update_output and it will be passed to the device self.sync_T_LS6081B.update({'freq': get_float(self.line_Freq)}) def update_P(self): #changes global variable. The change will be detected by update_output and it will be passed to the device self.sync_T_LS6081B.update({'Power': get_float(self.line_Power)}) def update_w(self): #changes global variable. The change will be detected by update_output and it will be passed to the device self.sync_T_LS6081B.update({'Pulsewidth': get_float(self.line_width)}) def update_DC(self): #changes global variable. The change will be detected by update_output and it will be passed to the device self.sync_T_LS6081B.update({'DC': get_float(self.line_DC)}) def update_RF(self): #updates global variable depending of the old state of the RF button. (If it was off, the new value is set to 1(= RF on)) if self.button_RF.text() == 'RF on/OFF': self.sync_T_LS6081B.update({'RF': 1}) else: self.sync_T_LS6081B.update({'RF': 0}) def update_Pulse(self): #updates global variable depending of the old state of the Pulse button. (If it was off, the new value is set to 1(= Pulse on)) if self.button_Pulse.text() == 'Pulse on/OFF': self.sync_T_LS6081B.update({'Pulse': 1}) else: self.sync_T_LS6081B.update({'Pulse': 0}) 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\\Tabor_LS6081B_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_float: temp = f"{get_float(l)}" file.write(temp+'\t') for l in self.lines_config_strings: file.write(l.text()+'\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\\Tabor_LS6081B_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: return formats = ['.6e','.2f','.3e','.2f'] for l,v,f in zip(self.lines_config_float,vals[0],formats): v = float(v) #convert string in txt to float, so number can be formatted according to "formats" when it's set l.setText(format(v,f)) for l,v in zip(self.lines_config_strings,vals[0][4:]): l.setText(v) 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()