Connect to serial from a PyQt GUI
I write a program to that send and recive data from serial, but I have a problem, I want to create a function "connect()" or a class, and when I press a button, the function is executed, but if I create this function in "MainWindow" class, variable "ser" from "TestThread" class become uninitialized, can you help me?
import sys
import serial
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.uic import loadUi
ser = serial.Serial('/dev/tty.usbmodem14201', 9600, timeout=1)
class TestThread(QThread):
serialUpdate = pyqtSignal(str)
def run(self):
while ser.is_open:
QThread.sleep(1)
value = ser.readline().decode('ascii')
self.serialUpdate.emit(value)
ser.flush()
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi('/Users/bogdanvesa/P2A_GUI/mainwindow.ui', self)
self.thread = TestThread(self)
self.thread.serialUpdate.connect(self.handleSerialUpdate)
self.connect_btn.clicked.connect(self.connectSer)
self.lcd_EBtn.clicked.connect(self.startThread)
def startThread(self):
self.thread.start()
def handleSerialUpdate(self, value):
print(value)
self.lcd_lineEdit.setText(value)
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Instead of using pySerial + thread it is better to use QSerialPort
that is made to live with the Qt event-loop:
from PyQt5 import QtCore, QtWidgets, QtSerialPort
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.message_le = QtWidgets.QLineEdit()
self.send_btn = QtWidgets.QPushButton(
text="Send",
clicked=self.send
)
self.output_te = QtWidgets.QTextEdit(readOnly=True)
self.button = QtWidgets.QPushButton(
text="Connect",
checkable=True,
toggled=self.on_toggled
)
lay = QtWidgets.QVBoxLayout(self)
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(self.message_le)
hlay.addWidget(self.send_btn)
lay.addLayout(hlay)
lay.addWidget(self.output_te)
lay.addWidget(self.button)
self.serial = QtSerialPort.QSerialPort(
'/dev/tty.usbmodem14201',
baudRate=QtSerialPort.QSerialPort.Baud9600,
readyRead=self.receive
)
@QtCore.pyqtSlot()
def receive(self):
while self.serial.canReadLine():
text = self.serial.readLine().data().decode()
text = text.rstrip('\r\n')
self.output_te.append(text)
@QtCore.pyqtSlot()
def send(self):
self.serial.write(self.message_le.text().encode())
@QtCore.pyqtSlot(bool)
def on_toggled(self, checked):
self.button.setText("Disconnect" if checked else "Connect")
if checked:
if not self.serial.isOpen():
if not self.serial.open(QtCore.QIODevice.ReadWrite):
self.button.setChecked(False)
else:
self.serial.close()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I have used the above code redesigned, so it has a mainwindow
with a menubar
and a statusbar
. I also added the QSerialPortInfo class. This version will find active ports and display them on the status bar.
I only tested this on rpi 4 and Windows 10.
import sys
from PyQt5 import QtCore, QtWidgets, QtSerialPort
from PyQt5.QtWidgets import QApplication, QMainWindow ,QWidget ,QToolBar ,QHBoxLayout, QAction ,QStatusBar ,QLineEdit ,QPushButton ,QTextEdit , QVBoxLayout
from PyQt5.QtCore import Qt , pyqtSignal
from PyQt5.QtSerialPort import QSerialPortInfo
class AddComport(QMainWindow):
porttnavn = pyqtSignal(str)
def __init__(self, parent , menu):
super().__init__(parent)
menuComporte = menu.addMenu("Comporte")
info_list = QSerialPortInfo()
serial_list = info_list.availablePorts()
serial_ports = [port.portName() for port in serial_list]
if(len(serial_ports)> 0):
antalporte = len(serial_ports)
antal = 0
while antal < antalporte:
button_action = QAction(serial_ports[antal], self)
txt = serial_ports[antal]
portinfo = QSerialPortInfo(txt)
buttoninfotxt = " Ingen informationer"
if portinfo.hasProductIdentifier():
buttoninfotxt = ("Produkt specifikation = " + str(portinfo.vendorIdentifier()))
if portinfo.hasVendorIdentifier():
buttoninfotxt = buttoninfotxt + (" Fremstillers id = "+ str(portinfo.productIdentifier()))
button_action = QAction( txt , self)
button_action.setStatusTip( buttoninfotxt)
button_action.triggered.connect(lambda checked, txt = txt: self.valgAfComportClick(txt))
menuComporte.addAction(button_action)
antal = antal +1
else:
print("Ingen com porte fundet")
def valgAfComportClick(self , port):
self.porttnavn.emit(port)
def closeEvent(self, event):
self.close()
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
portname = "None"
self.setStatusBar(QStatusBar(self))
menu = self.menuBar()
comfinder = AddComport(self , menu)
comfinder.porttnavn.connect(self.valgAfComport)
self.setWindowTitle("Serial port display / send")
self.message_le = QLineEdit()
self.send_btn = QPushButton(
text="Send",
clicked=self.send
)
self.output_te = QTextEdit(readOnly=True)
self.button = QPushButton(
text="Connect",
checkable=True,
toggled=self.on_toggled
)
lay = QVBoxLayout(self)
hlay = QHBoxLayout()
hlay.addWidget(self.message_le)
hlay.addWidget(self.send_btn)
lay.addLayout(hlay)
lay.addWidget(self.output_te)
lay.addWidget(self.button)
widget = QWidget()
widget.setLayout(lay)
self.setCentralWidget(widget)
self.serial = QtSerialPort.QSerialPort(
portname,
baudRate=QtSerialPort.QSerialPort.Baud9600,
readyRead=self.receive)
@QtCore.pyqtSlot()
def receive(self):
while self.serial.canReadLine():
text = self.serial.readLine().data().decode()
text = text.rstrip('\r\n')
self.output_te.append(text)
@QtCore.pyqtSlot()
def send(self):
self.serial.write(self.message_le.text().encode())
@QtCore.pyqtSlot(bool)
def on_toggled(self, checked):
self.button.setText("Disconnect" if checked else "Connect")
if checked:
if not self.serial.isOpen():
self.serial.open(QtCore.QIODevice.ReadWrite)
if not self.serial.isOpen():
self.button.setChecked(False)
else:
self.button.setChecked(False)
else:
self.serial.close()
def valgAfComport(self , nyport):
seropen = False
if self.serial.isOpen():
seropen = True
self.serial.close()
self.serial.setPortName(nyport)
if seropen:
self.serial.open(QtCore.QIODevice.ReadWrite)
if not self.serial.isOpen():
self.button.setChecked(False)
print(nyport)
def closeEvent(self, event):
self.serial.close()
print("Comport lukket")
# print(comporttxt)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())