Seite 1 von 1

Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Mittwoch 2. September 2015, 09:54
von Onkel_Phil
Hallo,
da ich es für angebracht halte mich erst einmal vorzustellen:
Mein Name ist Philipp bin >40 und von Programmieren hab ich nicht viel Ahnung.
Um dies zu ändern brütet mein Hirn immer wieder (für mich) spannende "Kann man das machen?"-Projekte aus... an denen ich dann meist bei der Programmierung ins Stocken gerate.
Zurzeit hängt es mal wieder und leider waren meine Sucherfolge ehr von schwachem Erfolg gekrönt. Wahrscheinlich liegt es auch daran, dass ich das Problem nicht korrekt in Worte, geschweige den mit den richtigen Fachbegriffen umschreiben kann...

Was will ich:
ein Raspi soll ein BMP180 auslesen und den Luftdruck, korrigiert um ein Offset, auf ein touch tft (im Vollbildmodus, der Nutzer soll keinen einfachen Zugriff auf den Desktop haben) ausgeben.
- dieses GUI (Normal) soll enthalten: Luftdruck, Uhrzeit, Luftdruck min/max-wert seit Programmstart, ein Button zum Herunterfahren des gesamten Raspi, ein Button zum Starten der Kalibrierung GUI und ein Label welches das Datum der letzten Kalibrierung anzeigt
- eine zweite GUI soll via Fullscreen Touchfeld (Zahlen 1 bis 9, "." , "OK", "Clear", "Abbruch" es ermöglichen den korrekten Luftdruck einzugeben, eine Routine soll dann die Differenz zum realen Messwert ermitteln und den Offset, sowie den Zeitpunkt der Kalibrierung in eine Datei schreiben.

Das Lesen und schreiben der Offset.txt Datei habe ich im Modul BMP180.py integriert, dort werden auch die Summen/Differenzen gebildet.

Womit ich erhebliche Probleme habe ist die Abfolge der GUI Klassen.
Das diese nicht bzw. nur bedingt linear ablaufen ist mir "fast" klar....
Laufen einmal durch und waren dann auf eine Aktion des Nutzers.

Nun soll aber das Label Druck regelmäßig (Intervall kann 1 min betragen, nicht Zeitkritisch!) neu dargestellt werden, nachdem der Luftdruck ausgelesen wurde, oder eine neue Kalibrierung gemacht wurde (Dann natürlich auch ein neue LastCalDatum).

Bei meinem Versuch aus "def Messen():" ein update in der Klasse Normal auszulösen geht was schief.
Da ich hier nicht weiter weis, hab ich das Listing angehangen und für die, nur auf dem Raspi laufenden Modul, feste Werte vorgegeben sowie den Vollbildmodus deaktiviert.
(das auslesen und der Vollbildmodus funktionieren)
Was ich bisher verstanden habe ist, dass der Aufruf von Normal.update aus Messen() einen zweiten Screen über den ersten legt und dabei ausser den zwei Button nix darstellt... doof...
Ich vermute ich muss beim Aufrufen irgend wie eine Referenz auf den bereits dargestellten Screen herstellen, damit dann die Textvariablen mit neuen Inhalt gefüllt werden und auf dem Screen dargestellt werden... nur wie und wo bekomme ich die Referenzierung hin?

Ich hoffe mir kann das jemand erklären....

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
# GUI

import time
import Tkinter
#import bmp as BMP180

Value_min = 1200.0
Value_max = 0.0


class Cal_tk(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        self.grid()
        self.overrideredirect(1)
        w, h = (self.winfo_screenwidth()/4), (self.winfo_screenheight()/4)
        self.geometry("%dx%d+0+0" % (w, h))
        self.rowconfigure((0,12), weight=1)
        self.columnconfigure((0,3), weight=1)

        OK_CAL = Tkinter.Button(self, text = "OK", command = self.OK_Click)               
        OK_CAL.grid (row=10, column=1, rowspan=1, columnspan=1)

    def OK_Click(self):
        print("Kalibrierung OK")


        
class Normal_tk(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def initialize(self):   
        self.miniscreen = 4 # 1 für Umgebung RPI; 4 für Entwicklung auf Desktop
        self.grid() 
        if (self.miniscreen == 1):
            self.overrideredirect(1)
        w, h = (self.winfo_screenwidth()/self.miniscreen), (self.winfo_screenheight()/self.miniscreen)
        self.geometry("%dx%d+0+0" % (w, h))
        self.rowconfigure((0,12), weight=1)
        self.columnconfigure((0,3), weight=1)     

        self.LastCal = Tkinter.StringVar()
        self.LastCal.set(str("DatumCal"))
        self.Zeit = Tkinter.StringVar()
        self.Zeit.set(time.strftime("%d.%m.%Y %H:%M:%S"))
        self.Druck = Tkinter.StringVar()
        self.Druck.set (1013.12)
        self.Max_Druck = Tkinter.StringVar()
        self.Max_Druck.set(str("Pmax_sample"))
        self.Min_Druck = Tkinter.StringVar()
        self.Min_Druck.set(str("Pmin_sample"))
              
        #Buttons
        Start_CAL = Tkinter.Button(self, text = "Neue Kalibrierung starten", command = self.On_StartCal_Click)               
        Start_CAL.grid (row=10, column=1, rowspan=1, columnspan=1)
        Stop_button = Tkinter.Button(self, text="    Shutdown    ", command = self.On_Shutdown_Click)
        Stop_button.grid (row=12, column=1, rowspan=1, columnspan=1)
       
        #Dynamic Label
        Label_Druck = (Tkinter.Label(self, textvariable = str(self.Druck),
                      foreground="red", font=("Arial", "15", "bold")).
            grid (row=0, column=1, rowspan=1, columnspan=1))   
        Label_Zeit = (Tkinter.Label(self, textvariable = str(self.Zeit)).
                      grid (row=1, column=1, rowspan=1, columnspan=1))
        Label_Max_Druck = (Tkinter.Label(self, textvariable = self.Max_Druck).
                      grid (row=3, column=1, rowspan=1, columnspan=1))
        Label_Min_Druck = (Tkinter.Label(self, textvariable = self.Min_Druck).
                      grid (row=4, column=1, rowspan=1, columnspan=1))     
        LastCal_Label = (Tkinter.Label(self, textvariable = self.LastCal).
                      grid (row=9, column=1, columnspan=1))

    def update(DatumCal, P_Sample, Pmin_sample, Pmax_sample):
        Zeit.set(time.strftime("%d.%m.%Y %H:%M:%S"))
        LastCal.set(DatumCal)
        self.Druck.set(str("aktueller Wert: " + str(P_sample) + " hPa"))
        self.Max_Druck.set(str("maximaler Wert: " + str(self.Pmin_sample) + " hPa"))
        self.Min_Druck.set(str("minimaler Wert: " + str(self.Pmax_sample) + " hPa"))
        
    def On_Shutdown_Click(self):
        print("Shutdown")
        #Raspi sudo init 0 

    def On_StartCal_Click(self):
        print("Neue Kalibrierung")
        # sprung zur Kalibrierung... 

def Messwerte_lesen():
    Mittelwert_Druck = 0.0
    #(LastCalDate, akt_offset) = BMP180.read_offsetfile()
    (LastCalDate, akt_offset) = ("01.09.2015", -1.0)
    for x in range (0,20):
        #(Temp, Druck) = BMP180.readBmp180()
        (Temp, Druck) = (110, 1013.23)
        Mitwert_Druck = Mittelwert_Druck + Druck
    Druck = round(float(Mittelwert_Druck/20.0)+ float(akt_offset), 2)
    time.sleep(0.1)
    Normal_tk(None).update(LastCalDate, Druck, Druck-1.0, Druck+1.0)
    
if __name__ == "__main__":
    app = Normal_tk(None)
    app.after(500, Messwerte_lesen)
    app.mainloop()

    



Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Mittwoch 2. September 2015, 10:09
von BlackJack
@Onkel_Phil: Du müsstest der Funktion doch eindach nur `app` übergeben und dann kann sie darauf die `update()`-Methode aufrufen. Und am Ende dann mit `app.after()` den eigenen nächsten Aufruf in Auftrag geben.

Wobei wenn `Messwerte_lesen()` sonst eigentlich nichts mit der GUI zu tun hat, dann sollte man diese Funktion die Werte zurückgeben lassen und in der GUI dann eine Methode definieren die regelmässig per `after()` diese Funktion aufruft und die Werte darstellt.

Es darf übrigens nur *ein* `Tk`-Exemplar zur gleichen Zeit geben. Das ist das *Haupt*fenster. Weitere Fenster muss man mit `Toplevel` erstellen. Sonst können komische Sachen bis hin zu Programmabstürzen passieren.

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Mittwoch 2. September 2015, 11:06
von Onkel_Phil
Danke für die Antwort.

und, Ok, die drei Sätze muss ich mir gaaaanz langsam zu Gemüte führen (alter Mann eben....)

also einfach app übergeben funktioniert so nicht (mach ich bestimmt falsch...)

die Idee mit dem ***.after(500 Messwerte_lesen(Rückgabewerte[Anzahl2]) find ich spannend. nur wo baut man das ein?

muss das dann als def in der Klasse erfolgen? und wie ruft diese sich selbst rekursiv auf?
ich versuche dass mal in def update(): und den Aufruf im init-Bereich der class?
ich vermute, dass ich dann auch die min/max-Werte in der classe berechnen und erhalten muss, die können ja dann beim Verlassen der Klasse (CalModus) verschwinden.

.....
also ein einfaches
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
self.update.after(500)

gibt ne Fehlermeldung_
self.update.after(500)
AttributeError: 'function' object has no attribute 'after'

so geht's nicht...
ich werde es weiter probieren.....

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Mittwoch 2. September 2015, 11:53
von BlackJack
@Onkel_Phil: Wieso funktioniert `app` übergeben nicht? Also was heisst das? Was hast Du gemacht, was ist passiert. Raten können wir das ja schlecht. ;-)

Die `update()`-Methode hat natürlich kein `after`-Attribut und dann hast Du da gar keine Argumente übergeben selbst wenn die Methode so ein Attribut hätte. Du hast die `after()`-Methode doch schon verwendet in Deinem Code. Diese Methode haben alle Tkinter-Widgets, also zum Beispiel `Tk`- oder `Toplevel`-Exemplare.

Ich würde das ans Ende einer Methode schreiben die sich dann damit selbst aufrufen lässt. Was übrigens nicht rekursiv ist, denn bei Rekursion wären mehrere Aufrufe einer Funktion oder Methode gleichzeitig aktiv. Hier wird der Aufruf ja beendet bevor irgendwann später der Nächste kommt. Ungetestet:

Code: Alles auswählen

    def _read_pressure(self):
        last_date, pressure = messwerte_lesen()
        self.update(last_date, pressure, pressure - 1, pressure + 1)
        self.after(500, self._read_pressure)
Wobei die `update()`-Methode in Deiner Klasse die beiden letzten Argumente überhaupt nicht verwendet‽

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Mittwoch 2. September 2015, 12:58
von Onkel_Phil
also ....
ich habe probiert mit
app.after(500, Messwerte_lesen(app))
Meswerte_lesen aufzurufen und in def Messwerte_lesen(target):
Normal_tk(traget).update(LastCalDate, Druck, Druck-1.0, Druck+1.0)
zu übergeben.
Fehlermeldung:
Traceback (most recent call last):
File "C:\Users\Philipp\Desktop\AA WorkImage 16G Rasp2\fullclass4.py", line 110, in <module>
app.after(500, Messwerte_lesen(app))
File "C:\Users\Philipp\Desktop\AA WorkImage 16G Rasp2\fullclass4.py", line 106, in Messwerte_lesen
Normal_tk(target).update(LastCalDate, Druck, Druck-1.0, Druck+1.0)
File "C:\Users\Philipp\Desktop\AA WorkImage 16G Rasp2\fullclass4.py", line 37, in __init__
Tkinter.Tk.__init__(self,parent)
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1745, in __init__
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
TypeError: must be string or None, not instance

das mit der .after() Attribut kann ich halbwegs nachvollziehen.

Druck-1 und+1 sind waren für Druck min und max zum testen.
wollte erst einmal das Programm zum laufen kriegen bevor ich gimicks einbaue....

auf jeden Fall vielen Dank für deine Hilfe.
ich muss jetzt leider "die Arbeit ruhen" lassen... Tasche packen und den Flieger besteigen.
Darf/werde ich mich in einer Woche wieder melden?!

Gruß
Philipp

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Mittwoch 2. September 2015, 13:22
von cofi
Ich rate mal wie der Code nun aussieht:

Code: Alles auswählen

def Messwerte_lesen(target):
    ...
    Normal_tk(target).update(LastCalDate, Druck, Druck-1.0, Druck+1.0)

if __name__ == "__main__":
    app = Normal_tk(None)
    app.after(500, Messwerte_lesen, app)
    app.mainloop()
Das kann nicht funktionieren, wie dir die Fehlermeldung auch sagt, ist aber auch nicht das was BlackJack im Sinn hatte. Gemeint war:

Code: Alles auswählen

def Messwerte_lesen(target):
    ...
    target.update(LastCalDate, Druck, Druck-1.0, Druck+1.0)
Und benutze in Zukunft bitte Code/Python Tags fuer Code.

Edit: Ich sehe gerade du hast den tatsaechlichen Aufruf ja hingeschrieben. Der funktioniert aber nicht so wie du denkst. Mit `Messwerte_lesen(app)` rufst du die Funktion _direkt_ auf und nicht mit `after` und auch nicht nach einer halben Sekunde.
Da `Messwerte_lesen` auch keinen expliziten Rueckgabewert hat und damit `None` zurueck gibt, wird dir die Zeile noch mit einer Exception um die Ohren fliegen.

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Freitag 11. September 2015, 11:36
von Onkel_Phil
Hallo zusammen,
da ich mich nun wieder diesem "Problem" widmen kann habe ich auch wieder ein paar Fragen....
Ich habe in der Zwischenzeit ein bisschen recherchieren können.
Ich habe hier im Forum unter http://www.python-forum.de/viewtopic.ph ... 6&p=221643
etwas ähnliches gefunden was mir weitergeholfen hat.
Daher habe ich meinen Code umgeschrieben und das daraus gemacht:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
# GUI

import time
import Tkinter as tk
import random
#import bmp as BMP180

Value_min = 1200.0
Value_max = 0.0

""" deaktiviert .... erst muss das Messen funktionieren
class Cal_tk(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        self.grid()
        self.overrideredirect(1)
        w, h = (self.winfo_screenwidth()/4), (self.winfo_screenheight()/4)
        self.geometry("%dx%d+0+0" % (w, h))
        self.rowconfigure((0,12), weight=1)
        self.columnconfigure((0,3), weight=1)

        OK_CAL = Tkinter.Button(self, text = "OK", command = self.OK_Click)               
        OK_CAL.grid (row=10, column=1, rowspan=1, columnspan=1)

    def OK_Click(self):
        print("Kalibrierung OK")
"""
    
class Normal_frame(tk.Frame):
    def __init__(self, parent):
        self.parent = parent
        tk.Frame.__init__(self, app_screen)
        self.grid()

        self.LastCal = tk.StringVar()
        self.LastCal.set(str("DatumCal"))
        self.Zeit = tk.StringVar()
        self.Zeit.set(time.strftime("%d.%m.%Y %H:%M:%S"))
        self.Druck = tk.StringVar()
        self.Druck.set (1013.12)
        self.Max_Druck = tk.StringVar()
        self.Max_Druck.set(str("Pmax_sample"))
        self.Min_Druck = tk.StringVar()
        self.Min_Druck.set(str("Pmin_sample"))
              
        #Buttons
        Start_CAL = tk.Button(self, text = "Neue Kalibrierung starten", command = self.On_StartCal_Click)               
        Start_CAL.grid (row=10, column=1, rowspan=1, columnspan=1)
        Stop_button = tk.Button(self, text="    Shutdown    ", command = self.On_Shutdown_Click)
        Stop_button.grid (row=12, column=1, rowspan=1, columnspan=1)
       
        #Dynamic Label
        Label_Druck = (tk.Label(self, textvariable = str(self.Druck),
                      foreground="red", font=("Arial", "15", "bold")).
                      grid (row=0, column=1, rowspan=1, columnspan=1))   
        Label_Zeit = (tk.Label(self, textvariable = str(self.Zeit)).
                      grid (row=1, column=1, rowspan=1, columnspan=1))
        Label_Max_Druck = (tk.Label(self, textvariable = self.Max_Druck).
                      grid (row=3, column=1, rowspan=1, columnspan=1))
        Label_Min_Druck = (tk.Label(self, textvariable = self.Min_Druck).
                      grid (row=4, column=1, rowspan=1, columnspan=1))     
        LastCal_Label = (tk.Label(self, textvariable = self.LastCal).
                      grid (row=9, column=1, columnspan=1))

    def update(self, DatumCal, P_Sample):
        self.Pmin_sample, self.Pmax_sample = (0.0, 0.0)
        self.Zeit.set(time.strftime("%d.%m.%Y %H:%M:%S"))
        self.LastCal.set(DatumCal)
        self.Druck.set(str("aktueller Wert: " + str(P_Sample) + " hPa"))
        self.Max_Druck.set(str("maximaler Wert: " + str(self.Pmin_sample) + " hPa"))
        self.Min_Druck.set(str("minimaler Wert: " + str(self.Pmax_sample) + " hPa"))
        
    def On_Shutdown_Click(self):
        print("Shutdown")
        #Raspi sudo init 0 

    def On_StartCal_Click(self):
        print("Neue Kalibrierung")
        # sprung zur Kalibrierung... 

def Messwerte_lesen():
    Mittelwert_Druck = 0.0
    #(LastCalDate, akt_offset) = BMP180.read_offsetfile()
    (LastCalDate, akt_offset) = ("01.09.2015", -1.0)
    for x in range (0,20):
        #(Temp, Druck) = BMP180.readBmp180()
        (Temp, Druck) = ((110+random.randint(0, 8)), (1013.23+random.randint(0, 8)))
        Mittelwert_Druck = Mittelwert_Druck + Druck
    Druck = round(float(Mittelwert_Druck/20.0)+ float(akt_offset), 2)
    time.sleep(0.1)
    print(Temp, Druck)
    Normal_frame(app_screen).update(LastCalDate, Druck)
    app_screen.after(500, Messwerte_lesen)
    
    
if __name__ == "__main__":
    
    app_screen = tk.Tk()
    app_screen.grid() 
    app_screen.miniscreen = 4 # 1 für Umgebung RPI; 4 für Entwicklung auf Desktop
    if (app_screen.miniscreen == 1):
        app_screen.overrideredirect(1)
    w, h = (app_screen.winfo_screenwidth()/app_screen.miniscreen), (app_screen.winfo_screenheight()/app_screen.miniscreen)
    app_screen.geometry("%dx%d+0+0" % (w, h))
    app_screen.rowconfigure((0,12), weight=1)
    app_screen.columnconfigure((0,2), weight=1)  
    app_screen.grid()
    Normal_Anzeige = [Normal_frame(app_screen)]
    Messwerte_lesen()
    app_screen.mainloop()
Nun passiert folgendes:
Das Fenster wird dargestellt und es werden kontinuierlich Werte "gemessen" aber leider nicht in den Textvariablen die schon vorhanden sind, sondern es werden immer neue Labels generiert und unten angefügt.
Falls jemand einen Tipp hat? ... wo hab ich es mal wieder nicht verstanden?.

Danke und Gruß

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Freitag 11. September 2015, 12:10
von Sirius3
@Onkel_Phil: du erzeugst ja auch immer neue Frames:

Code: Alles auswählen

Normal_frame(app_screen).update(LastCalDate, Druck)

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Freitag 11. September 2015, 14:50
von Onkel_Phil
hmmm, OK....

und wie verhindert man (also ich) das?

Also wenn ich das richtig verstehe, greife ich zwar auf den richtigen Screen zu, initialisiere aber alle Elemente erneut (in einem extra Frame?)

wie muss man .update(...) aufrufen ohne das die Labels und Button neu erstellt werden und nur die Label-Beschriftung erneut wird?

Bin für jeden Hinweis dankbar

ein Ahha-Moment.... oder das Glück ist mit den Dummen....
hier meine aktuelle Lösung mit .update der Label
(der Hinweis war also sehr hilfreich, Danke.)

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
# GUI

import time
import Tkinter as tk
import random
import os
#import bmp as BMP180

Value_min = 1200.0
Value_max = 0.0 

"""
class Cal_tk(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def initialize(self):
        self.grid()
        self.overrideredirect(1)
        w, h = (self.winfo_screenwidth()/4), (self.winfo_screenheight()/4)
        self.geometry("%dx%d+0+0" % (w, h))
        self.rowconfigure((0,12), weight=1)
        self.columnconfigure((0,3), weight=1)

        OK_CAL = Tkinter.Button(self, text = "OK", command = self.OK_Click)               
        OK_CAL.grid (row=10, column=1, rowspan=1, columnspan=1)

    def OK_Click(self):
        print("Kalibrierung OK")
"""

        
class Normal_frame(tk.Frame):
    def __init__(self, parent):
        self.parent = parent
        tk.Frame.__init__(self, app_screen)

        app_screen.rowconfigure((0,12), weight=1)
        app_screen.columnconfigure((0,3), weight=1)
        
        self.LastCal = tk.StringVar()
        self.Zeit = tk.StringVar()
        self.Druck = tk.StringVar()
        self.Max_Druck = tk.StringVar()
        self.Min_Druck = tk.StringVar()
              
        #Buttons
        Start_CAL = tk.Button(self, text = "Neue Kalibrierung starten", command = On_StartCal_Click)               
        Start_CAL.grid (row=12, column=1, rowspan=1, columnspan=1)
        Stop_button = tk.Button(self, text="    Shutdown    ", command = On_Shutdown_Click)
        Stop_button.grid (row=12, column=2, rowspan=1, columnspan=1)
       
        #Dynamic Label
        Label_Druck = (tk.Label(self, textvariable = str(self.Druck),
                      foreground="red", font=("Arial", "15", "bold")).
                      grid (row=0, column=1, rowspan=1, columnspan=1))   
        Label_Zeit = (tk.Label(self, textvariable = str(self.Zeit)).
                      grid (row=1, column=1, rowspan=1, columnspan=1))
        Label_Max_Druck = (tk.Label(self, textvariable = self.Max_Druck).
                      grid (row=3, column=1, rowspan=1, columnspan=1))
        Label_Min_Druck = (tk.Label(self, textvariable = self.Min_Druck).
                      grid (row=4, column=1, rowspan=1, columnspan=1))     
        LastCal_Label = (tk.Label(self, textvariable = self.LastCal).
                      grid (row=9, column=1, columnspan=1))
        self.grid()
               
    def update(self, DatumCal, P_Sample):
        global Value_min, Value_max
        if Value_min > P_Sample:
            Value_min = P_Sample
        if Value_max < P_Sample:
            Value_max = P_Sample
        self.Zeit.set(time.strftime("%d.%m.%Y %H:%M:%S"))
        self.LastCal.set(DatumCal)
        self.Druck.set(str("aktueller Wert: " + str(P_Sample) + " hPa"))
        self.Max_Druck.set(str("maximaler Wert: " + str(Value_min) + " hPa"))
        self.Min_Druck.set(str("minimaler Wert: " + str(Value_max) + " hPa"))
        
def On_Shutdown_Click():
    print("Shutdown")
    # Shutdown
    # os.system('shutdown now -h') # Funktioniert erst in der final aktivieren
     
def On_StartCal_Click():
    print("Neue Kalibrierung")
    # Sprung zur Kalibrierung... 

def Messwerte_lesen():
    global Normal_Anzeige
    global app_screen
    Mittelwert_Druck = 0.0
    #(LastCalDate, akt_offset) = BMP180.read_offsetfile()
    (LastCalDate, akt_offset) = ("01.09.2015", -1.0)
    for x in range (0,20):
        #(Temp, Druck) = BMP180.readBmp180()
        # time.sleep(0.1)
        (Temp, Druck) = ((110+random.randint(0, 8)), (1013.23+random.randint(0, 8)))
        Mittelwert_Druck = Mittelwert_Druck + Druck
    Druck = round(float(Mittelwert_Druck/20.0)+ float(akt_offset), 2)
    Werteframe.update(LastCalDate, Druck)
    app_screen.after(2500, Messwerte_lesen)
    
if __name__ == "__main__":
   
    app_screen = tk.Tk()
    app_screen.miniscreen = 4 # 1 für Umgebung RPI; 4 für Entwicklung auf Desktop
    if (app_screen.miniscreen == 1):
        app_screen.overrideredirect(1)
    w, h = (app_screen.winfo_screenwidth()/app_screen.miniscreen), (app_screen.winfo_screenheight()/app_screen.miniscreen)
    app_screen.geometry("%dx%d+0+0" % (w, h))
    Werteframe = Normal_frame(app_screen)
    Messwerte_lesen()   
    app_screen.mainloop()


Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Freitag 11. September 2015, 20:16
von BlackJack
@Onkel_Phil: Ein paar Anmerkungen zum Code:

Die Namensschreibweisen halten sich zum Teil nicht an den Style Guide for Python Code.

Auf Modulebene sollten nur Konstanten, Funktionen, und Klassen definiert werden. Das Hauptprogramm steht nicht auf Modulebene sondern in einer Funktion die üblicherweise `main()` heisst. Dann wirst Du das ”Problem” haben das Du auf Variablennamen die Du auf Modulebene definiert hast nicht mehr einfach so zugreifen kannst. Was gut ist, denn so etwas ist total unübersichtlich. Werte die von einer Funktion oder Methode verwendet werden sollten diese als Argumente betreten und als Rückgabewert verlassen. Und nicht einfach so aus der ”Umgebung” genommen oder gar mit ``global`` auch noch verändert werden. ``global`` geht gar nicht. Insbesondere nicht wenn man schon Klassen verwendet, denn dann gibt es dafür echt keine Ausrede mehr.

Um ``if``-Bedingungen gehören keine Klammern.

Bei `Normal_frame.__init__()` bindest Du das `parent`-Argument an das Objekt — was nicht nötig ist — aber beim Aufruf der `__init__()` von der Basisklasse verwendest Du `app_screen`, was es an der Stelle gar nicht geben dürfte weil das wie oben schon geschrieben ein lokaler Name in der Hauptfunktion sein sollte.

Die `StringVar`-Exemplare würde ich persönlich ja erst einmal bleiben lassen bis man so eine Indirektion tatsächlich mal braucht.

``rowspan=1`` und ``columnspan=1`` sind Angaben die man sich sparen kann.

Die Namen bei dem erstellen von den `Label`\n werden a) nicht benutzt und werden b) alle an den Wert `None` gebunden, was wenig nützlich ist.

Widgets sollten sich in ihrer `__init__()` nicht selber „layouten“. Das macht auch keins der von Tkinter zur Verfügung gestellten Widgets, weil man damit den Ersteller dadurch einschränkt was er mit dem Widget anfangen kann.

Die `Messwerte_lesen()`-Funktion greift auf `Werteframe` und `app_screen` zurück welche nicht auf Modulebene bekannt sein sollten. Da *jedes* Widget eine `after()`-Methode besitzt, braucht man `app_screen` auch nicht unbedingt. Und `Werteframe` sollte man einfach als Argument übergeben. Das hatten wir doch schon mal in einem älteren Beitrag‽

Der Name `Mittelwert_Druck` ist inhaltlich falsch, das ist kein Mittelwert sondern eine Summe aller Druckmessungen in der Schleife.

Die Anzahl der Messungen für den Durchschnittswert steht zweimal literal im Quelltext. So etwas ist immer eine beliebte Fehlerquelle, weil man schnell mal vergisst beide Werte anzupassen oder sich bei einem vertippt.

Bei der Berechnung vom Druck sind die `float()`-Aufrufe überflüssig und das `round()` gehört da auch nicht hin. Wenn man die Anzahl für die Nachkommastellen bei der Anzeige beschränken will, dann ist das eine Aufgabe für die Formatierung der Werte als Zeichenkette.

Wenn man der Lesefunktion Argumente mitgibt, dann muss man die natürlich auch bei `after()` angeben, damit die auch beim nächsten Aufruf wieder übergeben werden. Die Funktion wäre aber vielleicht als Methode auf dem `Normal_frame` besser aufgehoben. Zumindest ein Teil davon, denn die Durchschnittsbildung der Messungen und die Minimal- und Maximalwerte gehören IMHO nicht in die GUI sondern sind Geschäftslogik.

Bei der Aktualisierung der GUI-Texte sind viele unnötige `str()`-Aufrufe. Eine Zeichenkette wird dadurch nicht mehr zur Zeichenkette in dem man nochmal `str()` damit aufruft. Das ist eine Nulloperation. Werte sollte man per Zeichenkettenformatierung in Zeichenketten einfügen und da nicht mit `str()` und ``+`` etwas zusammenstückeln. Das ist eher BASIC als Python.

Ich komme dann als Zwischenergebnis ungefähr zu so etwas:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
from __future__ import absolute_import, division, print_function
import random
import time
import Tkinter as tk
#import bmp as BMP180
 
       
class NormalFrame(tk.Frame):

    def __init__(self, parent):
        self.parent = parent
        tk.Frame.__init__(self, parent)
        self.max_pressure = 0.0
        self.min_pressure = 1200.0
 
        self.pressure_label = tk.Label(
            self, foreground='red', font=('Helvetica', '15', 'bold')
        )
        self.pressure_label.grid(row=0, column=1)
        self.time_label = tk.Label(self)
        self.time_label.grid(row=1, column=1)
        self.max_pressure_label = tk.Label(self)
        self.max_pressure_label.grid(row=2, column=1)
        self.min_pressure_label = tk.Label(self)
        self.min_pressure_label.grid(row=3, column=1)
        self.last_calibration_date_label = tk.Label(self)
        self.last_calibration_date_label.grid(row=4, column=1)

        tk.Button(
            self, text='Neue Kalibrierung starten', command=do_calibration
        ).grid(row=5, column=1)
        tk.Button(self, text='Shutdown', command=do_shutdown).grid(
            row=5, column=2
        )
               
    def update_values(self, last_calibration_date, pressure):
        self.min_pressure = min(self.min_pressure, pressure)
        self.max_pressure = max(self.max_pressure, pressure)
        self.pressure_label['text'] = 'aktueller Wert: {0:.2f} hPa'.format(
            pressure
        )
        self.time_label['text'] = time.strftime('%d.%m.%Y %H:%M:%S')
        self.max_pressure_label['text'] = 'maximaler Wert: {0:.2f} hPa'.format(
            self.max_pressure
        )
        self.min_pressure_label['text'] = 'minimaler Wert: {0:.2f} hPa'.format(
            self.min_pressure
        )
        self.last_calibration_date_label['text'] = last_calibration_date


def do_shutdown():
    print('Shutdown')
    # Shutdown
    # os.system('shutdown now -h') # Funktioniert erst in der final aktivieren
     

def do_calibration():
    print('Neue Kalibrierung')
    # Sprung zur Kalibrierung...


def read_measurements(values_frame):
    # last_calibration_date, current_offset = BMP180.read_offsetfile()
    last_calibration_date, current_offset = ('01.09.2015', -1.0)
    pressure_sum = 0.0
    sample_count = 20
    for _ in xrange(sample_count):
        # _temperature, pressure = BMP180.readBmp180()
        # time.sleep(0.1)
        _temperature, pressure = (
            (110 + random.randint(0, 8)), (1013.23 + random.randint(0, 8))
        )
        pressure_sum = pressure_sum + pressure
    average_pressure = pressure_sum / sample_count + current_offset
    values_frame.update_values(last_calibration_date, average_pressure)
    values_frame.after(2500, read_measurements, values_frame)


def main():   
    root = tk.Tk()
    root.miniscreen = 4 # 1 für Umgebung RPI; 4 für Entwicklung auf Desktop
    if root.miniscreen == 1:
        root.overrideredirect(True)
    width, height = (
        root.winfo_screenwidth() // root.miniscreen,
        root.winfo_screenheight() // root.miniscreen,
    )
    root.geometry('{0}x{1}+0+0'.format(width, height))

    values_frame = NormalFrame(root)
    values_frame.pack(side=tk.TOP)
    read_measurements(values_frame)  
    root.mainloop()


if __name__ == '__main__':
    main()

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Samstag 12. September 2015, 06:48
von Onkel_Phil
heydernei......

habe mir gerade deine ausführlichen Kommentare durchgelesen.
(am besten fand ich "... das ist BASIC...." naja, wer mit einem C64 angefangen hat der darf das :D )
Und da das meine ersten Gehversuche mit "class" sind, kann ich hier viel lernen - ganz verstanden hab ich das auch noch nicht.
Sieht so aus, also ob ich mir hier mal wieder einen zu großen Brocken vorgenommen habe.

Deine anderen Anmerkungen werde ich mir alle in Ruhe durchlesen und ggf. deinen Code als weitere Grundlage heranziehen.
Ich bin mir sicher ich werde hier noch die ein oder andere Frage stellen...

Danke und Gruß

EDIT:

eine Frage habe ich bereits....

bei dem Versuch das Layout meinen Vorstellungen anzupassen habe ich bei meinem Code festgestellt, dass der Gridmanager sich nicht an meine Row-Wünsche hält.
Ich hatte diese Problem erst einmal vertagt. Ich wollte die Button etwas von einander trennen um auf dem kleinen RaspiDisplay für Ordnung zu sorgen. Hat nicht so gut geklappt.
Daher hab ich das mit dem Code von BlackJack ausprobiert, da geht das auch nicht.
...werden vom Gridmanager nicht genutzte Spalten/Reihen ausgeblendet?

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Montag 14. September 2015, 12:49
von BlackJack
@Onkel_Phil: Spalten und Zeilen haben eine Breite und Höhe die jeweils dem breitesten und höchsten Inhalt einer der Zellen entspricht. Und wenn in allen Zellen nix drin ist…

Zusätzliche Abstände steuert man mit den Padding-Argumenten beim `grid()`-Aufruf und nicht über ganze Zeilen/Spalten.

Edit: Und wenn man expandierenden Zeilen/Spalten haben möchte dann muss man das Grid auf dem Widget konfigurieren auf dem man auch die Widgets in diesem Grid platziert hat. Die Aufrufe in Deinem Code, die ich einfach rausgelöscht hatte aus meiner Version, waren auf dem falschen Widget. Deine Elemente waren auf einem Frame im Grind angeordnet, Du hast aber das Grid auf dem Hauptfenster konfiguriert, was keine Auswirkungen auf das Grid im Frame hat.

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Mittwoch 16. September 2015, 19:24
von Onkel_Phil
Ah, Option verstanden und eingebaut.
Danke.

Sieht jetzt schön, d. h. gewollt! unspektakulär aus:
Bild.

jetzt kann ich mich an den Softwaretechnischen Kalibriervorgang machen.

Gruß aus Berlin

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Sonntag 4. Oktober 2015, 09:42
von Onkel_Phil
Hallo zusammen,
nach erheblicher Programmierabstinenz (wenn man das den programmieren nennen darf :D ), habe ich ein Code gestrickt, der zumindest funktioniert....

Leider hackt es an den ein oder anderen Stelle.
Ganz oben auf der Liste "ist blöde" steht die Laufgeschwindigkeit. Das Programm braucht ca 5 bis 10 Sekunden, bis es aus dem LXTerminal auf dem Raspi die Oberfläche anzeigt.
Von der Hauptoberfläche zum Kalibriermodus sind es ca. 1 bis 3 Sekunden (noch halbwegs erträglich) aber vom Kalibriermodus zurück wieder eine gefühlte Ewigkeit mit (und das sollte nicht sein) Blick auf den RaspiDesktop.

Da ich annehme, dass ich wieder grundlegende "Style Guide for Python Code" verletzt habe, bitte ich euch hier nicht allzu scharf mit mir ins Gericht zu gehen (Kritik ist aber erwünscht).
Wichtiger wäre mir zu verstehen, warum das Prog. so zäh läuft.

Ein zweites Problem habe ich mit dem LF im offsetfile.
Hier schreibt und liest das Prog den Datensatz (Datum und Offset) zwar so, dass es diesen auch wieder lesen und nutzen kann, aber wenn ich das File mit auch einen WinX Rechner zur Auswertung hole, dann stehen die in einer Endloszeile.

SO und jetzt die CodeZeilen:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
from __future__ import absolute_import, division, print_function
import random
import time
import Tkinter as tk
import os
import platform

try:
    import bmp as BMP180
except ImportError:
    import  bmpX as BMP180 #mit Zufallswerten für die Windows Testumgebung 

       
class CalFrame(tk.Frame):

    def __init__(self, parent):
        self.parent = parent
        tk.Frame.__init__(self, parent)
        self.spany = 8
        self.pressure, self.drift = read_average_measurements(200) #alter offset (drift) ist im Druckwert enthalten
        self.pressure -= self.drift
        self.pressure_label = tk.Label(
            self, foreground='red', font=('Helvetica', '15', 'bold')
        )
        self.pressure_label.grid(row=0, column=1, columnspan=2, pady=(20,10))
        self.Button_plus_1 = tk.Button(
            self, text='+ 1.0 hPa', command=lambda: self.do_plus_x(+1)
        )
        self.Button_plus_1.grid(row=1, sticky="W,E,N,S", column=1, pady=(self.spany,self.spany))
        self.Button_minus_1 = tk.Button(
            self, text='- 1.0 hPa', command=lambda: self.do_plus_x(-1)
        )
        self.Button_minus_1.grid(row=2, sticky="W,E,N,S", column=1, pady=(self.spany,self.spany))       
        self.Button_plus_001 = tk.Button(
            self, text='+ 0.01hPa', command=lambda: self.do_plus_x(+0.01)
        )
        self.Button_plus_001.grid(row=1, sticky="W,E,N,S", column=2, pady=(self.spany,self.spany))
        self.Button_minus_001 = tk.Button(
            self, text='- 0.01hPa', command=lambda: self.do_plus_x(-0.01)
        )
        self.Button_minus_001.grid(row=2, sticky="W,E,N,S", column=2, pady=(self.spany,self.spany))
        self.Button_Abbruch = tk.Button(
            self, text='Abbruch', command=lambda: self.restart()
        )
        self.Button_Abbruch.grid(row=3, sticky="W,E,N,S", column=1, pady=(self.spany,self.spany))
        self.Button_OK = tk.Button(
            self, text='OK', command=lambda: self.do_write_new_calibration_datafile(self.drift)
        )
        self.Button_OK.grid(row=3, sticky="W,E,N,S", column=2, pady=(self.spany,self.spany))
        self.pressure_label['text'] = 'aktueller Mittelwert: {0:.2f} hPa'.format(
            self.pressure+self.drift
        )
        
    def do_plus_x(self, x): # Anpassen des alten Driftwertes um x
        self.drift += x
        self.pressure_label['text'] = 'aktueller Mittelwert: {0:.2f} hPa'.format(
            self.pressure+self.drift
        )

    def do_write_new_calibration_datafile(self, cal_offset):
        timestamp = time.strftime('%d.%m.%Y')
        BMP180.write_offsetfile(timestamp, cal_offset)
        self.restart()
        
    def restart(self):
        self.parent.destroy()
        main()
 
       
class NormalFrame(tk.Frame):

    def __init__(self, parent):
        self.parent = parent
        tk.Frame.__init__(self, parent)
        self.max_pressure = 0.0
        self.min_pressure = 1200.0
        self.calmodus = False

        self.pressure_label = tk.Label(
            self, foreground='red', font=('Helvetica', '15', 'bold')
        )
        self.pressure_label.grid(row=0, column=1, pady=(20,10))
        self.time_label = tk.Label(self)
        self.time_label.grid(row=1, column=1)
        self.max_pressure_label = tk.Label(self)
        self.max_pressure_label.grid(row=2, column=1)
        self.min_pressure_label = tk.Label(self)
        self.min_pressure_label.grid(row=3, column=1)
        self.last_calibration_date_label = tk.Label(self)
        self.last_calibration_date_label.grid(row=4, column=1)
        self.NeueKal_Button = tk.Button(
            self, text='Neue Kalibrierung starten', command=lambda: self.do_calibration(self.calmodus)
        )
        self.NeueKal_Button.grid(row=5, column=1, pady=(10,10))
        self.Ende_Button = tk.Button(
            self, text='Shutdown', command=do_shutdown
        )
        self.Ende_Button.grid(row=6, column=1, pady=(10,10))
    
    def update_values(self, last_calibration_date, pressure):        
        self.min_pressure = min(self.min_pressure, pressure)
        self.max_pressure = max(self.max_pressure, pressure)
        self.pressure_label['text'] = 'aktueller Wert: {0:.2f} hPa'.format(
            pressure
        )
        self.time_label['text'] = time.strftime('%d.%m.%Y    -    %H:%M:%S')
        self.max_pressure_label['text'] = 'maximaler Wert: {0:.2f} hPa'.format(
            self.max_pressure
        )
        self.min_pressure_label['text'] = 'minimaler Wert: {0:.2f} hPa'.format(
            self.min_pressure
        )
        self.last_calibration_date_label['text'] = 'letzte Kalibrierung: {0}'.format(
            last_calibration_date
        )

    def do_calibration(self, calmodus):
        if self.calmodus == True:
            self.calmodus = False
        elif self.calmodus == False:
            self.calmodus = True
        #print (self.calmodus)

    def clear(self):
        self.pressure_label.grid_forget()
        self.time_label.grid_forget()
        self.max_pressure_label.grid_forget()
        self.min_pressure_label.grid_forget()
        self.last_calibration_date_label.grid_forget()
        self.NeueKal_Button.grid_forget()
        self.Ende_Button.grid_forget()  #grid_forget
        self.CAL_label = tk.Label(
            self, foreground='red', font=('Helvetica', '15', 'bold')
        )
        self.CAL_label['text'] = 'Kalibriermodus'.format(
            "!"
        )
        self.CAL_label.grid()
        

def do_shutdown():
    #print('Shutdown')
    # Shutdown
    os.system('shutdown now -h') # Funktion erst in der Final aktivieren

def do_write_new_calibration_datafile(cal_offset, cal_frame_to_destroy):
    timestamp = time.strftime('%d.%m.%Y')
    #print(timestamp, cal_offset) 
    BMP180.write_offsetfile(timestamp, cal_offset)
    wait_label = tk.Label(cal_frame_to_destroy)
    wait_label.grid(row=2, column=1)
    time.sleep(1)
    cal_frame_to_destroy.destroy()
    main()

def stop_calibration():
    main()
    

def read_average_measurements(sample_count):
    last_calibration_date, current_offset = BMP180.read_offsetfile()
    pressure_sum = 0.0
    for _ in xrange(sample_count):
        _temperature, pressure = BMP180.readBmp180()
        pressure_sum = pressure_sum + pressure
    average_pressure = pressure_sum / sample_count + float(current_offset)
    return(average_pressure, float(current_offset))

def read_measurements(values_frame, cal_frame):
    last_calibration_date, current_offset = BMP180.read_offsetfile()
    pressure_sum = 0.0
    sample_count = 30

    for _ in xrange(sample_count):
        _temperature, pressure = BMP180.readBmp180()
        pressure_sum = pressure_sum + pressure
    average_pressure = pressure_sum / sample_count + float(current_offset)

    if values_frame.calmodus == False:
        values_frame.update_values(last_calibration_date, average_pressure)
        values_frame.after(200, read_measurements, values_frame, cal_frame)
    else:
        values_frame.clear()
        cal_frame.pack(side=tk.TOP)


def platform_test():
    # Windows oder Linux - erster Buchstabe W oder L
    # 1 für Umgebung RPI; 4 für Entwicklung auf Desktop
    first_letter = platform.platform()[0]
    if first_letter == "L":
        environment = 1
    elif first_letter == "W":
        environment = 4
    return(environment)


def main():
    screen_faktor = platform_test()
    root = tk.Tk()
    root.miniscreen = screen_faktor # 1 für Umgebung RPI; 4 für Entwicklung auf Desktop
    if root.miniscreen == 1:
        root.overrideredirect(True)
    width, height = (
        root.winfo_screenwidth() // root.miniscreen,
        root.winfo_screenheight() // root.miniscreen,
    )
    root.geometry('{0}x{1}+0+0'.format(width, height))

    values_frame = NormalFrame(root)
    cal_frame = CalFrame(root)
    values_frame.pack(side=tk.TOP)
    read_measurements(values_frame, cal_frame)
       
    root.mainloop()
    

if __name__ == '__main__':

    #runlevel = 0 # Runlevel 0: Normal Modus; runlevel 1: Cal Modus
    main()

und hier die kastriere bmpX.py Datei um das unter Windows zum testen laufen zu lassen:

Code: Alles auswählen

#import smbus
import string
import time
from ctypes import c_short
import os.path
import random
 
DEVICE = 0x77 # Default device I2C address
 
#bus = smbus.SMBus(1) # Rev 2 Pi uses 1 
 
def convertToString(data):
  # Simple function to convert binary data into
  # a string
  return str((data[1] + (256 * data[0])) / 1.2)

def getShort(data, index):
  # return two bytes from data as a signed 16-bit value
  return c_short((data[index] << 8) + data[index + 1]).value

def getUshort(data, index):
  # return two bytes from data as an unsigned 16-bit value
  return (data[index] << 8) + data[index + 1]

def readBmp180Id(addr=DEVICE):
  # Register Address
  REG_ID     = 0xD0
  (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2)
  return (chip_id, chip_version)

def read_offsetfile():
  diff = -1.0
  Date = "01.02.2015"
  if os.path.isfile('/home/pi/BaroMBBM/offset.txt'):
    with open("/home/pi/BaroMBBM/offset.txt", 'r+') as f:
      for line in f:
        if len(line)>5:
          (Date, diff) = line.split(" ")
  #offsetfile.close() nicht noetig ... macht with
  return (Date, diff)

def write_offsetfile(Date, diff):
  if os.path.isfile('/home/pi/BaroMBBM/offset.txt'):
    with open("/home/pi/BaroMBBM/offset.txt", 'a') as f:
      f.write(str(Date) + " " +str(diff)+"\n")
  #offsetfile.close() nicht noetig ... macht with
  return 

       
def readBmp180(addr=DEVICE):
  return (((110+random.randint(0, 8)), (1013.23+random.randint(0, 8))))




Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Sonntag 4. Oktober 2015, 18:12
von wuf
Hi Onkel_Phil

Hier ein face lifting deines Skripts. Dein Programmierstil ist nicht meiner darum diese Änderung. Durch copy & paste übernahm ich einiges aus deinem Skript, welches ich anderst schreiben würde. Das Skript importiert dein kastriertes bmpX.py als Dummy-BMP180. Bis jetzt konnte ich keinen zähen Ablauf feststellen. Eventuell könnte dies dein wirklicher BMP180 Zugriff bewirken.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function
from functools import partial
import random
import time
import os
import platform

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
    import tkFont as fnt
except:
    # Tkinter for Python 3.xx
    import tkinter as tk
    import tkinter.font as fnt

try:
    import bmp as BMP180
except ImportError:
    import  bmpX as BMP180 #mit Zufallswerten für die Windows Testumgebung


APP_TITLE = "BMP180 Barometer"

DISPLAY_TEXT_01 = 'aktueller Wert: {0:.2f} hPa'
DISPLAY_TEXT_02 = 'maximaler Wert: {0:.2f} hPa'
DISPLAY_TEXT_03 = 'minimaler Wert: {0:.2f} hPa'
DISPLAY_TEXT_04 = 'letzte Kalibrierung: {0}'
DISPLAY_TEXT_05 = 'aktueller Mittelwert: {0:.2f} hPa'

CAL_WINDOW_TITLE = 'Kalibrieren'


class CalFrame(tk.Frame):
 
    def __init__(self, parent, **options):
        self.parent = parent
        self.toplevel = tk.Toplevel(parent)
        self.toplevel.title(CAL_WINDOW_TITLE)
        self.toplevel.protocol("WM_DELETE_WINDOW", self.close)
        
        self.cal_header_label = tk.Label(self.toplevel, foreground='red',
            text='Kalibriermodus', font=('Helvetica', '15', 'bold'))
        self.cal_header_label.pack()
        
        tk.Frame.__init__(self, self.toplevel, **options)
        
        self.spany = 8
        self.pressure_var = tk.StringVar()
        
        # alter offset (drift) ist im Druckwert enthalten
        self.pressure, self.drift = parent.read_average_measurements(200)
        self.pressure -= self.drift
        
        self.pressure_label = tk.Label(self, textvariable=self.pressure_var,
            foreground='red', font=('Helvetica', '15', 'bold'))
        self.pressure_label.grid(row=0, column=1, columnspan=2, pady=(10,10))
        
        self.Button_plus_1 = tk.Button(
            self, text='+ 1.0 hPa', command=lambda: self.do_plus_x(+1))
        self.Button_plus_1.grid(
            row=1, sticky="W,E,N,S", column=1, pady=(self.spany,self.spany))
            
        self.Button_minus_1 = tk.Button(
            self, text='- 1.0 hPa', command=lambda: self.do_plus_x(-1))
        self.Button_minus_1.grid(
            row=2, sticky="W,E,N,S", column=1, pady=(self.spany,self.spany))
                  
        self.Button_plus_001 = tk.Button(
            self, text='+ 0.01hPa', command=lambda: self.do_plus_x(+0.01))
        self.Button_plus_001.grid(
            row=1, sticky="W,E,N,S", column=2, pady=(self.spany,self.spany))
            
        self.Button_minus_001 = tk.Button(
            self, text='- 0.01hPa', command=lambda: self.do_plus_x(-0.01))
        self.Button_minus_001.grid(
            row=2, sticky="W,E,N,S", column=2, pady=(self.spany,self.spany))
            
        self.Button_Abbruch = tk.Button(
            self, text='Abbruch', command=lambda: self.close())
        self.Button_Abbruch.grid(
            row=3, sticky="W,E,N,S", column=1, pady=(self.spany,self.spany))
            
        self.Button_OK = tk.Button(self, text='OK',
            command=lambda: self.do_write_new_calibration_datafile(self.drift))
        self.Button_OK.grid(
            row=3, sticky="W,E,N,S", column=2, pady=(self.spany,self.spany))
            
        self.pressure_var.set(DISPLAY_TEXT_05.format(self.pressure+self.drift))
        
    def do_plus_x(self, x):
        """Anpassen des alten Driftwertes um x"""
        self.drift += x
        self.pressure_var.set(DISPLAY_TEXT_05.format(self.pressure+self.drift))
        
    def do_write_new_calibration_datafile(self, cal_offset):
        timestamp = time.strftime('%d.%m.%Y')
        BMP180.write_offsetfile(timestamp, cal_offset)
        self.close()
    
    def close(self):
        self.parent.close_cal_window ()
        self.toplevel.destroy()
        
        
class ValueFrame(tk.Frame):
 
    def __init__(self, parent):
        self.parent = parent
        tk.Frame.__init__(self, parent)
        self.max_pressure = 0.0
        self.min_pressure = 1200.0
        
        self.pressure_var = tk.StringVar()
        self.time_var = tk.StringVar()
        self.max_var = tk.StringVar()
        self.min_var = tk.StringVar()
        self.last_cal_var = tk.StringVar()
        
        self.pressure_label = tk.Label(self, foreground='red',
            textvariable=self.pressure_var, font=('Helvetica', '15', 'bold'))
        self.pressure_label.grid(row=0, column=1, pady=(20,10))
        
        self.time_label = tk.Label(self, textvariable=self.time_var)
        self.time_label.grid(row=1, column=1)
        
        self.max_pressure_label = tk.Label(self, textvariable=self.max_var)
        self.max_pressure_label.grid(row=2, column=1)
        
        self.min_pressure_label = tk.Label(self, textvariable=self.min_var)
        self.min_pressure_label.grid(row=3, column=1)
        
        self.last_calibration_date_label = tk.Label(self,
            textvariable=self.last_cal_var)
        self.last_calibration_date_label.grid(row=4, column=1)
        
        self.NeueKal_Button = tk.Button(
            self, text='Neue Kalibrierung starten',
            command=self.parent.open_cal_window)
            
        self.NeueKal_Button.grid(row=5, column=1, pady=(10,10))
        
        self.Ende_Button = tk.Button(
            self, text='Shutdown', command=parent.do_shutdown)
        self.Ende_Button.grid(row=6, column=1, pady=(10,10))
   
    def update_values(self, last_calibration_date, pressure):        
        self.min_pressure = min(self.min_pressure, pressure)
        self.max_pressure = max(self.max_pressure, pressure)

        self.pressure_var.set(DISPLAY_TEXT_01.format(pressure))
        self.time_var.set(time.strftime('%d.%m.%Y    -    %H:%M:%S'))
        self.max_var.set(DISPLAY_TEXT_02.format(self.max_pressure))
        self.min_var.set(DISPLAY_TEXT_03.format(self.min_pressure))
        self.last_cal_var.set(DISPLAY_TEXT_04.format(last_calibration_date))

         
class Application(tk.Frame):

    def __init__(self, master):
        self.master = master
        self.master.protocol("WM_DELETE_WINDOW", self.do_shutdown)
        tk.Frame.__init__(self, master)
        
        self.calmodus = False
        self.values_frame = ValueFrame(self)
        self.values_frame.pack(side=tk.TOP)
        
        self.read_measurements()

    def open_cal_window(self):
        self.calmodus = True
        #self.values_frame.clear()
        self.cal_frame = CalFrame(self, bd=10)
        self.cal_frame.pack(side=tk.TOP)
    
    def close_cal_window(self):
        self.calmodus = False
       
    def read_average_measurements(self, sample_count):
        last_calibration_date, current_offset = BMP180.read_offsetfile()
        pressure_sum = 0.0
        for _ in xrange(sample_count):
            _temperature, pressure = BMP180.readBmp180()
            pressure_sum = pressure_sum + pressure
        average_pressure = pressure_sum / sample_count + float(current_offset)
        return(average_pressure, float(current_offset))
     
    def read_measurements(self):
        last_calibration_date, current_offset = BMP180.read_offsetfile()
        pressure_sum = 0.0
        sample_count = 30
     
        for _ in xrange(sample_count):
            _temperature, pressure = BMP180.readBmp180()
            pressure_sum = pressure_sum + pressure
        average_pressure = pressure_sum / sample_count + float(current_offset)
     
        if self.calmodus == False:
            self.values_frame.update_values(last_calibration_date,
                average_pressure)
                
        self.values_frame.after(200, self.read_measurements)
        
    def do_shutdown(self):
        print("Application-Shutdown")
        self.master.destroy()

    
def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    #app_win.miniscreen = screen_faktor # 1 für Umgebung RPI; 4 für Entwicklung auf Desktop
    #if app_win.miniscreen == 1:
        #app_win.overrideredirect(True)
    #width, height = (
        #app_win.winfo_screenwidth() // app_win.miniscreen,
        #app_win.winfo_screenheight() // app_win.miniscreen,
    #)
    width = height = 400
    app_win.geometry('{0}x{1}+0+0'.format(width, height))
    
    app = Application(app_win).pack(fill='both', expand=True, padx=6, pady=6)
    
    app_win.mainloop()
 
 
if __name__ == '__main__':
    main()
Gruss wuf :wink:

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Sonntag 4. Oktober 2015, 18:33
von Onkel_Phil
Hallo Wuf,

einfach umgeschrieben... OK, das kann ich verstehen.
Was das Zäh angeht, so ist das nur auf dem RasPi der Fall, unter win7 läuft/lief das Prog ganz flüssig.
Könnte also tatsächlich mit dem i2c modul zu tun haben. ggf. muss ich die Ausleserate herunter schrauben.
so schnell ändert sich der Luftdruck nun auch nicht.
Ich werde mir dann deinen Entwurf mal in ruhe anschauen und auf dem Raspi testen, kann aber dauern bis ich hier eine Rückmeldung schreiben.

Aber gleich mal ein Frage: du definierts oben "Display_text", das sind doch dann globale "Variablen", oder nicht? oder gilt das als Konstante und dann nicht so verrucht wie globale Variablen?

Viele Grüße und eine
Schöne Woche

Phil

Re: Klassenbasiertes GUI mit textvariable rekursiver Aufruf

Verfasst: Sonntag 4. Oktober 2015, 19:17
von wuf
Hi Phil

Da sprichst du berechtig etwas an wo man igendwie nicht als vollwertige Konstante bezeichnen kann. Hier müsste sich ein Experte darüber äussern. Aber um den eventuell verruchte Eindruck einer globalen Variable zu entschärfen könntes du diese doppeldeutigen-Konstanten natürlich in die zugehörigen Klassen als kleingeschriebene Attribute transferieren.

Gruss wuf :wink: