Absoluter Anfänger Stoppuhr mit Farben

Fragen zu Tkinter.
Antworten
RamazanKaya
User
Beiträge: 2
Registriert: Montag 24. Februar 2020, 21:51

Hi ich hoffe das hier jemand mir helfen kann, es geht um folgendes:

Ein Azubi im 3 Lehrjahr hatte mir damals geholfen, ein Script zu schreiben wo mit ich Zeiten einstellen konnte und diese Visualisiert wurden.

Ich bekomme es leider nicht mehr hin, dass wenn die Zeit läuft es nicht mehr geändert werden darf.

Der Schalter zum ändern von schnell auf langsam ist der GPIO18

Es funktioniert soweit alles super nur wenn die Zeit läuft darf er nicht von schnell auf langsam um switchen

Hat jemand eine idee wie ich das noch darein bekomme ?

Code: Alles auswählen


from tkinter import *
import RPi.GPIO as GPIO
import time

     



GPIO.setmode(GPIO.BCM)
GPIO.setwarnings (False)
GPIO.setup(15, GPIO.IN)
GPIO.setup(14, GPIO.IN)
GPIO.setup(17, GPIO.IN)
GPIO.setup(18, GPIO.IN)


global flip
global modus
modus=0
flip=2

def exit():
        root.destroy()
        GPIO.cleanup()
        sys.exit()

def save():
        runit()
        labelshow()
        


                         
class StopWatch(Frame):
        global modus
        """ Implements a stop watch frame widget. """                                                                
        def __init__(self, parent=None, **kw):        
            Frame.__init__(self, parent, kw)
            self._start = 0.0        
            self._elapsedtime = 0.0
            self._running = 0
            self.timestr = StringVar()               
            self.makeWidgets()      

        def makeWidgets(self):                         
            """ Make the time label. """
            text="test"
            
            
            l = Label(self,textvariable=self.timestr, bg="white")
            self._setTime(self._elapsedtime)
            l.place(relx=0.5, rely=0.5,anchor=CENTER)
            l.config(font=("Courier 100 bold"))
            l.pack(fill=Y, expand=NO, pady=1, padx=1)
            
            
            
            
        
    
        def _update(self): 
            """ Update the label with elapsed time. """
            self._elapsedtime = time.time() - self._start
            self._setTime(self._elapsedtime)
            self._timer = self.after(50, self._update)
    
        def _setTime(self, elap):
            """ Set the time string to Minutes:Seconds:Hundreths """
            hours = int(elap/60/60)
            if hours >= 1:
                minutes = int(elap/60 - hours*60.0)
                seconds = int(elap - 3600*hours - (minutes * 60))
            else:
                minutes = int(elap/60)
                seconds = int(elap - minutes*60.0)
            self.timestr.set('%02d:%02d:%02d' % (hours, minutes, seconds))

        
        def Start(self):
            global modus
            """ Start the stopwatch, ignore if running. """           
            if not self._running:
              modus=0
              self._elapsedtime = 0.0  
              self._setTime(self._elapsedtime)
              self._start = time.time() - self._elapsedtime
              self._update()
              self._running = 1
            
    
        def Stop(self):
            
            """ Stop the stopwatch, ignore if stopped. """


            if self._running:
                
                self.after_cancel(self._timer)
                self._elapsedtime = time.time() - self._start    
                self._setTime(self._elapsedtime)
                self._running = 0
                
                

#        def bediener(self):
#               self._update()

 

def main():
    global modus
    global setter
    setter=0
    rot= 4.0
    gelb= 1.0
    root = Tk()
    root.configure(background="green2")
    root.geometry("800x480")
    
    sw = StopWatch(root)
    sw.place(relx=0.5,rely=0.5,anchor=CENTER)
