Daten von Arduino in GUI darstellen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Unimatrix
User
Beiträge: 7
Registriert: Donnerstag 1. Januar 2015, 12:55

Hallo,
ich habe ein kleines Problem mit PyQt. Und zwar habe ich eine simple Arduino Schaltung aufgebaut, die mir über die Serielle Schnittstelle einen Spannungswert übergibt. Ebenfalls soll eine LED vom PC aus gesteuert werden. Soweit funktioniert auch alles bis auf eine Kleinigkeit. In der GUI wird das übertragene Signal nur beim Programmstart aktualisiert und danach nie mehr. Ich möchte jedoch, dass das Signal im Textfeld permanent, bzw. alle 100ms erneuert wird. Kann mir jemand auf die Sprünge helfen, wie sich das am einfachsten realisieren lässt?


Hier mein Code dazu:

Code: Alles auswählen

import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from window import Ui_MainWindow
import serial
import time

ser = serial.Serial('COM3', 9600, timeout=0)

class Main(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        
        #Connections
        self.Connections()
        self.Input()
        

    def Connections(self):
        self.ui.pushButton.clicked.connect(self.ON)
        self.ui.pushButton_2.clicked.connect(self.OFF)
     
    def Input(self):
        #Hiermit wird die eingelesene Spannung in eine Textzeile geschrieben
        self.ui.lineEdit.setText(str(ser.readline()))
        
    #Steuerung der LED
    def ON(self):
        ser.write('1')
        
    def OFF(self):
        ser.write('0')
        
if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = Main()
    window.show()
    sys.exit(app.exec_())

Code: Alles auswählen

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'qui.ui'
#
# Created: Fri Apr 03 22:38:30 2015
#      by: PyQt4 UI code generator 4.9.6
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(800, 253)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralwidget)
        self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
        self.verticalLayout = QtGui.QVBoxLayout()
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.lineEdit = QtGui.QLineEdit(self.centralwidget)
        self.lineEdit.setObjectName(_fromUtf8("lineEdit"))
        self.verticalLayout.addWidget(self.lineEdit)
        self.horizontalLayout = QtGui.QHBoxLayout()
        self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
        self.pushButton = QtGui.QPushButton(self.centralwidget)
        self.pushButton.setObjectName(_fromUtf8("pushButton"))
        self.horizontalLayout.addWidget(self.pushButton)
        self.pushButton_2 = QtGui.QPushButton(self.centralwidget)
        self.pushButton_2.setObjectName(_fromUtf8("pushButton_2"))
        self.horizontalLayout.addWidget(self.pushButton_2)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.horizontalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.pushButton.setText(_translate("MainWindow", "On", None))
        self.pushButton_2.setText(_translate("MainWindow", "Off", None))
Ich habe schon mit

Code: Alles auswählen

    def Input(self):
        #Hiermit wird die eingelesene Spannung in eine Textzeile geschrieben
        while True:
             self.ui.lineEdit.setText(str(ser.readline()))
experimentiert, aber da startet dann die GUI gar nicht mehr.
BlackJack

@Unimatrix: Schau Dir mal die `QTimer`-Klasse an.
Unimatrix
User
Beiträge: 7
Registriert: Donnerstag 1. Januar 2015, 12:55

Danke. So funktioniert es nun:

Code: Alles auswählen


import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import QObject, QTimer
from window import Ui_MainWindow
import serial
import time

ser = serial.Serial('COM3', 9600, timeout=0)

