Reading data from the serial port hangs my GUI

3

I'm doing an access control application in Python. I have a window in PyQt5, which has a running clock and a connection to an Arduino, which is the one that will read a card through its corresponding NFC.

The fact is that for the two tasks (clock and NFC) to work at the same time, I'm trying with 2 timer, one for the clock and one for Arduino, but as soon as I put the one of the Arduino, I'm left hanging Program. If at that time I read card, apparently it works, but the GUI is blocked. I'll put the code here to see if someone can help me out.

import sys, re, time
import serial
import os.path #Comprobar si la BBDD existe
from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog, QMessageBox, 
QTableWidget, QTableWidgetItem, QLineEdit
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtCore import Qt
from time import localtime, strftime


#Clase heredada de QMainWindow (Constructor de ventanas)
class Ventana(QDialog):
    #Método constructor de la clase
    def __init__ (self):
        #Iniciar objeto QDialog
        QDialog.__init__(self)
        #Cargar configuración del archivo .ui en el objeto
        uic.loadUi('fichajes.ui', self)

        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.Tick)
        self.timer.start(1000)
        self.timer2 = QtCore.QTimer(self)
        self.timer2.timeout.connect(self.LeerTarjeta)
        self.timer2.start(1000)

        self.btnSalir.clicked.connect(self.Salir)
        #self.lblTime.setText(strftime("%H:%M:%S", localtime()))
        #self.lblDate.setText(strftime("%d-%m-%Y", localtime()))


    def Tick(self):
        # get the current local time from the PC
        self.lblTime.setText(strftime("%H:%M:%S", localtime()))
        self.lblDate.setText(strftime("%d-%m-%Y", localtime()))

    def LeerTarjeta(self):

        # Conectar con Arduino
        try:
            arduino = serial.Serial('COM4', 9600)
            rawString = arduino.readline()
            print(rawString)
            self.txtUID.setText(str(rawString)[7:-5])
            self.Tick()
        except Exception:
            # Colocado Exception para que detecte el CTRL + C como interrupción
            print("Problem with the serial port" + Exception)

    def Salir(self):
        exit()

# Instancia para iniciar la aplicación (obligatorio pasar argumento)
app = QApplication(sys.argv)
#Crear un objeto de la clase
ventana = Ventana()
#Mostrar ventana
ventana.show()

#Ejecutar la aplicación
app.exec_()
    
asked by visent 07.07.2018 в 21:56
source

2 answers

3