#   root.wm_overrideredirect(True)             #Vollbild
#    root.wm_overrideredirect(True)             #Vollbild
    root.update()
    while(True):

        root.update()
        if sw._elapsedtime >= rot:
            root.configure(background="red")
        elif sw._elapsedtime >= gelb:
            root.configure(background="yellow")
        else:
            root.configure(background="green2")
            
        if  GPIO.input(18) == 1:
            setter=2
        if sw._running == 1:
                modus = 1
                rot = 60.0*12               #Farbveränderung für 2 Schnell
                gelb = 60.0*95                                                                                              
        if  GPIO.input(18) == 0 and modus:
                modus = 0
                rot = 60.0*24               #Farbveränderung für 1 Langsam
                gelb = 60.0*19
        if not GPIO.input(15) == 0:
            sw.Stop()
            time.sleep(0.5)
            root.update()
        if not GPIO.input(14) == 0:
            sw.Start()
            time.sleep(0.5)



   
        root.update()

                                


    
    root.mainloop()

if __name__ == '__main__':
    main()

Benutzeravatar
__blackjack__
User
Beiträge: 14087
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@RamazanKaya: Sternchen-Importe sind Böse™. Bei `tkinter` holt man sich da fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Man importiert auch nicht nur Sachen die in `tkinter` definiert werden, sondern auch was dieses Modul seinerseits von woanders importiert.

``as`` verwendet man bei Importen zum Umbenennen aber bei `GPIO` wird gar nichts umbenannt.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Keine Variablen und kein Code der irgendwelche Seiteneffekte hat wie GPIO-Pins zu konfigurieren.

``global`` hat auf Modulebene keinerlei Effekt. Grundsätzlich hat ``global`` in einem Programm auch nichts zu suchen. Wenn man objektorientiert programmiert gibt es dafür auch keine Ausrede.

Ich glaube ich sehe das erste mal ein ``global`` auf Klassenebene — auch das macht hier keinen Sinn. An der Stelle wo es steht verhindert es zu dem das der Docstring tatsächlich einer ist, denn der muss als erster Ausdruck im Klassenkörper stehen. Das gleiche gilt für Funktionen/Methoden.

Die `exit()`- und die `save()`-Funktion werden definiert aber nirgends verwendet. Könnten sie auch gar nicht, denn beide verwenden Objekte die undefiniert sind.

Eingerückt wird mit vier Leerzeichen pro Ebene. Nicht mal vier, mal acht, und mal nur zwei.

Man sollte `GPIO.cleanup()` auf jeden Fall am Ende des Programms aufrufen, das also am besten mit ``try``/``finally`` sicherstellen. Wenn man das macht braucht sehr wahrscheinlich auch die Warnungen von `GPIO` nicht untersdrücken. Sollten da trotzdem welche kommen, sollte man auch deren Ursache beseitigen statt sie stumm zu schalten.

Der Pin 17 wird als Eingang konfiguriert, aber nirgends verwendet.

Für die Pinnummern sollte man auch aussagekräftige Konstanten definieren, statt magische Zahlen mehrfach im Code stehen zu haben.

Einige Namen sind nicht wirklich aussagekräftig.

Was bedeutet `modus` das auf 0 und 1 gesetzt wird? Das sollte man mindestens aus dem Namen oder den Werten erkennen können. Also im Namen beschreiben *was* das wahr oder falsch sein kann — man sollte da dann auch `True` und `False` statt der Zahlen nehmen, damit man gleich weiss das der Wert nicht auch 2 oder -1 sein kann. Oder man definiert sinnvoll benannte Konstanten, beispielsweise mit dem `enum`-Modul. Man kann auch beides machen — guter Name *und* benannte Werte.

`setter` ist noch verwirrender weil diesem Namen die Werte 0 und 2 zugewiesen werden können und der Name wird nirgends lesend verwendet.

Das auf Modulebene definierte `flip` wird mit dem magischen Wert 2 initialisiert. Aber nirgends verwendet.

Bei den Namen `rot` und `gelb` erwartet glaube ich kaum jemand das daran Zeiten in Sekunden gebunden werden, wenn ich das richtig verstanden habe.

