I am really new to programming and I recently had to do a GUI with PyQt. Basicly what it does is reading reading some data coming from an Arduino on the serial port and updates a graph every 100 values sent by the Arduino. There is also a log function to log the data into a formatted file. Everything seemed to work fine but now my program stops working and a message saying python stopped working pops up. I tried a few time and the same message pop up at random moments. Could you please help me figure this out?
Mainly from what I read on some other forums and this one, I think the problem is in my PlotThread class which is used by my main window class. I don't know what that problem is though and how to fix it. Here are the Window and PlotThread class.
Thanks,
Simon Bellemare
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.log_x = np.array([])
self.log_y = np.array([])
self.test_id = ''
self.plot_thread = PlotThread()
self.plot_thread.main = self
self.figure = plt.figure()
# Canvas Widget
self.canvas = FigureCanvas(self.figure)
# Navigation Widget
self.toolbar = NavigationToolbar(self.canvas, self)
# Button to control plot function
self.control_plot = QPushButton('Start')
self.control_plot.clicked.connect(self.plot)
# Button to log data
self.log_data = QPushButton('Log')
self.log_data.clicked.connect(self.log_file)
# Progress bar to indicate data harvesting speed
self.progressBar = QProgressBar(self)
self.progressBar.setGeometry(QRect(10, 10, 200, 25))
self.progressBar.move(500, 10)
# Layout
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.control_plot)
layout.addWidget(self.log_data)
self.setLayout(layout)
# Initialising the plot figure
self.fig1 = self.figure.add_subplot(111)
rect = self.fig1.patch
rect.set_facecolor('white')
self.fig1.set_xlabel('time [s]')
self.fig1.set_ylabel('RSSI [dBm]')
self.fig1.grid(True)
self.fig1.spines["top"].set_visible(False)
self.fig1.spines["right"].set_visible(False)
self.fig1.spines["bottom"].set_visible(False)
self.fig1.spines["left"].set_visible(False)
def log_file(self):
directory = ''.join(filter(str.isalpha, self.test_id))
# Creating the directory if it doesn't exist
create_directory(directory)
# Creating the file if it doesn't exist
if not os.path.isfile(
'C:\\Users\\Simon\\Documents\\Ete2017\\PythonCode\\main\\{0}\\{1}.txt'.format(directory,
self.test_id)):
log = open('C:\\Users\\Simon\\Documents\\Ete2017\\PythonCode\\main\\{0}\\{1}.txt'.format(directory,
self.test_id),
'w')
# Creating the header
log.write(' ' * 9 + 'Time [s]' + ' ' * 9 + '|' + ' ' * 4 + 'RSSI [dBm] \n')
else:
log = open('C:\\Users\\Simon\\Documents\\Ete2017\\PythonCode\\main\\{0}\\{1}.txt'.format(directory,
self.test_id),
'a')
# Logging the data
for i in range(np.size(self.log_x)):
x = str(self.log_x[i])
space_num = 24 - len(x)
log.write(' ' * space_num + x + " |" + ' ' * 5 + str(self.log_y[i]) + ' \n')
# Reinitialising logging
self.log_x = np.array([])
self.log_y = np.array([])
def pause(self):
self.plot_thread.terminate()
self.control_plot.setText('Start')
self.control_plot.clicked.connect(self.plot)
self.log_data.setText('Log')
self.log_data.clicked.connect(self.log_file)
def plot(self):
self.control_plot.setText('Pause')
self.control_plot.clicked.connect(self.pause)
self.log_data.setText('')
self.log_data.clicked.connect(self.wait)
self.plot_thread.start()
def wait(self):
pass
class PlotThread(QThread):
def __init__(self):
QThread.__init__(self)
self.main = ''
self.t_start = 0
self.ser = ser
# Replace by expected values at time 0s
self.xf = 0
self.yf = -43
def run(self):
while self.isRunning():
print("Starting to plot")
self.ser.flushInput()
self.t_start = time.time()
# Preallocating memory for the arrays
x = np.empty([100])
y = np.empty([100])
# Initializing start values
x[0] = self.xf
y[0] = self.yf
self.main.progressBar.setValue(1)
# Reading data from the Arduino
for i in range(99):
reading = str(ser.readline())
while not reading[3:5].isdigit():
reading = str(ser.readline())
y[i + 1] = -int(reading[3:5])
x[i + 1] = self.xf + (time.time() - self.t_start)
print(x[i])
self.main.progressBar.setValue(i + 1)
# Preparing start values for next iteration
self.xf = x[99]
self.yf = y[99]
# Preparing the log variables
self.main.log_x = np.append(self.main.log_x, x)
self.main.log_y = np.append(self.main.log_y, y)
# Plotting raw data
self.main.fig1.plot(x, y, 'r--')
# Filtering raw data
poly8 = np.polyfit(x, y, 8)
xGraph = np.linspace(x[0], x[99], num=200)
yGraph = np.polyval(poly8, xGraph)
# Graphing filtered data
self.main.fig1.plot(xGraph, yGraph, 'b')
# Saving the graph into a file
timer = self.xf + time.time() - self.t_start
# Scaling the axes
if timer > 80:
print("Scaling the axes")
self.main.fig1.set_xlim([int(timer) - 80, timer])
# refresh canvas
self.main.canvas.draw()
self.main.canvas.flush_events()
You may not interact with the GUI in a non-main thread:
As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.