PiQt5 GUI meets live Daten

  • Hallo liebes Forum,

    seit Tagen arbeite ich mich etwas in PiQt5 ein.
    Das unterscheidet sich doch etwas zur gewohnten Script Programmierung.

    Nun stehe ich vor zwei, sagen wir hartnäckigen Herrausvorderungen.

    Zum ersten habe ich einen Corde der eine Bluetooth Verbindung aufbaut.
    Die Erwartung wäre:

    Button click -> Im Textbrowser wird ausgegeben "Warten.....Verbindung wird aufgebaut" -> GUI Wartet -> Wenn die Funktion durchgelaufen ist-> im Textbrowser wird ausgegeben "Bluetooth Verbunden"

    Das ist mein derzeitiger Code:

    Code
    def Bluetoot_Verbinden(self):
        self.Text_List("Warten........Bluetooth wird verbunden")
        OBD_Modul.Bluetooth_Verbinden()
        self.Text_List("Bluetooth Verbunden")

    Ergebnis: Es wird das Modul gestartet und de BT Verbindung aufgebaut.
    GUI freezed aber alles was dann geklickt wird, wird nach Abschluss der .sh hintereinander ausgeführt.
    Nach beendigung der .sh werden beide Text_List gleichzeitig ausgegeben.

    Bei dem Start der OBD Verbindung ähnlich.

    Das Modul benötigt 30 Sekunden für die Verbindung und gibt dann die Verbindungsinfos die zur weiteren Kommunication benötigt werden zurück.

    Wenn ich das Modul per Button click Aufrufe startet das Modul, wartet aber nicht sonder gibt nur den aktuellen Status "Wird Verbunden" zurück.

    Code
    def Verbindung_Aufbauen(self):
        global connection
        connection = Modul.Verbindung_Aufbauen
        sconnection = str(connection)
        self.Text_List(sconnection)

    Hier bekomme ich nur:
    Sprich er startet zwar das Modul wartet aber nicht bis es abgearbeitet ist sondern gibt nur den Status das er das Verbinden gestartet hat aus.
    Kann ich der GUI sagen das es warten soll bis das Modul abgearbeitet ist?

    Vielen Dank schon einmal

  • Zur hilfreichsten Antwort springen
  • Shadysoul Funktionen die als GUI-Rückrufe registriert werden, dürfen nur kurz etwas machen, denn die werden von der GUI-Hauptschleife aufgerufen und während in der Funktion *Dein* Code läuft, ist die GUI-Hauptschleife natürlich lahm gelegt, die wartet ja darauf das Deine Rückruffunktion abgearbeitet wird. Erst wenn die zurückkehrt, kann die GUI-Hauptschleife auch wieder die GUI aktualisieren und alle Änderungen die während Deine Funktion lief, werden auf einen Schlag sichtbar.

    Im zweiten Fall passiert nicht das was Du glaubst was passiert. Das ist aber auch an der Ausgabe deutlich sichtbar: Du gibst `connection` aus und das ist keine Verbindung sondern die *Funktion* die Du gar nicht *aufgerufen* hast.

    Das ``global`` hat da nichts zu suchen. Du hast eine Klasse, da gibt es wirklich keine Ausreden mehr undurchsichtige, fehleranfällige globale Variablen zu verwenden.

    Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

    Namen sollten keine kryptischen Abkürzungen enthalten, und auch keine Grunddatentypen. Also auch nicht `str_connection` statt `sconnection`. Ich sehe in dem konkreten Fall auch gar nicht warum man dieses popelige kleine Zwischenergebnis überhaupt an einen Namen binden muss.

    Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt die sie durchführen. `Text_List()` ist keine Tätigkeit.

    GUI-Programmierung ist nicht so einfach. Nebenläufige Programmierung auch nicht. Beides zusammen ist noch einen Tick schwieriger. Aber da muss man sich mit beschäftigen wenn man länger laufende Programmabschnitte hat, welche die GUI nicht blockieren sollen. Wenn da etwas 30 Sekunden dauert, dann muss das in einen Thread ausgelagert werden. Während der nebenher läuft muss man dann auch dafür sorgen, dass man den Button nicht noch mal klicken kann. Und wenn die Aufgabe abgearbeitet ist, muss der Button wieder aktiviert werden. Oder vielleicht auch in diesem Fall erst wieder wenn die Verbindung wieder geschlossen wurde. Der Thread muss (Zwischen)Ergebnisse threadsicher über Signale an den GUI-Thread kommunizieren. Die Qt-Dokumentation hat da Informationen zu wie man Worker-Threads aufsetzt und dafür sorgt das der jeweilige Code im richtigen Thread ausgeführt wird.

    Bei „ODB connect“ und „ODB disconnect“ macht es wahrscheinlich auch keinen Sinn das immer beide Buttons vom Benutzer angeklickt werden können. Das ist eine der Neuigkeiten die GUI-Programmierung in der Regel mitbringt: Nicht mehr der Programmierer gibt vor was in welcher Reihenfolge ausgeführt wird, sondern der Benutzer kann alles in beliebiger Reihenfolge nutzen/klicken, und man muss da entweder auf alle Möglichkeiten sinnvoll reagieren, oder die Möglichkeiten sinnvoll einschränken.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Vielen Dank für dein Feedback.

    Naja wie einst einmal ein sehr weißer Prof gemeint hat: "Namen sind nicht mehr als Schall und Rauch" glaube ich nicht das deine Kritik an Groß und Kleinschreibung hier sehr zur Problemlösung beigebracht hat.

    Auch sind Dinge wie eine globale Variable aus vielen Versuchen der letzten Tage entstanden.

    Dennoch danke für deine sehr Lehrreiche Kritik, und viel Freude weiter hin in diesem Forum.

    Dennoch würde ich mich gerne weiter an die Community wenden, um eine Syntax diskutieren zu können welche Hilfreich bei der Umsetzung des Vorhabens ist.

  • Shadysoul Namen sind wichtig, Schreibweisen sind wichtig, weil es hier nicht um irgendwelche willkürlichen Namen geht, wie in dem „Schall und Rauch“-Zitat, sondern um Namen deren Hauptaufgabe es ist Informationen zum Leser zu übermitteln. Und auch die Schreibweise tut das. `Ding` ist eine Klasse und `ding` ist ein naheliegender Name für ein Exemplar dieser Klasse. Zumindest wenn sich alle an diese Konvention halten, funktioniert diese Informationsübermittlung. Wenn man dagegen eine Funktion oder ein Exemplar `Ding` nennt, dann ist das nicht mehr so klar. Und man muss mehr über die Definition wissen und kann leichter Fehler machen. Noch schwieriger wird es für alle die sich an die Konvention halten, und denken sie wissen was `Ding` ist — eine Klasse, denn es ist ja wie eine geschrieben. Und schon treten *vermeidbare* Verständnisprobleme auf und es ist einfacher Fehler zu machen.

    Toll das Du Dir genau die Namenskonventionen rausgesucht hast aus meiner Antwort. Ja, das mag das unwichtigste an der konstruktiven Kritik gewesen sein — für das konkrete Problem. Aber auf das bin ich ja auch eingegangen und habe erklärt woran es liegt und was man dagegen macht. Wenn Du das ignorieren möchtest, schön, aber dann kommst Du halt auch mit Deinem Programm nicht weiter.

    Vor GUI-Programmierung muss objektorientierte Programmierung (OOP) halbwegs gut sitzen, denn Qt ist ein grossen objektorientiertes GUI-Rahmenwerk und es erfordert zwangsläufig, dass man selbst trittsicher im schreiben von Klassen und den verwendeten Entwurfsmustern ist. Wenn man anfängt mit ``global`` zu experimentieren, fehlt noch Grundlagenwissen zu OOP.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Ich glaube das ganze hier fürt zu nichts außer einer Diskusion die sich immer weiter von der eigentlichen Frage Stellung entfernt.

    Ich wünsche noch ein schönes verlängertes Wochenende und werde mir die Informationen im World Wide Web zusammen suchen.

  • Ich glaube das ganze hier fürt zu nichts

    Und ich glaube Du verstehst nicht, worum es uns geht. Es gibt Konventionen wie PEP8 um Code auf den ersten Blick verständlich und lesbar zu machen.

    Du willst Hilfe und es den Lesern Deines Codes ermöglichen diesen zu verstehen? Dann halte Dich an PEP8! :shy:

  • Selbstverständlich verstehe ich worum es geht.
    Vielleicht jetzt noch einmal runtergebrochen das es auch ein Kevin oder ein Perry versteht.
    Ich hatte kein Thread eröffnet "Hilfe welche Variablen Namenskonvenionen mache ich falsch", sonder ein Thread in dem ich erhofft hatte einen Einstieg ins
    Multithreading zu bekommen.
    Das ist vergleichbar wie wenn auf die Frage:
    "Hilfe liebe Nachbarn wie lösche ich am schnellsten den Brand meines Gartenhauses"
    Die Antwort:
    "Streiche bitte sofort deinen Zaun Weiß wie alle anderen Nachbarn" kommt

    Zitat aus der 3. Klasse: "Thema verfehlt"

    Um in das Thema aber für nachfolgende Leser ein klein wenig nützlichen Inhalt zu bekommen.
    Mir hat das Video sehr weitergeholfen:

    Implementing a Background Process in PyQT5

    Hier das ganze auch noch einmal textuell zum Nachlesen:
    Delay without blocking Main Thread

    Das Internet kann ab und an doch Hilfreich sein

    • Hilfreichste Antwort

    Zum Lernen von PyQt kann ich generell Python GUIs empfehlen. Wie die Vorredner schon erwähnten, sollte entweder mit Threads oder Multiprocessing gearbeitet werden. PyQt hat dazu auch die sogenannte Worker Klasse, um sowas zu realisieren. Explizit hier von Python GUIs ein Tutorial, oder auch hier von Real Python, beides finde ich persönlich gute Seiten.

    Für lange Task kann ich auch PyQt-Spinner empfehlen, weiß der Nutzer dann, dass etwas im Hintergrund läuft. Hier ist ein Beispiel, wie ich den Worker + Spinner in einem meiner größeren Projekte für längere Aufgaben verwenden.

    Im Allgemeinen kann der Worker dann am Ende ein Signal emittieren, worauf du dann wieder reagieren kannst.

    Zu deiner anderen Frage mit dem Warten: Es kommt darauf an, wie der Code (Synchron, Asynchron) ausgeführt wird. IdR ist Python synchron, es kommen aber mehr und mehr auch asynchrone Funktionen rein. PyQt verwendet auch teilweise direkt mehrere Threads. Ich kenne jetzt nicht genau, wie das ODB Modul funktioniert. Ggf. hilft es bei asynchroner Ausführung auf die Beendung zu warten, oder zu der Ausführung ein callback / Signal hinzuzufügen, das nach der erfolgreichen Ausführung dann die Information in das Fenster schreibt.

  • Ich glaube das ganze hier fürt zu nichts außer einer Diskusion die sich immer weiter von der eigentlichen Frage Stellung entfernt.

    Du verstehst nicht oder willst nicht verstehen, dass das _alles_ wichtig ist. Du fragst, weil du gerne besseren Code hättest. Da gehört halt auch die PEP8 dazu. Wenn du die als Python-Programmierer ignorierst wirst du das immer und immer wieder gesagt bekommen. Mit Recht. Oder anders: wenn du dich der PEP8 verweigerst, verweigerst du dich Python.

    Abgesehen davon hast du doch explizit gesagt bekommen, was (außer den Namen) in deinem Code falsch ist. Und zwar sehr ausführlich in der allerersten Antwort. Das musst du jetzt halt "nur noch" umsetzen.

    Du kannst jederzeit gerne deine korrigierten Code hier posten. Idealerweise den gesamten Kontext, also zumindest die Klasse und alle relevanten Methoden. GUI Programmierung ohne Klassen geht nun mal nicht wirklich, außer man hat eine mega-triviale Anwendung. Das gilt auch nicht spezifisch für Qt, sondern alle GUI Frameworks, als z.B. auch Tkinter und GTK.

    Gruß, noisefloor

  • Shadysoul Was würdest Du als Ingenieur sagen, wenn Dir eine technische Zeichnung vorgelegt wird, in der die Maße mal nur von links oder auch mal von oben lesbar sind, neben denen die richtig eingezeichnet wurden? Also ich könnte damit ich nicht arbeiten. Würdest Du so eine Zeichnung etwa durchgehen lassen? :-/

    Es gibt gute Gründe für Regeln und Verhaltensnormen und dafür, dass man sich daran halten sollte.

  • Zum Lernen von PyQt kann ich generell Python GUIs empfehlen. Wie die Vorredner schon erwähnten, sollte entweder mit Threads oder Multiprocessing gearbeitet werden. PyQt hat dazu auch die sogenannte Worker Klasse, um sowas zu realisieren. Explizit hier von Python GUIs ein Tutorial, oder auch hier von Real Python, beides finde ich persönlich gute Seiten.

    Für lange Task kann ich auch PyQt-Spinner empfehlen, weiß der Nutzer dann, dass etwas im Hintergrund läuft. Hier ist ein Beispiel, wie ich den Worker + Spinner in einem meiner größeren Projekte für längere Aufgaben verwenden.

    Im Allgemeinen kann der Worker dann am Ende ein Signal emittieren, worauf du dann wieder reagieren kannst.

    Zu deiner anderen Frage mit dem Warten: Es kommt darauf an, wie der Code (Synchron, Asynchron) ausgeführt wird. IdR ist Python synchron, es kommen aber mehr und mehr auch asynchrone Funktionen rein. PyQt verwendet auch teilweise direkt mehrere Threads. Ich kenne jetzt nicht genau, wie das ODB Modul funktioniert. Ggf. hilft es bei asynchroner Ausführung auf die Beendung zu warten, oder zu der Ausführung ein callback / Signal hinzuzufügen, das nach der erfolgreichen Ausführung dann die Information in das Fenster schreibt.

    Die Dokumentationen haben sehr gut geholfen sich in das Thema rein zu arbeiten.
    Danke dir.

    Wenn man sich dort durch arbeitet hat man schon einen ganz guten Überblick.

    Das meiste Funktioniert mir der Dokumentation von

    Vielen Dank dir

  • Hi zusammen,

    ich starte den ausgelagerten Job über:

    Code
    def Verbindung_Aufbauen(self):
        if self.p is None:
            self.Text_List("Verbindung wird aufgebaut")
            self.p = QProcess()
            self.p.readyReadStandardOutput.connect(self.handle_stdout)
            self.p.stateChanged.connect(self.handle_state)
            self.p.finished.connect(self.process_finished)
            self.p.start("python3", ['Test.py'])

    Besteht die Möglichkeit in der letzten Zeile nicht nur die Datei Test.py zu starten sondern inerhalb der "Test.py" ein Modul: "Verbinden"
    Zu Starten? ( In der Art Test.Verbinden() )
    Oder ein Parameter mit übergeben den ich dann in Test.py abfragen kann um ausschließlich ein Modul innerhalb "Test.py" zu starten?

    Danke euch zusammen

    Beste Grüße

    Sebastian

  • Probier mal einfach noch ein start:

    Code
    self.p.start("python3", ['Test.py'])
    self.p.start("python3", ['Test2.py'])
  • Probier mal einfach noch ein start:

    Code
    self.p.start("python3", ['Test.py'])
    self.p.start("python3", ['Test2.py'])

    Vielen Dank für deine schnelle Antwort.

    aber würde das nich einfach nach einer .py Suchen die Test2 heißt?
    Ich habe innerhalb meiner Test.py mehrere Module

    Code
    def TestModul1:
        print("Test1")
    
    def TestModul2:
        print ("Test2"

    In diesem Stiel und nun würde ich gerne innerhalb der Datei "Test.py" das Modul "TestModul2" ansprechen

    Also in der Art:

    import Test

    Test.TestModul2()

    nur halt im self.p.start

  • Wenn du auf die Funktionen von Test.py zugreifen willst, ist das Ausführen als Prozess der falsche Weg.

    Es wird einfach ein weiterer Prozess gestartet, der keinen Zugriff auf den aktuellen Interpreter hat, der den Prozess gestartet hat.

    Wenn du die Funktionen nutzen willst, dann importiere diese in dein Programm.

    Dann würde ich Test.py aber einen anderen sinnvollen Namen geben und Module schreibt man komplett klein.

    Python: main.py
    from utils import test_funktion
    
    
    
    test_funktion()

    oder wie du es vorgeschlagen hast

    Code
    import utils
    
    
    utils.test_funktion()
    Python: utils.py
    def test_funktion():
        print("Print von test_funktion")

    Falls test_funktion später irgendwas ist, dass länger blockiert, kannst du diese Funktion immer noch via Qt als Thread ausführen.

    Das kannst du dann als Klasse in dem Modul implementieren:

  • Ja genau so hatte ich das ganze auch lauffähig bevor ich es jetzt alles mit einer GUI ausführen will.

    Das dient nur als Beispiel.

    In dem Modul sind mehrere Module um mit der OBD Schnittstelle im Auto zu kommunizieren.

    Die jetzige Funktion in „Test.py“ die ich mittels eines Buttons aus der GUI ansprechen will hat eine Laufzeit von ca 30 Sekunden.

    Hier soll mit dem Button Klick der Sub Prozess gestartet werden der dieses Modul startet und dann nach den 30 Sekunden die Verbindungsinformationen an die GUI.pi zurück gibt.

    Mit den weiteren Buttons werden dann nich Live Daten an die GUI übergeben.

    Also wird dann eine Worker Class das richtige Stichwort sein?

    Dann lese ich mich da tiefer ein.

    Vielen Dank dir für dein Feedback und die Mühe.

    Habe heute leider den Laptop vergessen, weshalb ich keinen Code zur Hand habe.

  • Hallo,

    Oder ein Parameter mit übergeben den ich dann in Test.py abfragen kann um ausschließlich ein Modul innerhalb "Test.py" zu starten?

    um das noch ganz allgemein zu beantworten. Ja, man kann Parameter beim Start übergeben und auswerten.

    Aufruf + Ausgabe:

    Code
    pi@***:~ $ python3 parameterübergabe_test.py Hello World
    sys.argv[0] ist immer der Skriptname, alles weitere übergebene Parameter
    index 0: parameterübergabe_test.py
    index 1: Hello
    index 2: World

    https://docs.python.org/3.9/library/sys.html#sys.argv

  • Hallo,

    um das noch ganz allgemein zu beantworten. Ja, man kann Parameter beim Start übergeben und auswerten.

    Aufruf + Ausgabe:

    Code
    pi@***:~ $ python3 parameterübergabe_test.py Hello World
    sys.argv[0] ist immer der Skriptname, alles weitere übergebene Parameter
    index 0: parameterübergabe_test.py
    index 1: Hello
    index 2: World

    https://docs.python.org/3.9/library/sys.html#sys.argv

    Vielen Dank für deine Antwort,

    ich war die letzte Tage leider beruflich nicht Zuhause somit kam ich jetzt erst dazu mir das ganze an zu schauen.
    Sorry falls die Verständnissfragen etwas low Level sind, PyQT ist gerade etwas ganz neues in meiner Welt ^^.

    Ich habe mich ein wenig daran Versucht.
    Und einmal ein greifbares Beispiel gemacht.
    Hier soll wieder Test.py mittels eines Button klicks gestartet werden mit einem Paramter der Übergeben wird.
    In Test.py wird dann eine Operation ausgeführt und das Ergebniss wieder an die GUI zur plotten in einer Textbox zurückgegeben.

    Code des Test.py:

    Hier hat sich mir die Frage gestellt wie komme ich in Test.py an den Parameter um ihn auszuwerten?

    Und hier die Auszüge aus der GUI:

    Vielen Dank dir nochmal fürs Zeit nehmen.

    Einen schönen Start in die Woche morgen.

    Sebastian

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!