Namen sollten nicht kryptisch abgekürzt werden. Wenn man `stopwatch` meint, sollte man nicht `sw` schreiben.

Die `Tk.update()`-Methode sollte man so nicht verwenden. Du schreibst da Deine eigene `mainloop()` was nicht so einfach geht wie es da steht. Die `update()`-Methode sollte man nur verwenden wenn man sehr genau weiss was Tk da alles macht, weil sonst komische Dinge passieren können unter bestimmten Umständen. Wenn man selbst so eine Schleife schreibt, versucht man in der Regel sich auf die normale, ereignisbasierte GUI-Programmierung einzulassen. In dem Programm wird aber bereits alles verwendet was dazu notwendig ist, nämlich die `after()`-Methode um die Schleifendurchläufe dieser Endlosschleife ohne `time.sleep()` und `Tk.update()` in die normale Tk-Hauptschleife zu integrieren.

Der Code in der Schleife greift auf Interna des `StopWatch`-Objekts zu. Also sollte der Code entweder nicht ausserhalb der Klasse stehen oder die Interna sollten eine öffentliche API haben.

Die Rückgabewerte von `GPIO.input()` würde ich nicht mit irgend etwas vergleichen. Und schon gar nicht so verquer wie ``not GPIO.input(15) == 0``. Das kann man mit einer Operation weniger als ``GPIO.input(15) != 0`` oder ``GPIO.input(15) == 1`` ausdrücken und letztlich kann man den Vergleich ganz weg lassen: ``GPIO.input(15)``.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Einige der Methodennamen entsprechen also nicht der Konvention.

`Stopwatch._running` sieht nach einem Flag aus, also `True` und `False` statt 1 und 0. Wobei der Wert redundant zu sein scheint wenn man die `Stopwatch` sauber initialisieren würde und alle Attribute in der `__init__()` und nicht erst irgendwann später definiert. Dann hätte man nämlich das `_timer`-Attribut schon dort und müsste das mit `None` initialisieren. Und dann könnte man den Code so schreiben das dieses Attribut immer den Wert `None` hat wenn die Uhr nicht läuft und die ID die `after()` zurück gibt wenn die Uhr läuft. Wobei eventuell auch der `_start`-Wert diese Aufgabe schon erfüllen kann. Den würde ich nicht auf 0 initialisieren, denn eigentlich hat man ja *keinen* Startzeitpunkt, 0 wäre aber theoretisch ein gültiger Wert für einen Zeitstempel.

Wenn die Zeit nicht von Sachen wie Zeitumstellungen oder -anpassungen betroffen sein soll, wäre `time.monotonic()` eine bessere Wahl als `time.time()`.

`makeWidgets()` als eigene Methode macht nicht viel Sinn.

Die Umrechnung in `_setTime()` ist komisch ausgedrückt. Man würde da eher nur mit ganzen Zahlen rechnen und nicht mit den Stunden anfangen sondern Sekunden, Minuten, und Stunden in der Reihenfolge mit Ganzzahldivision- und Modulo-Operationen ausrechnen. Da braucht's dann keine Sonderbehandlungen per ``if``/``else`` und die beiden Operationen in Form der eingebauten `divmod()`-Funktion ist da sehr praktisch.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
RamazanKaya
User
Beiträge: 2
Registriert: Montag 24. Februar 2020, 21:51

Vielen Dank für die ausführliche Erläuterung, wie beschrieben absoluter Anfänger.
Ich war mir schon vorher sicher, dass das Skript nicht 100% ist, aber es funktionierte bis jetzt gut.
Nur das ich wie gesagt den GPIO 18 während der laufenden Zeit umschalten kann störte mich.
Ich hatte gedacht, dass da nur eine Zeile fehlt, aber so kann man wohl nicht viel damit machen was ?

Auf jedenfall vielen Dank __blackjack__ für deine Antwort
LangeS
User
Beiträge: 2
Registriert: Donnerstag 21. Mai 2020, 16:03

