Qserial

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
kitebuggy
User
Beiträge: 29
Registriert: Donnerstag 5. August 2021, 21:11

Hallo

Ich habe ein Problem in meinem Programm zur Kommunikation mit dem Arduino. Ich habe mich hieran orientiert: https://iosoft.blog/pyqt-serial-terminal-code/

Das Programm funktioniert auch. Aber ich will einen Befehl an den Arduino schicken und die Antwort in eine Variable schreiben, um diese weiterzuverarbeiten.
Wenn ich nun aber im Slot

Code: Alles auswählen

def receive(self):
        while self.serial.canReadLine():
            text = self.serial.readLine().data().decode()
            text = text.rstrip('\r\n')
            self.output_te.append(text)
            print('Output: ',text)
            self.variable=text
einfach schreibe

Code: Alles auswählen

self.variable=text
erhalte ich die Letzte Zeile vor dem Befehl, da die Antwort des Arduinos erst später kommt.
Leider habe ich zu dem Thema nur Sachen gefunden, die alle auf ein Textedit schreiben und da ist der Zeitpunkt, zu dem die Antwort kommt egal.

Was muss ich da tun, resp. in was sollte ich mich reinlesen, um zu verstehen, was ich da falsch mache? Mit warteschleifen kann ich ja nicht arbeiten, da sonst die GUI solange blockiert ist bis die Antwort kommt.

Vielen Dank für eure Anregungen.

Hier mein Code:

Code: Alles auswählen

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)
        self.setFixedSize(650,650)

  
        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
        )
        self.getvar=QPushButton('Variable',clicked=self.prtvar)
        self.getvar.setShortcut('Ctrl+G')
        
        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)
        lay.addWidget(self.getvar)
        
        widget = QWidget()
        widget.setLayout(lay)
        self.setCentralWidget(widget)

        self.serial = QtSerialPort.QSerialPort(
            portname,
            baudRate=QtSerialPort.QSerialPort.Baud115200,
            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)
            print('Output: ',text)
            self.variable=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()
            
    @QtCore.pyqtSlot()
    def prtvar(self):
        print('Variable: ',self.variable)
  
    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_())
i
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie eigentlich alles in GUIs muss man Ereignis-getrieben arbeiten. In diesem Fall zb über readyRead: https://doc.qt.io/qt-6/qiodevice.html#readyRead, und das ganze mit Zustandsmaschinen und Timern Paaren.
kitebuggy
User
Beiträge: 29
Registriert: Donnerstag 5. August 2021, 21:11

Danke. Wo finde ich ein gutes Tutorial zu diesem Thema, da wie gesagt, die meisten Sachen, die ich dazu gefunden habe, in ein Textedit schreiben und nicht eine Variable füllen?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Keine Ahnung. Ich brauche keines, und kenne darum keines. Hast du mal gesucht mit den ergeben Stichworten? Wie in meinem Link zu sehen, ist das Thema im übrigen allgemein auf IO bezogen, also auch Sockets. Tutorials dazu sollten also auch funktionieren. Muss man kreativ werden.
kitebuggy
User
Beiträge: 29
Registriert: Donnerstag 5. August 2021, 21:11

Hallo.

Ich habe jetzt etwas programmiert, auch von einer Seite im Internet inspiriert. Da funktioniert jetzt per Thread, aber noch nicht so, wie ich es will. Eigentlich sollte, wenn vom seriellen Port Daten geschickt werden, ein Signal ausgelöst werden und eine variable (hier self.var) gefüllt werden, resp. diese Daten erhalten. Passiert aber nicht. Wenn Daten da sind (Empfang), sollen diese einfach per Slot prtvar angezeigt werden.
Kann mir jemand bitte helfen, diesen Code zu verbessern, resp. mir zu erklären, wo der Denkfehler liegt? Danke

Hier der Code:

Code: Alles auswählen

VERSION = "v0.09"
from PyQt5 import QtGui, QtCore
from PyQt5.QtWidgets import QTextEdit, QLineEdit, QWidget, QApplication, QVBoxLayout,QHBoxLayout,QPushButton
from PyQt5.QtCore import pyqtSignal
try:
    import Queue
except:
    import queue as Queue
import sys, time, serial
 
WIN_WIDTH, WIN_HEIGHT = 684, 400    # Window size
SER_TIMEOUT = 0.1                   # Timeout for serial Rx
RETURN_CHAR = "\n"                  # Char to be sent when Enter key pressed
PASTE_CHAR  = "\x16"                # Ctrl code for clipboard paste
baudrate    = 115200                # Default baud rate
portname    = "/dev/ttyACM0"           # Default port name
hexmode     = False                 # Flag to enable hex display
 

