GUI während eines laufenden Skriptes weiterverwenden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Hallo erstmal,
ich bin gerade dabei ein Programm zu schreiben, welches verschiedene Aufgaben erfüllen soll, um das Programm einigermaßen übersichtlich zu halten wollte ich es in ein Hauptprogramm und mehrere ausglagerte Nebenprogramme, die dann mit "os.system('sudo python ....py" geöffnet werden, gliedern. Dies hat auch alles einwandfrei funktioniert. Nur habe ich jetzt das Problem, dass wenn eines der Unterprogramme läuft, ich die GUI (welche im Hauptptogramm läuft) nicht mehr bedienen kann, weil diese gerade auf das Fertigstellen des Unterprgramms wartet (Die Unterprogramme laufen aber teilweise mehrer Stunden). Das Problem dachte ich eigentlich mit dem auslagern zu umgehen, dies hat aber nicht funktioniert und im Netz finde ich auch nichts dazu.
könnte mir vielleicht wer sagen, wie ich ein Unterprogramm starten kann und das Hauptprogramm davon aber nicht beeinträchtig wird?
Grüße Jonas
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zuerstmal solltest du Abstand von os.system nehmen. Das ist schon lange deprecated, und auch Ursache für dein Problem.

Stattdessen nimmst du subprocess.Popen & bekommst damit ein Objekt, das deinen Prozess repräsentiert, aber nicht blockiert.

Und dann ist da noch die Frage, ob wenn du eh alles in Python machst, es nicht eh viel bessere wäre keine Prozesse oder bestenfalls multiprocessing zu verwenden, weil du den code gleich aufrufen kannst.
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Erstmal danke dür die schnelle Antwort, ich werde das mit subprocess auf jeden fall probieren.
Ja ich mache alles in Python, da es auf dem Rapsberry Pi laufen soll.
Und was meinst du mit dem letzten Absatz? in wieweit keine Prozesse?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na was macht denn dein anderer Code? Wenn der auch in Python ist, dann kann man den ja auch im gleichen Interpreter laufen lassen. Sudo-Nutzung kann ein Grund sein, das trotzdem über subprocess zu lösen, aber sudo wird von Anfängern auch gerne wie Salz auf Pommes verwendet - reichlich, und ohne das es die Sache besser macht ;)
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Ja es ist alles in python geschrieben, habe aber im ganzen Code kein Sudo stehen. Ich starte das Programm über das Terminal mit sudo, liegt es vielleicht daran? :roll: Ja bin gerade dabei mir herauszusuchen, wie man ein anderes Python skript über subprocess startet. Haha ja, habe schon versucht möglichst wenig unnötiges in den code einzubauen ... :lol:
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast aber doch "sudo" in Deinem os.system-Aufruf stehen??
Um Dir zu helfen, wie Du es besser machen könntest, müßtest Du mehr verraten, was Du programmiert hast.
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Ja im os.system aufruf steht natürlich sudo, das bezieht sich ja aber auch nur indirekt auf das programm, oder?
Ich programmiere ein "Miniatur Smarthome" Programm, das heißt ich habe verschiedene Wecker, die Verschiedene Dinge ein und aus schalten (das ein und ausschalten geschieht einfach über die GPIO Pins und dann über Funkteckdosen). Die Verschiedenen Wecker steuer ich über eine Grafische Oberfläche (gebe die Weckzeit ein und will sie hier auch aktivieren und deaktiveren). Die Grafische Oberfläche ist für den Ersten wecker auch schon fertig und ein Programm für den Wecker habe ich auch schon, es funktioniert und läuft auch so, dass es den Prozessor nicht dauerhaft voll auslastet. Mein Problem ist jetzt halt, dass wenn ich eine Weckzeit einstelle und den Wecker dann Starte, reagiert die GUI so lange nicht, bis der Wecker auf grund des errreichen der eingestellten Weckzeit ausgelöst wird.
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Ich würde auch alles in ein Programm schreiben, es geht halt nur darum, dass man die GUI weiter bedienen kann und andere Programmfunktionen ausführen kann, während das Weckerprogramm läuft, egal ob nun im gleichen oder in einem anderen Skript
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Du GPIO-Pins ändern willst, brauchst Du kein sudo, Du mußt nur als normaler User in der richtigen Gruppe sein. Für eine Weckfunktion ein eigenes Programm aufzurufen, ist total übertrieben. Aber ohne Deinen Code zu kennen, kann man da schlecht weiterhelfen.
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Das hier ist mein code bis jetzt, es sind natürlich noch nicht alle funktionen drin, weil ich erstmal die eine zum laufen bringen möchte

