Reputation: 344
I have a PyQt program that hosts 2 widgets. The idea is to interface with my Arduino and display the Arduinos info in the program. I can't really attach my whole program but I'll give the highlights.
The Arduino takes a command over serial via ser.write
and returns the subsequent information using ser.read()
So a simple function to continuously read the information from the Arduino would be
while True:
ser.write(command.encode()
time.sleep(.1)
resp=ser.read()
data=struct.unpack('<b',resp)
Now I want to use the information in data
in my PyQt program however I cannot continuously run a loop in my Qt program because it will never display the program. I've tried using QThread
by making a demo program however it crashes with error QThread: Destroyed while thread is still running
. This is my demo program which should have similar functionality to the actual program.
import sys
import urllib
import urllib.request
import serial
import time
from PyQt4 import QtCore, QtGui
class CmdThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
BASIC="\x24\x4d\x3c\x00"
self.ser=serial.Serial()
#ser.port="COM12"
self.ser.port='COM12'
self.ser.baudrate=115200
self.ser.bytesize = serial.EIGHTBITS
self.ser.parity = serial.PARITY_NONE
self.ser.stopbits = serial.STOPBITS_ONE
self.ser.timeout = 0
self.ser.xonxoff = False
self.ser.rtscts = False
self.ser.dsrdtr = False
self.ser.writeTimeout = 2
self.ser.open()
print('Initializing in 10 seconds...')
time.sleep(10)
def run(self):
self.ser.write(self.BASIC.encode())
time.sleep(0.1)
resp=self.ser.read()
datalength=struct.unpack('<b',resp)[0]
data=self.ser.read(datalength+1)
data=data[4:-1]
temp=struct.unpack('<'+'h'*(int(len(data)/2)),data)
self.ser.flushInput()
self.ser.flushOutput()
print((temp[0]/10,temp[1]/10,temp[2]))
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_cmd)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)
def start_cmd(self):
downloader = CmdThread()
downloader.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Can anyone explain why this is happening or maybe a solution on how to incorporate the simpler while
loop into a Qt widget? Thanks so much!
Upvotes: 1
Views: 2010
Reputation: 11849
You need to store a reference to the QThread
so that it isn't garbage collected. Like this:
def start_cmd(self):
self.downloader = CmdThread()
self.downloader.start()
However be warned that clicking the button a second time will replace the original reference with a new one. And so the first thread may get garbage collected (which is probably fine if it is finished). You may want to eventually consider a more complex architecture where you always have a thread running and send commands from the main thread to the arduino thread via Qt signals/slots.
On another note, I assume at some point you will be using the results from the arduino to update a widget. Please make sure you don't directly access the widget from the thread (Qt GUI objects should only ever be accessed from the main thread). Instead you need to emit a signal (with a parameter that contains your data) from the thread which connects to a slot in the main thread. This slot is then able to safely access the GUI objects.
This shows a more complex example with two way communication between the main thread and a worker thread (albeit without sending data along with the signal emission, but that is a reasonably trivial change)
Upvotes: 1