Connect to serial from a PyQt GUI
Asked Answered
F

2

9

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()
Fragrant answered 8/3, 2019 at 20:21 Comment(1)
@eyllanesc Ok, when I press a button, I want the connection to Arduino be made (open serial), when I press another button close connection (close serial) but I have no idea how to make thisFragrant
W
18

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_())
Worl answered 8/3, 2019 at 21:48 Comment(1)
thank you a lot, I'll use your code, but for my peace, is possible to connect to serial from a button from my "main window" class? in the same time my "TestThread" class to run fine.. can you give me an example?Fragrant
G
6

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_())
Gwendolyngweneth answered 30/12, 2020 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.