Jas
Jas

Reputation: 73

Terminal with Threads using PyQt

I'm trying to build a PyQt app which (among other things) has the ability via a QTextEdit Box to function like a serial terminal program (HyperTerminal, TeraTerm, etc.) I've read through a few examples from the PySerial page and I think I've managed to get the receive data thread working properly but maybe not as efficiently as possible.

My problem is how do I take the last typed character in the QTextEdit box and send that out the serial connection? I've tried using the textChanged signal that QTextEdit emits, but that then sends everything that I type AND that it receives. I've tried setting up an eventFilter in my main GUI class, but I can't figure out how to get that over to the serial function in another file. Do I want to have a separate thread that listens for a signal emitted from the eventFilter? How do I do that? Is there a more elegant way to do this?

I'm sure I've just managed to overthink this and the solution is simple, but I'm somewhat struggling with it. I'll attach the relevant code snippets (not a full code set) and perhaps somebody can point me in the right direction. If anybody also thinks that the threading that I'm doing could be done in a more efficient manner, then please relay that to me as well!

Thanks for any help that anybody can provide!

Main File:

import sys
from PyQt4 import QtGui
from MainGUI import TestGUI
from SerialClasses import *
from SerialMiniterm import *

class StartMainWindow(QtGui.QMainWindow):      
    def __init__(self, parent=None):
        super(StartMainWindow, self).__init__(parent)
        self.ui = TestGUI()
        self.ui.setupUi(self)    
        self.ui.serialTextEditBox.installEventFilter(self)

    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.KeyPress and source is self.ui.serialTextEditBox):
            # print some debug statements to console
            if (event.key() == QtCore.Qt.Key_Tab):
                print ('Tab pressed')
            print ('key pressed: %s' % event.text())
            print ('code pressed: %d' % event.key())
            # do i emit a signal here?  how do i catch it in thread?
            self.emit(QtCore.SIGNAL('transmitSerialData(QString)'), event.key())
            return True
        return QtGui.QTextEdit.eventFilter(self, source, event)   

    def serialConnectCallback(self):
        self.miniterm = SerialMiniterm(self.ui, self.SerialSettings)
        self.miniterm.start()
        temp = self.SerialSettings.Port + 1
        self.ui.serialLabel.setText("<font color = green>Serial Terminal Connected on COM%d" % temp) 

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    app.setStyle("Cleanlooks")
    myapp = StartMainWindow()
    myapp.show()
    sys.exit(app.exec_())

SerialMiniterm.py:

import serial
from PyQt4 import QtGui, QtCore

def character(b):
    return b

class SerialMiniterm(object):
    def __init__(self, ui, SerialSettings):
        self.SerialSettings = SerialSettings
        self.ui = ui
        self.serial = serial.Serial(self.SerialSettings.Port, self.SerialSettings.BaudRate, parity=self.SerialSettings.Parity, rtscts=self.SerialSettings.RTS_CTS, xonxoff=self.SerialSettings.Xon_Xoff, timeout=1)
        self.repr_mode = self.SerialSettings.RxMode
        self.convert_outgoing = self.SerialSettings.NewlineMode
        self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
        self.dtr_state = True
        self.rts_state = True
        self.break_state = False

    def _start_reader(self):
        """Start reader thread"""
        self._reader_alive = True
        self.receiver_thread = ReaderThread(self.alive, self._reader_alive, self.repr_mode, self.convert_outgoing, self.serial)
        self.receiver_thread.connect(self.receiver_thread, QtCore.SIGNAL("updateSerialTextBox(QString)"), self.updateTextBox)
        self.receiver_thread.start()

    def _stop_reader(self):
        """Stop reader thread only, wait for clean exit of thread"""
        self._reader_alive = False
        self.receiver_thread.join()

    def updateTextBox(self, q):
        self.ui.serialTextEditBox.insertPlainText(q)
        self.ui.serialTextEditBox.moveCursor(QtGui.QTextCursor.End)
        #print "got here with value %s..." % q

    def start(self):
        self.alive = True
        self._start_reader()
        # how do i handle transmitter thread?

    def stop(self):
        self.alive = False

    def join(self, transmit_only=False):
        self.transmitter_thread.join()
        if not transmit_only:
            self.receiver_thread.join()

class ReaderThread(QtCore.QThread):       
    def __init__(self, alive, _reader_alive, repr_mode, convert_outgoing, serial, parent=None):
        QtCore.QThread.__init__(self, parent)
        self.alive = alive
        self._reader_alive = _reader_alive
        self.repr_mode = repr_mode
        self.convert_outgoing = convert_outgoing
        self.serial = serial

    def __del__(self):
        self.wait()

    def run(self):
        """loop and copy serial->console"""
        while self.alive and self._reader_alive:
            data = self.serial.read(self.serial.inWaiting())
            if data:                            #check if not timeout
                q = data
                self.emit(QtCore.SIGNAL('updateSerialTextBox(QString)'), q)

Upvotes: 2

Views: 2640

Answers (1)

Luke
Luke

Reputation: 11644

Something like this?

from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])

class Terminal(QtGui.QPlainTextEdit):
    def keyPressEvent(self, event):
        print event.text()
        return QtGui.QPlainTextEdit.keyPressEvent(self, event)

term = Terminal()
term.show()

Upvotes: 2

Related Questions