Code: Alles auswählen

# Alles importieren

import RPi.GPIO as GPIO                                                                 
import os
import time
import datetime
import shlex
import subprocess
from threading import Thread
os.system('clear')


#-------------------------------------------------------------------------------------------------------------------------------
# Variablen Definieren

WeAAnAus = 0

VWeAZeitS = 0
VWeAZeitM = 0

Statustext = " "



#--------------------------------------------------------------------------------------------------------------------------------
# Fenstereinstellungen


from Tkinter import *                                                                  

fenster = Tk()
fenster.title("Zimmersteuerung")
fenster.geometry("720x680")



#----------------------------------------------------------------------------------------------------------------------------------

# Unterprogramme Audiowecker

def PrgAWeAn():                                                                         #Audiowecker An
    WeAAnAus = 1
    print ("Wecker An")
    Statustext = "Audiowecker An"
    Status.config(text=Statustext)


def PrgAWeAus():                                                                        #Audiowecker Aus
    WeAAnAus = 0
    print ("Wecker Aus")
    Statustext = "Audiowecker Aus"
    Status.config(text=Statustext)


def PrgAZeit():                                                                         #Weckzeit einstellen
    VWeAZeitS = WeAZeitS.get()
    VWeAZeitM = WeAZeitM.get()
    print (VWeAZeitS, VWeAZeitM)
    Statustext = "Audiowecker Zeit:  " + VWeAZeitS + " : " + VWeAZeitM + " Uhr"
    Status.config(text=Statustext)
    command=alarm_clock



def alarm_clock(hour, minute, precision = 10 ):                                         #Wecker programm
    while True:
        now = time.localtime()
        if now.tm_hour >= hour and now.tm_min >= minute:
            print ("Aufstehen")
            return True
        time.sleep(precision)

def main():
    hour = int(raw_input('Stunde: '))
    minute = int(raw_input('Minute: '))
    alarm_clock(hour, minute)

if __name__ == '__main__':
    main()

#--------------------------------------------------------------------------------------------------------------------------------

# GUI Erstellung

#           Wecker Audio

LaAWe = Label (text="Musik Wecker", bg="red")                                            #Label "Musik Wecker"
LaAWe.place(x = 280, y = 5, width=150, height=30)


WeAAn = Button(fenster, text="An", command=PrgAWeAn)                                    #Wecker anschalten Button
WeAAn.place(x = 230, y = 45, width=100, height=25)


WeAAus = Button(fenster, text="Aus", command=PrgAWeAus)                                 #Wecker ausschalten Button
WeAAus.place(x = 400, y = 45, width=100, height=25)


WeAZeitS = Entry(fenster)                                                                  #Weckzeit Eingabe Stunde
WeAZeitS.place(x = 200, y = 80, width=50, height=30)


LaAD = Label (text=":")                                                                 #Label zwischen eingaben
LaAD.place(x = 258, y = 82, width=20, height=30)


WeAZeitM = Entry(fenster)                                                                  #Weckzeit Eingabe Minute
WeAZeitM.place(x = 280, y = 80, width=50, height=30)


WeAEin = Button(fenster, text="Wecker erstellen", command=PrgAZeit)                     #Weckzeit Einlesen/Wecker setzten
WeAEin.place(x = 450, y = 80, width=150, height=25)


#-------------------------------------------------------------------------------------------------------------------------------
#           Wecker Licht



#------------------------------------------------------------------------------------------------------------------------------
#           Licht ueber Bewegungsmelder



#------------------------------------------------------------------------------------------------------------------------------
#           Status bzw. Ausgabelabel

Status = Label (text=Statustext)                                            #Statuslabel definieren
Status.place(x = 200, y = 620, width=300, height=30)

#-------------------------------------------------------------------------------------------------------------------------------



mainloop()
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Die Ausgabe an GPIO Pins habe ich, wie man sieht auch noch nicht eingebaut, das ist aber kein Problem, dass kann ich selber. Hatte jetzt erstmal eine ausgabe in das Statuslabel am unteren Ende des Programms und eine ausgabe ins Terminal.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Jonas1243: Hm, hier hat wahrscheinlich noch niemand geantwortet weil es noch keiner geschafft hat freundliche Worte für den notwendingen Totalverriss zu finden. Ich entschuldige mich schon mal im Voraus…

Den Code zum laufen zu bringen, heisst ihn erst einmal komplett zu verwerfen, und als nächstes Programmieren/Python zu lernen. Das geht so gar nicht. Weder von vom Inhalt, noch von der Form her betrachtet.