Probier mal das hier:

Code: Alles auswählen

from tkinter import *
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings (False)
GPIO.setup(15, GPIO.IN)
GPIO.setup(14, GPIO.IN)
GPIO.setup(17, GPIO.IN)
GPIO.setup(18, GPIO.IN)

global flip
global modus
modus=0
flip=2

def exit():
        root.destroy()
        GPIO.cleanup()
        sys.exit()

def save():
        runit()
        labelshow()
        
                       
class StopWatch(Frame):
        global modus
        """ Implements a stop watch frame widget. """                                                                
        def __init__(self, parent=None, **kw):        
            Frame.__init__(self, parent, kw)
            self._start = 0.0        
            self._elapsedtime = 0.0
            self._running = 0
            self.timestr = StringVar()               
            self.makeWidgets()      

        def makeWidgets(self):                         
            """ Make the time label. """
            text="test"
            
            l = Label(self,textvariable=self.timestr, bg="white")
            self._setTime(self._elapsedtime)
            l.place(relx=0.5, rely=0.5,anchor=CENTER)
            l.config(font=("Courier 100 bold"))
            l.pack(fill=Y, expand=NO, pady=1, padx=1)
            

        def _update(self): 
            """ Update the label with elapsed time. """
            self._elapsedtime = time.time() - self._start
            self._setTime(self._elapsedtime)
            self._timer = self.after(50, self._update)
    
        def _setTime(self, elap):
            """ Set the time string to Minutes:Seconds:Hundreths """
            hours = int(elap/60/60)
            if hours >= 1:
                minutes = int(elap/60 - hours*60.0)
                seconds = int(elap - 3600*hours - (minutes * 60))
            else:
                minutes = int(elap/60)
                seconds = int(elap - minutes*60.0)
            self.timestr.set('%02d:%02d:%02d' % (hours, minutes, seconds))

        
        def Start(self):
            global modus
            """ Start the stopwatch, ignore if running. """           
            if not self._running:
              modus=0
              self._elapsedtime = 0.0  
              self._setTime(self._elapsedtime)
              self._start = time.time() - self._elapsedtime
              self._update()
              self._running = 1
            
    
        def Stop(self):
            
            """ Stop the stopwatch, ignore if stopped. """


            if self._running:
                
                self.after_cancel(self._timer)
                self._elapsedtime = time.time() - self._start    
                self._setTime(self._elapsedtime)
                self._running = 0


#        def bediener(self):
#               self._update()

 

def main():
    global modus
    global setter
    setter=0
    rot= 4.0
    gelb= 1.0
    lock=0
    root = Tk()
    root.configure(background="green2")
    root.geometry("800x480")
    
    sw = StopWatch(root)
    sw.place(relx=0.5,rely=0.5,anchor=CENTER)
#   root.wm_overrideredirect(True)             #Vollbild
#    root.wm_overrideredirect(True)             #Vollbild
    root.update()
    while(True):

        root.update()
        if sw._elapsedtime >= rot:
            root.configure(background="red")
        elif sw._elapsedtime >= gelb:
            root.configure(background="yellow")
        else:
            root.configure(background="green2")
            
        if  GPIO.input(18) == 1:
            setter=2

        if sw._running == 1 and not lock:
            lock=1 # nur einmal unmittelbar nach start das if ausführen, danach das if "sperren", lock
            modus = 1
            rot = 60.0*12               #Farbveränderung für 2 Schnell
            gelb = 60.0*95
            if GPIO.input(18) == 0:
                modus = 0
                rot = 60.0*24           #Farbveränderung für 1 Langsam
                gelb = 60.0*19
                
        if not GPIO.input(15) == 0:
            sw.Stop()
            time.sleep(0.5)
            root.update()
        if not GPIO.input(14) == 0:
            sw.Start()
            time.sleep(0.5)

   
        root.update()

    root.mainloop()

if __name__ == '__main__':
    main()
Antworten