In this answer I will show 2 solutions:

  • The first is to implement a thread dedicated to reading the serial since in general you do not know when you have the data, and in your case I see that you are opening the port every period of time.
  • import sys
    
    import serial
    import threading
    
    from PyQt5 import uic, QtCore, QtWidgets
    
    class Helper(QtCore.QObject):
        textSignal = QtCore.pyqtSignal(str)
    
    def leer_puerto(port, baudrate, helper):
        arduino = serial.Serial(port, baudrate)
        while arduino.isOpen():
            rawString = arduino.readline()
            helper.textSignal.emit(str(rawString)[7:-5])
    
    
    class Ventana(QtWidgets.QDialog):
        def __init__ (self, parent=None):
            super(Ventana, self).__init__(parent)
            uic.loadUi('fichajes.ui', self)
            self.timer = QtCore.QTimer(self)
            self.timer.timeout.connect(self.tick)
            self.timer.start(1000)
            self.btnSalir.clicked.connect(self.close)
            port = "COM4"
            self.helper = Helper()
            self.helper.textSignal.connect(self.txtUID.setText)
            threading.Thread(target=leer_puerto, args=(port, 9600, self.helper), daemon=True).start()
    
        @QtCore.pyqtSlot()
        def tick(self):
            self.lblTime.setText(QtCore.QTime.currentTime().toString("hh:mm:ss"))
            self.lblDate.setText(QtCore.QDate.currentDate().toString("dd-MM-yyyy"))
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        ventana = Ventana()
        ventana.show()
        sys.exit(app.exec_())
    
  • The second solution is to use QSerialPort, this class is specialized in handling the serial and friendly with the eventloop of Qt.
  • import sys
    
    from PyQt5 import uic, QtCore, QtWidgets, QtSerialPort
    
    
    class Ventana(QtWidgets.QDialog):
        def __init__ (self, parent=None):
            super(Ventana, self).__init__(parent)
            uic.loadUi('fichajes.ui', self)
            self.timer = QtCore.QTimer(self)
            self.timer.timeout.connect(self.tick)
            self.timer.start(1000)
            self.btnSalir.clicked.connect(self.close)
            port = "COM4"
            self.arduino = QtSerialPort.QSerialPort(port, self)
            self.arduino.setBaudRate(QtSerialPort.QSerialPort.Baud9600)
            self.arduino.readyRead.connect(self.onReadyRead)
    
            if not self.arduino.open(QtSerialPort.QSerialPort.ReadWrite):
                # http://doc.qt.io/qt-5/qserialport.html#SerialPortError-enum
                print("Problema con el puerto", self.arduino.error())
    
        @QtCore.pyqtSlot()
        def tick(self):
            self.lblTime.setText(QtCore.QTime.currentTime().toString("hh:mm:ss"))
            self.lblDate.setText(QtCore.QDate.currentDate().toString("dd-MM-yyyy"))
    
        @QtCore.pyqtSlot()
        def onReadyRead(self):
            while self.arduino.canReadLine():
                rawString = self.arduino.readLine()
                self.txtUID.setText(str(rawString)[7:-5])
    
        def closeEvent(self, event):
            if self.arduino.isOpen():
                self.arduino.close()
            super(Ventana, self).closeEvent(event)
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        ventana = Ventana()
        ventana.show()
        sys.exit(app.exec_())
    
        
    answered by 08.07.2018 / 04:24
    source
    -1

    1) The easiest way to use a thread is to instantiate an object of the Thread class with a target function and make a call to its start () method.

    import threading
    def worker():
        """funcion que realiza el trabajo en el thread"""
        print 'Estoy trabajando para Genbeta Dev'
        return
    threads = list()
    for i in range(3):
        t = threading.Thread(target=worker)
        threads.append(t)
        t.start()
    

    2) See that the parameters match any type of object can be passed as a parameter to a thread.

    import threading
    def worker(count):
        """funcion que realiza el trabajo en el thread"""
        print "Este es el %s trabajo que hago hoy para Genbeta Dev" % count
        return
    threads = list()
    for i in range(3):
        t = threading.Thread(target=worker, args=(i,))
        threads.append(t)
        t.start()
    

    3) The output of the previous example would be like the one that follows would come out the following acceptance code:

    Este es el 0 trabajo que hago hoy para Genbeta Dev
    Este es el 1 trabajo que hago hoy para Genbeta Dev
    Este es el 2 trabajo que hago hoy para Genbeta Dev
    

    4) I forgot, by the way, that arguments can be used to name the threads that we create, although it is not necessary. Each instance of the Thread class has an assigned name by default.

    Naming threads can be useful, for example, when clarifying our code.

    import threading
    import time
    def worker():
        print threading.currentThread().getName(), 'Lanzado'
        time.sleep(2)
        print threading.currentThread().getName(), 'Deteniendo'
    def servicio():
        print threading.currentThread().getName(), 'Lanzado'
        print threading.currentThread().getName(), 'Deteniendo'
    t = threading.Thread(target=servicio, name='Servicio')
    w = threading.Thread(target=worker, name='Worker')
    z = threading.Thread(target=worker)
    w.start()
    z.start()
    t.start()
    

    5) For the following we show The exit of the previous code would be:

    Worker Lanzado
    Thread-1 Lanzado
    Servicio Lanzado
    Worker Deteniendo
    Thread-1 Deteniendo
    Servicio Deteniendo
    

    6) If we are going to debug or log something related to threads, it is best that we use the logging module for this, we place the following commands:

    import threading
    import logging
    import time
    logging.basicConfig( level=logging.DEBUG,
        format='[%(levelname)s] - %(threadName)-10s : %(message)s')
    def worker():
        logging.debug('Lanzado')
        time.sleep(2)
        logging.debug('Deteniendo')
    w = threading.Thread(target=worker, name='Worker')
    w.start()
    

    7) The logging module supports the inclusion of the name of the thread natively, the output of one of the previous messages would be similar to this:

    [DEBUG] - Worker : Lanzado
    

    8) To raise a thread as a daemon we just have to invoke its setDaemon () method by passing it the True argument.

    import threading
    import logging
    import time
    logging.basicConfig( level=logging.DEBUG,
        format='[%(levelname)s] - %(threadName)-10s : %(message)s')
    def daemon():
        logging.debug('Lanzado')
        time.sleep(2)
        logging.debug('Deteniendo')
    d = threading.Thread(target=daemon, name='Daemon')
    d.setDaemon(True)
    d.start()
    

    conclusion:

    Today we have made a first contact with the threading module of Python and we have learned to use the logging object to use debug messages with our threads.

        
    answered by 08.07.2018 в 10:15