Kommentare sollten einen Mehrwert zum Code liefern und deshalb nicht das Offensichtliche was schon als Code da steht noch einmal wiederholen. Wer bei einem Haufen ``import``-Anweisungen nicht weiss das dort importiert wird, für den ist auch ein entsprechender Kommentar sinnlos. Wobei der Kommentar bei den Importen nicht stimmt, denn da wird gar nicht alles importiert. Alse weder *alles* ;-), noch alles was das Modul importiert, denn etwas später steht dann ja noch mal ein durch anderen Code getrennter Import aus dem `Tkinter`-Modul. Importe sollten in der Regel am Anfang stehen, damit man leicht erkennen kann was für Abhängigkeiten eine Modul hat.

Es wird zwar nicht *alles* importiert, aber deutlich zu viel, denn von den Importen wird über die Hälfte überhaupt gar nicht verwendet. `subprocess` sollte vielleicht verwendet werden, denn `os.system()` sollte man *nicht* verwenden. Die Dokumentation der Funktion verweist auf das `subprocess`-Modul. Allerdings würde ich davon abraten ``clear`` aufzurufen. Für so einen Unsinn wird man schnell gehasst wenn man den Leuten einfach so Informationen aus einem Terminal löscht. Man weiss nicht was der Benutzer vorher gemacht hat und was man da weglöscht. Normale Programme löschen da nichts, also kommt das überraschend, und solche Überraschungen sollte man vermeiden. Damit sind wir dann auf nur noch zwei Importe runter.

Sternchen-Importe sind Böse™. Das holt Dir im Fall von Tkinter ca. 190 Namen ins Modul von denen Du nur einen ganz kleinen Bruchteil tatsächlich verwendest. Solche Importe machen den Code unübersichtlicher, weil man nicht mehr so leicht nachvollziehen kann was woher kommt. Zudem besteht die Gefahr von Namenskollisionen. `Tkinter` wird üblicherweise als `tk` importiert und der Modulinhalt dann über diesen Namen referenziert.

Noch mal zu den Kommentaren eine Faustregel: Nicht kommentieren was der Code macht, denn das steht da ja bereits als Code, sondern höchsten *warum* der Code das macht was er macht, aber nur sofern das nicht offensichtlich ist. Diese Abschnittskommentare sind unüblich. Zudem sind die Kommentarzeilen zu lang. Ich weiss, der Style Guide ist da mittlerweile ein bisschen entspannter (bis 120 Zeichen) aber ich persönlich finde 80 immer noch eine wichtige Grenze die einem oft genug begegnet (Terminals, E-Mail-Clients, …) wo alles über 80 Zeichen Probleme mit der Lesbarkeit bereiten kann.

Variablen gehören nicht auf Modulebene. Da gehört nur Code hin der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Und zwar das *ganze* Hauptprogramm und die Funktion wird auch erst am Ende aufgerufen. Wenn nachdem die Funktion abgelaufen ist noch GUI-Elemente erstellt werden, dann kann das nicht das Hauptprogramm gewesen sein.

Der Style Guide for Python Code sagt auch etwas über Namensschreibweisen. klein_mit_unterstrichen für alles ausser Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Namen wie `WeAAnAus` und `VWeAZeitS` gehen aber auch unabhängig von der Schreibweise so gar nicht. Namen sollen dem Leser vermitteln was der Wert dahinter bedeutet, also müssen die verständlich sein wenn man die liest und nicht Fragezeichen hinterlassen. Also keine kryptischen Abkürzungen. Besonders Unsinnig wird es, wenn man erst so einen KryAbkNmn schreibt und dann einen Kommentar dazu das das kryptisch abkekürzter Name bedeuten soll. Den Kommentar kann man sich sparen wenn man den Namen gleich passend und lesbar gewählt hätte. Und man wüsste dann nicht nur bei der Definition was der Name bedeutet, weil dort ein Kommentar steht, sondern jedes mal wenn der Name verwendet wird, weiss man auch was er bedeutet, ohne einen Kommentar irgendwo an anderer Stelle suchen zu müssen.

Bei `WeAAnAus` rate ich mal das der Wert aussagen soll ob der Audiowecker scharf geschaltet ist oder nicht. Dann könnte das beispielsweise `is_audio_alarm_armed` oder so ähnlich heissen, damit man das nicht raten muss. Und ganze Zahlen sind dafür der falsche Datentyp. Wenn irgendwo eine 0 zugewiesen wird, erwartet der Leser dass man vielleicht auch 42 oder -23 zuweisen kann. Wenn da `False` zugewiesen wird, dann weiss der Leser das sehr wahrscheinlich nur die Werte `True` und `False` für diese Variable als Wertebereich in Frage kommt.