# Display incoming serial data
def display(s):
    if not hexmode:
        sys.stdout.write(textdump(str(s)))
    else:
        sys.stdout.write(hexdump(s) + ' ')
 
# Main widget            
class Gui(QWidget):
    text_update = QtCore.pyqtSignal(str)
         
    def __init__(self, *args): 
        super().__init__()
        self.initializeUI()
        
        
    def initializeUI(self):
        self.setGeometry(100,100,300,300)
        self.setWindowTitle('Test Serialport')
    
        self.textinput=QLineEdit()
        sendbtn=QPushButton('Send',self)
        print('text: ',self.textinput.text())
        sendbtn.clicked.connect(lambda: self.serth.ser_out(self.textinput.text()))
        
        varbtn=QPushButton('Value',self)
        varbtn.clicked.connect(self.prtval)
        lambda:serth.varready.connect(self.prtval) #Ist das hier überhaupt richtig?
        #Ohne lamba sagt der Compiler: NameError: name 'serth' is not defined

        
        # Create custom text box
        self.textbox=QTextEdit()
        font = QtGui.QFont()
        font.setFamily("Courier New")           # Monospaced font
        font.setPointSize(10)
        #self.textbox.setFont(font)
        vlayout = QVBoxLayout()
        hlayout = QHBoxLayout()
        hlayout.addWidget(self.textinput)
        hlayout.addWidget(sendbtn)

        vlayout.addLayout(hlayout)
        vlayout.addWidget(self.textbox)
        vlayout.addWidget(varbtn)
        self.setLayout(vlayout)
        
        self.resize(WIN_WIDTH, WIN_HEIGHT)      # Set window size
        self.text_update.connect(self.append_text)      # Connect text update to handler
        sys.stdout = self                               # Redirect sys.stdout to self
        self.serth = SerialThread(portname, baudrate)   # Start serial thread
        self.serth.start()
    
    def prtval(self):
        print('Variable: ',self.serth.var)
        
    def write(self, text):                      # Handle sys.stdout.write: update display
        self.text_update.emit(text)             # Send signal to synchronise call with main thread
         
    def flush(self):                            # Handle sys.stdout.flush: do nothing
        pass
 
    def append_text(self, text):                # Text display update handler
        cur = self.textbox.textCursor()
        cur.movePosition(QtGui.QTextCursor.End) # Move cursor to end of text
        s = str(text)
        while s:
            head,sep,s = s.partition("\n")      # Split line at LF
            cur.insertText(head)                # Insert text at cursor
            if sep:                             # New line if LF
                cur.insertBlock()
        self.textbox.setTextCursor(cur)         # Update visible cursor
    ''' 
    def keypress_handler(self, event):          # Handle keypress from text box
        k = event.key()
        s = RETURN_CHAR if k==QtCore.Qt.Key_Return else event.text()
        if len(s)>0 and s[0]==PASTE_CHAR:       # Detect ctrl-V paste
            cb = QApplication.clipboard() 
            self.serth.ser_out(cb.text())       # Send paste string to serial driver
        else:
            self.serth.ser_out(s)               # ..or send keystroke
    '''
    def closeEvent(self, event):                # Window closing
        self.serth.running = False              # Wait until serial thread terminates
        self.serth.wait()
         
# Thread to handle incoming & outgoing serial data
class SerialThread(QtCore.QThread):
    varready=pyqtSignal()
    def __init__(self, portname, baudrate): # Initialise with serial port details
        QtCore.QThread.__init__(self)
        self.portname, self.baudrate = portname, baudrate
        self.txq = Queue.Queue()
        self.running = True
 
    def ser_out(self, s):
        # Write outgoing data to serial port if open
        #print('Serial out: ',Gui.textinput.text())
        self.txq.put(s)                     # ..using a queue to sync with reader thread
         
    def ser_in(self, s):                    # Write incoming serial data to screen
        display(s)
         
    def run(self): # Run serial reader thread
        
        print("Opening %s at %u baud %s" % (self.portname, self.baudrate,
              "(hex display)" if hexmode else ""))
        try:
            self.ser = serial.Serial(self.portname, self.baudrate, timeout=SER_TIMEOUT)
            time.sleep(SER_TIMEOUT*1.2)
            self.ser.flushInput()
        except:
            self.ser = None
        if not self.ser:
            print("Can't open port")
            self.running = False
        while self.running:
            s = self.ser.read(self.ser.in_waiting or 1)
            if s:                                       # Get data from serial port
                print('Rs232: ',s)
                self.var=s
                self.varready.emit()
                
                
            if not self.txq.empty():
                txd = str(self.txq.get())               # If Tx data in queue, write to serial port
                self.ser.write(txd.encode())
        if self.ser:                                    # Close serial port when thread finished
            self.ser.close()
            self.ser = None
             
