I wrote a longer code, to read out the weight of tem balances and print them in an excel file. Now I want to add a "plot" function which, plot the ten last values of each balance. the problem I got, is that the program will only plot after the measurement runds, this could take a longer time. Now i found out, that multi threading can avoid this problem. but I don't know how to implement it in my existing code. May be some one can tell me how to add the classes on the right was. So I would be able to plot some other values while the main loop writes them in to the excel file.
The following is my basic code, sorry for the long code, but I can't show a smaller version without loosing the whole context. I cut out the measurement command for balance 2 to 10, and also I left only one plot command. Cause they all are working and very similar to the others.
The measurement function "def Measurement():" will take the longest time, and is in the moment the part which is running actively in the foreground
The measurement Function "def Measurement():" will take the longest time, and is in the moment the part which is running actively in the foreground. this will block all other commands in the GUI, and this leads to the queuing of this other commands. For example: I press the "plot" or "Help" button, and they will open after one run of the measurement loop.
# -*- coding: utf-8 -*-
Created on Thu Aug 8 15:22:25 2019
The following program, is planed and designed to collect the weight information form 10 OHAUS SCOUT SKX2202 balances.
The program generates an GUI for the user to input all needed information and dates for the measurements.
After collecting, the program writes all needed and important information to an Excel file (`.xlsx`). Also the program should give the possibility to plot some weight values while the measurement is running.
# import all needed basic libraries
import time
import matplotlib.pyplot as plt
import os
import os.path
import string
import serial
import sys
import xlsxwriter
from openpyxl import Workbook
from datetime import datetime
from os import path
#import all needed libraries for the graphic user interface
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QGridLayout
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QProgressBar
from PyQt5.QtWidgets import QVBoxLayout,QRadioButton, QPlainTextEdit
#creating the main window, show the code to be an application
app = QApplication([])
app.setStyle('Fusio')
app.setStyleSheet("QPushButton {margin: 10ex;}")
window = QWidget()
window.setWindowTitle('Balance Messurements')
#creating the basic window elements
#all buttons for the main window (text added to the button will show their use)
buttonOkName = QPushButton('Confirm User Name')
buttonOkAnzahl = QPushButton('Confirm input ')
buttonOkZeit = QPushButton('Confirm time ')
buttonStarter = QPushButton("Start")
buttonhelp = QPushButton("Help")
buttonClose = QPushButton("close")
buttonOpenFile = QPushButton("Open File")
buttonPlot = QPushButton("Plot Graph")
chooseTime = QLabel("Choose if your Input Time are Minutes or Seconds")
minWahl = QRadioButton("Minutes")
sekWahl = QRadioButton("Seconds")
#user input mask for the user name, only accept Letters, no numbers, no space
user_name = QLineEdit()
user_name.setPlaceholderText("User Name")
user_name.setInputMask("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaaa")
#user input mask for number of measurements, only accept numbers , no Letters, no spaces
user_anzahl = QLineEdit()
user_anzahl.setPlaceholderText("Number")
user_anzahl.setInputMask('99999')
#user input mask for timeinterval, only accept numbers , no Letters, no spaces
user_zeit = QLineEdit()
user_zeit.setPlaceholderText("Timeintervall")
user_zeit.setInputMask('9999999999999')
#user input for the project name (written to the Excel file)
user_project=QLineEdit()
#the labels to name the single inputfields
user_name_label = QLabel("User Name:")
user_anzahl_label= QLabel("Number of measurements:")
user_zeit_label = QLabel("Break interval:")
Savepath =QLabel("Saving Path: ")
nameoffile = QLabel("Filename: ")
proname = QLabel("Project Name: ")
#progressbar, get values at the lower code
progcounter = QProgressBar()
#implememting the single parts to a full layout
firstlay = QHBoxLayout()
firstlay.addWidget(user_name_label)
firstlay.addWidget(user_name)
firstlay.addWidget (buttonOkName)
firstlay.addWidget(buttonhelp)
secondlay = QHBoxLayout()
secondlay.addWidget(user_anzahl_label)
secondlay.addWidget(user_anzahl)
secondlay.addWidget(buttonOkAnzahl)
layer10 = QHBoxLayout()
warning = QLabel("A breakinterval under 12 seconds can lead to irregular measurements.")
layer10.addWidget(warning)
layer10.addWidget(chooseTime)
layer11 = QHBoxLayout()
layer11.addWidget(minWahl)
layer11.addWidget(sekWahl)
layer11.addWidget(buttonOkZeit)
thirdlay = QHBoxLayout()
thirdlay.addWidget(user_zeit_label)
thirdlay.addWidget(user_zeit)
layer4 = QHBoxLayout()
notes = QLabel("Remarks/notes")
text_notes = QLineEdit()
layer4.addWidget(notes)
layer4.addWidget(text_notes)
layer5 =QHBoxLayout()
progres = QLabel("Progres: ")
layer5.addWidget(progres)
layer5.addWidget(progcounter)
layer5.addWidget(buttonStarter)
layer6= QHBoxLayout()
layer6.addWidget(proname)
layer6.addWidget(user_project)
layer7 = QHBoxLayout()
layer7.addWidget(Savepath)
layer8 = QHBoxLayout()
layer8.addWidget(nameoffile)
layer9 = QHBoxLayout()
layer9.addWidget(buttonPlot)
layer12 = QVBoxLayout()
layer12.addWidget(buttonClose)
#implementierung aller elemente in das zu zeugende Fenster
mainLayout = QGridLayout()
mainLayout.addLayout(firstlay, 1, 1, 1, 1)
mainLayout.addLayout(layer7, 2, 1, 1, 1)
mainLayout.addLayout(secondlay, 3 ,1, 1, 1)
mainLayout.addLayout(layer10, 5, 1, 1,1)
mainLayout.addLayout(layer11, 6, 1, 1, 1)
mainLayout.addLayout(thirdlay, 4,1, 1, 1)
mainLayout.addLayout(layer6, 7, 1, 1, 1)
mainLayout.addLayout(layer8, 9, 1, 1, 1)
mainLayout.addLayout(layer4, 8, 1, 1, 1)
mainLayout.addLayout(layer5, 10, 1, 1, 1)
mainLayout.addLayout(layer9, 11, 1, 1, 1)
mainLayout.addLayout(layer12,12,1,1,1)
#befehl das Fenster zu zeigen
window.setLayout(mainLayout)
window.show()
# funktion um Eingabe auf "NUr Letter" zu überprüfen
allowed_alpha = string.ascii_letters + string.whitespace
#following all functions will be defined
def on_minWahl_clicked():
global zeit
zeit_vorab = int(user_zeit.text())
zeit = zeit_vorab * 60
global Pause
Pause_vorab = int(user_zeit.text())
Pause = Pause_vorab * 60
global Zeitangabe
Zeitangabe = " minutes"
global intervall
intervall = 'Time [min]'
def on_sekWahl_clicked():
global zeit
zeit_vorab = int(user_zeit.text())
zeit = zeit_vorab
global Pause
Pause = int(user_zeit.text())
global Zeitangabe
Zeitangabe = " seconds"
global intervall
intervall = 'Time [sec]'
#function to confirm the user name
def on_OkName_clicked(self):
#convert the input to a global string for all other functions
name = str(user_name.text())
user = str(name)
# function to confirm the number of measurements
def on_OkAnzahl_clicked():
while True:
try:
#convert the input to a global integer for all other functions
global Anzahl
Anzahl = int(user_anzahl.text())
break
except Exception as inst:
print (type(inst) ) # the exception instance
print (inst.args) # arguments stored in .args
print (inst) # __str__ allows args to be printed directly
x, y = inst.args
print ('x =', x)
print ('y =', y)
#function to confirm the time for the break interval
def on_OkZeit_clicked():
while True:
try:
break
except Exception as inst:
print (type(inst) ) # the exception instance
print (inst.args) # arguments stored in .args
print (inst) # __str__ allows args to be printed directly
x, y = inst.args
print ('x =', x)
print ('y =', y)
intzeit = user_zeit.text()
if intzeit < 12:
alert = QMessageBox()
alert.setText('Intervall needs to be 12 seconds or longer!')
alert.exec_()
# start the main function/measurement function of this program by click on start button
def on_start_clicked():
while True: #function to run the measurements
try:
Measurement() #start the measurements
break
except IOError: # return information if an error appears
alert = QMessageBox()
alert.setText('Failed start the measurements!')
alert.exec_()
break
# function to check the saving path
def check_path():
dt = datetime.now()
global save_path
global user
user = user_name.text()
global filename
filename = filename = 'data_'+ dt.strftime("%Y%m%d_%I%M%S") + '.xlsx' # generates the file name
save_path = ('C:/Users/satzh/.spyder-py3/CPU Temp Measurements/' +user+'/') #generates the full saving path
a = os.path.exists(save_path) # function to found out if path exist
if a == True: # path exist = use the existing path
saveText = QLabel("Savepath: "+ save_path) #show saving path in the main window
layer7.addWidget(saveText)
#shows the saving path in a message box
alert = QMessageBox()
alert.setText("Userpath exist, Data will be saved into: %s"%save_path)
alert.exec_()
else: #path does not excist,
os.makedirs(save_path) # creats a new folder for the user, as save path
saveText = QLabel("Savepath: "+ save_path) # shows the new savepath in main window
layer7.addWidget(saveText)
#shows the saving path in a message box
alert = QMessageBox()
alert.setText("Path does not exist, file will be generated at:\n%s" %save_path)
alert.exec_()
name_end = QLabel(filename)
layer8.addWidget(name_end)
#check if their is a file withe the same name (not really probably, cause it's always named for the current time )
while True:
try:
path.exists(save_path,filename)
global wb # open an new excel workbook/file
wb = Workbook()
global sheet # creates in the workbook a new sheet
sheet = wb.active
sheet.title = "Book1"
wb.save(save_path+filename) # saves the workbook/file at the given path
#if the file exist, adding a 1 to the name and produce a new file
new_filename = 'filename'+'1'
global workbook
workbook = xlsxwriter.Workbook(save_path+ new_filename)
global worksheet
worksheet = workbook.add_worksheet()
break #if an error appears, it is creating a provisionally file
except TypeError:
workbook = xlsxwriter.Workbook(save_path + filename)
worksheet = workbook.add_worksheet()
break
else:
os.makedirs(save_path)
saveText = QLabel("Savepath: "+ save_path)
layer7.addWidget(saveText)
workbook = xlsxwriter.Workbook(save_path+filename)
worksheet = workbook.add_worksheet()
# measurement main function, collect values form the balances and write this in an Excel file
def Measurement():
c = 0
row = 2
col = 0
n = 0
# generates the title line and basic information for the Excel file
worksheet.write(0,0, 'Project Name:')
worksheet.write(0,1, user_project.text())
worksheet.write(0,2, "Remarks/Notes:")
worksheet.write(0,3, text_notes.text())
worksheet.write(0,4,"Number of measurements: %d"%Anzahl)
worksheet.write(0,5, "Breakinterval: %d"%Pause)
worksheet.write(1,0, 'Date' )
worksheet.write(1,1, 'Balance 1')
worksheet.write(1,2, 'Balance 2')
worksheet.write(1,3, 'Balance 3')
worksheet.write(1,4, 'Balance 4')
worksheet.write(1,5, 'Balance 5')
worksheet.write(1,6, 'Balance 6')
worksheet.write(1,7, 'Balance 7')
worksheet.write(1,8, 'Balance 8')
worksheet.write(1,9, 'Balance 9')
worksheet.write(1,10, 'Balance 10')
worksheet.set_column('A:B', 30)
worksheet.set_column('B:L',15)
#generates and show a message box that the measurements are started
alert = QMessageBox()
alert.setText("Measurements in progress")
alert.exec_()
# generating the value list for each balance for the print command
global lb1,lb2,lb3,lb4,lb5,lb6,lb7,lb8,lb9,lb10
lb1 =[]
lb2=[]
lb3=[]
lb4=[]
lb5=[]
lb6=[]
lb7=[]
lb8=[]
lb9=[]
lb10=[]
# measurments loop,
while c <= Anzahl:
dt = datetime.now() #create a shortcut for the date and time value
date_format = workbook.add_format({'num_format': 'd.mm.yyyy hh:mm:ss'}) #create the format which is written to the Excel file
worksheet.write_datetime(row,col, dt, date_format)
# the following notes are for all 10 measurements loops the same:
#Measurement loop for balance 1
while True:
try:
bal1 = serial.Serial('COM7' ,9600, bytesize=8, stopbits=1, timeout =0.1) #opens the serial/com-port for balance 1
#and set the bal1 variable for the port name/class
break #ends the loop when balance was opened
except OSError: #except Error for failing to open the serial port
alert = QMessageBox() #generates a messagebox to say that an error appears
alert.setText("Failed to open Balance 1")
alert.exec_()
break
while True:
try:
bal1.write(b'Z\r\n') #sends the first few characters to the balance, to activate her and make her responding to the Computer
s=bal1.read(17) #reads out the weight information
bal1.write(b'\x50') #sends the "print" command to the balance
t=list(str(s)) # creating a list of strings of the measurement values
liste = t
satz = "".join(liste [3:13] ) # reads the numbers out of the list and generate a new combined string
zwischen_string = satz # generates the values for the plotting lists
lb1.append(zwischen_string) # connect the value to the print list
gewicht = zwischen_string.replace('.', ',') #change the dot to a comma for the exported Value to excel
worksheet.write(row, 1 , gewicht) # writes the value to the Excel file
bal1.close() # close the serial port
break
except OSError:
alert = QMessageBox()
alert.setText("Failed to open collect and write the information form balance 1")
alert.exec_()
break
row += 1 #say the program, to use a new row for the next measuremnt in the Excel file
c +=1 # counter for measurements +1, next measurements for the while loop
time.sleep(Pause) # set the break, which is given by the user
#loop to set the maximum number of list elements to 10
#for the direct printing of the values
def check_plotlists():
if len(lb1)>=10:
del lb1[0]
if len(lb2)>=10:
del lb2[0]
if len(lb3)>=10:
del lb3[0]
if len(lb4)>=10:
del lb4[0]
if len(lb5)>=10:
del lb5[0]
if len(lb6)>=10:
del lb6[0]
if len(lb7)>=10:
del lb7[0]
if len(lb8)>=10:
del lb8[0]
if len(lb9)>=10:
del lb9[0]
if len(lb10)>=10:
del lb10[0]
app.processEvents() #allows to send back the percent value vor the processbar
prozent =(n/Anzahl)*100 #creating the percent value
progcounter.setValue(prozent) # gives back the percent value to the processbar
n +=1 #counter to generate the percent value
workbook.close() # close the excel file
layer9.addWidget(buttonOpenFile) # add the "open file" button
# öffnet am ende die angelegt datei
def on_OpenFile_clicked():
os.chdir
os.system('start excel.exe "%s%s"' % (save_path,filename ))
#helpFuntion, reads text from Help Instructions.txt file
#generating the help window, by clicking on the Help button
def open_help():
f = open('C:\\Users\\satzh\\.spyder-py3\\Help Instructions.txt',"r") #source for the help text
x = f.read() #reads the text from the txt. file
global windowhelp
windowhelp = QWidget()
windowhelp.setWindowTitle('Help')
buttonClose = QPushButton("Close")
buttonClose.clicked.connect(on_close_clicked)
helplayer1 = QVBoxLayout()
help_text= QPlainTextEdit()
help_text.appendPlainText(x)
help_text.zoomIn(4)
helplayer1.addWidget(help_text)
helplayer1.addWidget(buttonClose)
HelpLayout = QGridLayout()
HelpLayout.addLayout(helplayer1,1,1,1,1)
windowhelp.setLayout(HelpLayout)
windowhelp.setGeometry(300,300 ,800,480 )
windowhelp.show()
# close function for help window
def on_close_clicked():
windowhelp.close()
#generating plot menu
def on_plot_clicked():
#window generating
global printwindow
printwindow = QWidget()
printwindow.setWindowTitle('Plott')
#button generating
Balance1=QPushButton("Print Balance 1")
Balance2=QPushButton("Print Balance 2")
Balance3=QPushButton("Print Balance 3")
Balance4=QPushButton("Print Balance 4")
Balance5=QPushButton("Print Balance 5")
Balance6=QPushButton("Print Balance 6")
Balance7=QPushButton("Print Balance 7")
Balance8=QPushButton("Print Balance 8")
Balance9=QPushButton("Print Balance 9")
Balance10=QPushButton("Print Balance 10")
Balance11=QPushButton("Print All together")
Pclose = QPushButton("Close")
#generate all buttons for the print menu
printLayout1 = QHBoxLayout()
printLayout1.addWidget(Balance1)
printLayout1.addWidget(Balance2)
printLayout1.addWidget(Balance3)
printLayout2 = QHBoxLayout()
printLayout2.addWidget(Balance4)
printLayout2.addWidget(Balance5)
printLayout2.addWidget(Balance6)
printLayout3 = QHBoxLayout()
printLayout3.addWidget(Balance7)
printLayout3.addWidget(Balance8)
printLayout3.addWidget(Balance9)
printLayout4 = QHBoxLayout()
printLayout4.addWidget(Balance10)
printLayout4.addWidget(Balance11)
printLayout5 = QHBoxLayout()
printLayout5.addWidget(Pclose)
#generate the printlayout
printFinal = QVBoxLayout()
printFinal.addLayout(printLayout1)
printFinal.addLayout(printLayout2)
printFinal.addLayout(printLayout3)
printFinal.addLayout(printLayout4)
printFinal.addLayout(printLayout5)
#connect the print buttons to print orders
printwindow.setLayout(printFinal)
printwindow.show()
Balance1.clicked.connect(print_1)
Balance2.clicked.connect(print_2)
Balance3.clicked.connect(print_3)
Balance1.clicked.connect(print_4)
Balance5.clicked.connect(print_5)
Balance6.clicked.connect(print_6)
Balance7.clicked.connect(print_7)
Balance8.clicked.connect(print_8)
Balance9.clicked.connect(print_9)
Balance10.clicked.connect(print_10)
Balance11.clicked.connect(print_11)
Pclose.clicked.connect(on_pclose_clicked)
#closing the print menue
def on_pclose_clicked():
printwindow.close()
#printing for balancce 1
#following notes are for all print funtions the same
def print_1():
fig = plt.figure() #generates the basic figure for the plot, like the new window
plt.plot(lb1) #give the dates which should be printed
plt.title('Hydrogen Evolution') #generates the head-title
plt.ylabel('Weight [g]') # name fo the y cooridnates
plt.xlabel(intervall) # name for the x intervall (is defined by the choose between seconds and minutes functions above)
ax = plt.subplot(111)
ax.legend()
plt.ylim(-1, 425) # defined the intervall on the y coordinates
xmax= Anzahl*Pause #defined the intervall on the x coordinates
plt.xlim(0, xmax )
plt.show()
timestamp = time.strftime("%d%m%Y-%H%M%S") #generates part of the saving name
command = "fig.savefig('"+timestamp+"_h2evo_multiplot.png', dpi=200)" # saves the figure
exec(command)
fig.savefig('multiplot.png' , dpi=200)
#closing the Mainwindow and main Process
def close_all():
window.close()
sys.exit(app.exec_())
exit()
#connect the Buttons with their functions
buttonOkName.clicked.connect(on_OkName_clicked) #confirm name with checking user name
buttonOkName.clicked.connect( check_path) #confrim name with function to check saving path
buttonOkAnzahl.clicked.connect(on_OkAnzahl_clicked) #confirm number of messurements
buttonOkZeit.clicked.connect(on_OkZeit_clicked) #confirm time input
buttonStarter.clicked.connect(on_start_clicked)
buttonOpenFile.clicked.connect(on_OpenFile_clicked)
minWahl.toggled.connect(on_minWahl_clicked)
sekWahl.toggled.connect(on_sekWahl_clicked)
buttonhelp.clicked.connect(open_help)
buttonPlot.clicked.connect(on_plot_clicked)
buttonClose.clicked.connect(close_all)
app.exec_()
In an other thread here I found the following: QThread can run an event loop, QRunnable doesn't have one so don't use it for tasks designed to have an event loop. From: C++/Qt - QThread vs QRunnable
suppose you want to run a function named my_func using a thread.
import threading
my_thread = threading.Thread(target=my_func, args=())
my_thread.start()
if you want the thread to exit after the whole program exits, type this before starting
my_thread.setDaemon(True)
if you want to pass variables that your function need (arguments). suppose you have a function add(num1, num2) that returns num1+num2, you pass num1 and num2 in the args in a list as follow:
add_thread = threading.Thread(target=add, args=([4, 6]))