Die GUI-Elemente und die ”Funktionen” haben das gleiche Problem mit kryptischen Namen und Kommentaren die die erklären müssen. Funktionen und Methoden werden üblicherweise nach Tätigkeiten benannt. `alarm_clock` ist keine Tätigkeit, das wäre ein Name bei dem man einen Wert/ein Objekt erwarten würde der/das einen Wecker repräsentiert, und keine Funktion.

Das `alarm_clock()` den Wert `True` zurück gibt, macht keinen Sinn. Was soll das bedeuten? Die Funktion gibt ja *immer* `True` zurück, nie etwas anderes, das heisst der Aufrufer kann an diesem Wert nichts erkennen oder entscheiden. Im Code machst Du mit diesem Wert ja auch gar nichts.

Funktionen ”Unterprogramme” zu nennen kann auf eine falsche Vorstellung hindeuten, insbesondere wenn die dann alle keine Argumente bekommen und auf globalen Variablen operieren (wollen). Was so wie's da steht auch nicht wirklich funktioniert, weil Namen in Funktion in Python lokal sind sobald irgendwo innerhalb der Funktion eine Zuweisung an den Namen steht. `WeAAnAus` in `PrgAWeAn()` und `PrgAWeAus()` ist ein anderes `WeAAnAus` als das auf Modulebene. Und das ist auch gut so, denn das auf Modulebene sollte dort gar nicht stehen. Wenn eine Funktion einen Wert/Zustand über Aufrufe hinweg ändern/behalten soll, dann braucht man objektorientierte Programmierung (OOP). Da führt in Python spätestens bei GUI-Programmierung kein sinnvoller Weg dran vorbei. Denn alles was Funktionen ausser Konstanten verwenden, sollte als Argument in die Funktion/Methode hinein kommen, und nicht auf ”magische” und damit schwer nachvollziehbare Weise irgendwo global aus der ”Umgebung” kommen.

`place()` ist umständlich, fehleranfällig, und hat das Problem, dass die Angaben nur solange funktionieren, wie die Einstellungen/Anzeigehardware ähnlich genug dem Rechner sind, auf dem das entwickelt und getestet wurde. So etwas macht heute niemand mehr. `pack()` und `grid()` sind viel flexibler und die GUI passt sich automatisch an. Man muss dann auch die Fenstergrösse nicht mehr selber vorgeben. Die GUI lässt sich auch einfacher verändern oder erweitern.

Statt des `time`-Moduls würde ich für die Zeitoperationen das `datetime`-Modul verwenden. `datetime.time`-Objekte kann man beispielsweise miteinander vergleichen und die fassen Stunden und Minuten auch schon zu einem Wert/Objekt zusammen, so dass man das nicht als zwei Einzelwerte behandeln muss.

Last but not least funktioniert GUI-Programmierung nicht mehr so linear wie die `alarm_clock()`-Funktion. Bei einer GUI muss in der Regel die GUI-Hauptschleife laufen um die GUI ”am Leben” zu halten und sich um Ereignisse kümmern zu können und den Programmfluss zu steuern. Der ist ereignisbasiert. Das heisst man programmiert nicht mehr selbst lang laufende Schleifen und bestimmt was wann passiert, sondern man registriert bei der GUI Rückruffunktionen/-methoden die bei bestimmten Ereignissen *kurz* etwas machen und dann die Kontrolle wieder an die GUI-Hauptschleife zurück geben. Solche Ereignisse können das klicken des Benutzers auf eine Schaltfläche sein, aber auch das eine bestimmte Zeit, beispielsweise 10 Sekunden vergangen sind. Man würde also nicht mehr eine Schleife schreiben, die etwas testet und dann 10 Sekunden schläft, sondern eine Funktion/Methode die alle 10 Sekunden von der GUI-Hauptschleife aufgerufen wird und dann kurz etwas testet. In Tk gibt es dafür die `after()`-Methode auf Widgets um so einen (einmaligen) Rückruf nach einer bestimmten Zeit zu registrieren.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Jonas1243
User
Beiträge: 8
Registriert: Dienstag 13. November 2018, 21:27

Okay ... Danke für die ausführliche Antwort ... ich werde das ganze dann nochmal etwas überarbeiten
Antworten