if __name__ == "__main__":
    app = QApplication(sys.argv) 
    opt = err = None
    for arg in sys.argv[1:]:                # Process command-line options
        if len(arg)==2 and arg[0]=="-":
            opt = arg.lower()
            if opt == '-x':                 # -X: display incoming data in hex
                hexmode = True
                opt = None
        else:
            if opt == '-b':                 # -B num: baud rate, e.g. '9600'
                try:
                    baudrate = int(arg)
                except:
                    err = "Invalid baudrate '%s'" % arg
            elif opt == '-c':               # -C port: serial port name, e.g. 'COM1'
                portname = arg
    if err:
        print(err)
        sys.exit(1)
    w = Gui()
    w.setWindowTitle('PyQT Serial Terminal ' + VERSION)
    w.show() 
    sys.exit(app.exec_())
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Viele Leute denken, sie können ihr Problem mit Threads lösen. Dann haben sie plötzlich zwei Probleme. Und eines davon ist mit eines der schwersten, die Programmierung so zu bieten hat.

Ich kann nur mit Nachdruck davon abraten. Die Menge an neuen Konzepten, die du jetzt verstehen musst - Thread-ownership bei Qt-Objekten, Worker-Objekte, Separation von QThread und dem unterliegenden System-Thread, Synchronisationsprimitive, Queued Connections, Hauptschleifen - kommt einfach nur oben drauf auf das, was du eh schon verstehen musst, um den von mir skizzierten Ansatz zu benutzen.

Schreib das also besser um. Und ohne große Suche findet sich das hier: https://doc.qt.io/qt-6.2/qtserialport-t ... ample.html - also Qt selbst. Nicht eine dunkle Seite im Internet.
kitebuggy
User
Beiträge: 29
Registriert: Donnerstag 5. August 2021, 21:11

Werde ich mir ansehen und versuchen, zu verstehen.

Danke.
kitebuggy
User
Beiträge: 29
Registriert: Donnerstag 5. August 2021, 21:11

Ich übersetze mir jetzt das Beispiel: terminal (Serielle Schnittstelle) der Dokumentation von Qt von C++ nach Python, um die Benutzung der seriellen Schnittstelle in pyqt5 zu erlernen.

Jetzt hänge ich in der Dialogbox Settingdialog:

Ich habe eine Combox gefüllt mit Werten:

Code: Alles auswählen

self.ui.dataBitsBox.addItem('5',QtSerialPort.QSerialPort.Data5)
self.ui.dataBitsBox.addItem('6',QtSerialPort.QSerialPort.Data6)
self.ui.dataBitsBox.addItem('7',QtSerialPort.QSerialPort.Data7)
self.ui.dataBitsBox.addItem('8',QtSerialPort.QSerialPort.Data8)
und möchte jetzt die zugehärigen Daten auslesen. (Nicht die 5, sondern QtSerialPort.QSerialPort.Data5)

In c++ steht hierzu:

Code: Alles auswählen

m_currentSettings.dataBits = static_cast<QSerialPort::DataBits>(
                m_ui->dataBitsBox->itemData(m_ui->dataBitsBox->currentIndex()).toInt());
Mein Code dazu lautet:

Code: Alles auswählen

self.dataBits=self.ui.dataBitsBox.itemData(self.ui.dataBitsBox.currentData())
        print('DataBits: ',self.dataBits)
Es kommt aber nur zur Ausgabe von 'DataBits: None'

Kann mir vielleicht jemand da weiterhelfen?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich würde das ignorieren und die Einstellungen einfach festlegen. Es geht doch um ganz andere Baustellen.
kitebuggy
User
Beiträge: 29
Registriert: Donnerstag 5. August 2021, 21:11

Ja, du hast sogar Recht :-) . Nur dass ich hier sehr viel gelernt habe. Ich hebe mir das für später auf..
Dann will ich mal fortfahren....
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

currentIndex != currentData
kitebuggy
User
Beiträge: 29
Registriert: Donnerstag 5. August 2021, 21:11

Ja gut, aber ich will ja nicht den Namen, sondern den 2. Teil im Settingsdialog
Antworten