class Main(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        
        #Connections
        self.Connections()
        self.timer = QTimer()
        self.timer.timeout.connect(self.Input)    
        self.timer.start(100)
        self.Input()
        

    def Connections(self):
        self.ui.pushButton.clicked.connect(self.ON)
        self.ui.pushButton_2.clicked.connect(self.OFF)
     
    def Input(self):
        #Hiermit wird die eingelesene Spannung in eine Textzeile geschrieben
        self.ui.lineEdit.setText(str(ser.readline()))
        
    #Steuerung der LED
    def ON(self):
        ser.write('1')
        
    def OFF(self):
        ser.write('0')
        
if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = Main()
    window.show()
    sys.exit(app.exec_())
Wenn immer alles so einfach gehen würde :D
Was ich auch noch gelesen habe, ist die Möglichkeit verschiedene Threads anzulegen, womit ich derzeit aber auch noch nicht klar komme. Wäre schön, wenn ihr mir da ein paar Tipps geben könntet.
BlackJack

@Unimatrix: Noch ein paar Anmerkungen: Das `Serial`-Objekt sollte nicht auf Modulebene erstellt werden. Seiteneffekte beim reinen importieren eines Moduls sind schon unsauber, aber das durch das importieren sofort versucht wird eine serielle Verbindung zu öffnen ist gar nicht gut. Man kann das Modul dann nicht ohne entsprechende Hardware importieren. Damit verhindert man effektiv das man auf jedem Rechner Tests durchführen kann, und auch einige Werkzeuge wie zum Beispiel Sphinx gehen davon aus das man Module seiteneffektfrei importieren kann.

Quelltext aus ``*.ui``-Dateien generieren ist ein unnötiger Schritt weil man die ``*.ui``-Datei auch zur Laufzeit direkt laden kann. Dein Weg über das `ui`-Attribut ist auch irgendwie komisch. Üblicher wäre es die eigene GUI-Klasse von dem Qt-Widget *und* von der generierten Klasse erben zu lassen.

Ein paar Namen sind ausgesucht schlecht gewählt. Namen sollten dem Leser vermitteln was der Wert dahinter für eine Bedeutung hat im Kontext des Programms. Wenn die `Input()`-Methode und das `lineEdit` Namen hätten die nicht so verdammt generisch und nichtssagend wären, könnte man sich den Kommentar sparen was da passiert. Funktionen und Methoden werdne üblicherweise nach Tätigkeiten benannt um sie von ”passiven” Werten leichter unterscheiden zu können.

Wo wir bei Kommentaren sind: Die sollten dem Leser einen Mehrwert liefern. ``# Connections`` vor dem Aufruf der `Connnections()`-Methode leistet das ganz sicher nicht. Es gibt auch die Faustregel das ein Kommentar nicht beschreiben soll *was* gemacht wird, sondern warum das so gemacht wird wie der Code es beschreibt. Wenn man das *was* nicht am Code ablesen kann, sollte man erst über den Code und bessere Namensgebung nachdenken bevor man einen Kommentar dazu schreibt. Statt ``# Steuerung der LED`` zu kommentieren könnte man der/den Methode(n) entsprechende Namen geben die das aussagen.

Ich komme dann ungefähr auf das hier (ungetestet):

Code: Alles auswählen

from __future__ import absolute_import, division, print_function
import sys
from functools import partial

from PyQt4 import QtCore, QtGui, uic
from serial import Serial


class Main(QtGui.QMainWindow):

    def __init__(self, connection, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        uic.loadUi('qui.ui', self)
        self.connection = connection
        self.ledOnButton.clicked.connect(partial(self.switch_led, True))
        self.ledOffButton.clicked.connect(partial(self.switch_led, False))
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update_voltage)
        timer.start(100)

    def update_voltage(self):
        self.voltageDisplay.setText(self.connection.readline().decode('ascii'))

    def switch_led(self, state):
        self.connection.write(str(int(state)))
        self.connection.flush()


def main():
    application = QtGui.QApplication(sys.argv)
    window = Main(Serial('COM3', 9600, timeout=0))
    window.show()
    sys.exit(application.exec_())


if __name__ == '__main__':
    main()
Unimatrix
User
Beiträge: 7
Registriert: Donnerstag 1. Januar 2015, 12:55

Vielen Dank für die Anmerkungen. Werde das auf jeden Fall durcharbeiten!
BlackJack

@Unimatrix: Einen Thread könnte man wie folgt einbauen (ebenfalls ungetestet):

Code: Alles auswählen

from __future__ import absolute_import, division, print_function
import sys
from functools import partial

from PyQt4 import QtCore, QtGui, uic
from serial import Serial


class VoltageReader(QtCore.QThread):

    newVoltageRead = QtCore.pyqtSignal('QString')

    def __init__(self, connection, interval=100, parent=None):
        QtCore.QThread.__init__(self, parent)
        self.connection = connection
        self.interval = interval
    
    def run(self):
        timer = QtCore.QTimer()
        timer.timeout.connect(self.read_voltage)
        timer.start(self.interval)
        self.exec_()
        
    def read_voltage(self):
        self.newVoltageRead.emit(self.connection.readline().decode('ascii'))


class Main(QtGui.QMainWindow):

    def __init__(self, connection, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        uic.loadUi('qui.ui', self)
        self.connection = connection
        voltage_reader = VoltageReader(self.connection, self)
        voltage_reader.newVoltageRead.connect(self.voltageDisplay.setText)
        voltage_reader.start()
        self.ledOnButton.clicked.connect(partial(self.switch_led, True))
        self.ledOffButton.clicked.connect(partial(self.switch_led, False))

    def switch_led(self, state):
        self.connection.write(str(int(state)))
        self.connection.flush()


def main():
    application = QtGui.QApplication(sys.argv)
    window = Main(Serial('COM3', 9600, timeout=0))
    window.show()
    sys.exit(application.exec_())


if __name__ == '__main__':
    main()
Das lesen Zeile von der seriellen Verbindung kann jetzt die GUI nicht mehr blockieren.
Unimatrix
User
Beiträge: 7
Registriert: Donnerstag 1. Januar 2015, 12:55

Hey,
nochmals vielen Dank! Das bringt mich auf jeden Fall deutlich weiter!
Antworten