Anzeige von sich ändernden Variablen

Fragen zu Tkinter.
Antworten
mumpey
User
Beiträge: 4
Registriert: Montag 16. März 2015, 09:47

Hey ho.
Ich habe lange im forum gesucht und nicht wirklich eine Antwort in den bestehenden Threads gefunden.

Ich habe ein funktionierendes Programm, welches Temperaturen aus angeschlossenen Sensoren ausliest, verrechnet und als PWM-Signal wieder ausgibt.
Diese While-Schleife soll nach einer bestimmten zeit wieder die Temperaturen auslesen usw...
Das funktioniert auch soweit.

Nun komme ich aber den Punkt wo ich mir die Variablen (temperaturen, PWM-Signal) mit Tkinter anzeigen lassen möchte.
Beim ersten Durchlauf funktioniert es auch, aber durch den "mainloop()" Befehl startet die while schleife nicht wieder von vorn, sondern bleibt einfach stehen.
kann mir da jemand zufällig mal einen tipp geben?
BlackJack

@mumpey: Die Schleife durch eine Funktion ersetzen die *einen* Schleifendurchlauf abbildet und diese dann mit Hilfe der `after()`-Methode auf Widgets regelmässig aufrufen lassen, oder das `threading`-Modul verwenden um die Schleife nebenläufig auszuführen. Von dem Thread aus darf man allerdings nichts an der GUI verändern, also müsste man die Daten beispielsweise über eine `Queue.Queue` an den GUI-Thread übermitteln, in dem dann wieder mit der `after()`-Methode regelmässig geprüft wird ob in der Queue etwas zum Anzeigen steckt.
mumpey
User
Beiträge: 4
Registriert: Montag 16. März 2015, 09:47

Schönen guten morgen ;)

Gibt es irgenwo einen link, welcher die after()-Methode näher beschreibt. Bin im Netz nicht wirklich schlau geworden, oder stell mich einfach nur zu doof an.
Wer Rechtschreibfehler findet, darf sie behalten.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@mumpey: wie wärs mit diesem Link? Erster Verweis ist die Dokumentation und dann kommen noch gut 400000 Links auf Anwendungsbeispiele.
mumpey
User
Beiträge: 4
Registriert: Montag 16. März 2015, 09:47

Code: Alles auswählen

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


# Die Sensoren müssen mit "modprobe w1-gpio" und "modprobe w1-therm" aktiviert werden!


# Import der Module

import sys
import os
import time
import math
math.pi
import RPi.GPIO as GPIO
from Tkinter import *
import Tkinter as tk

# PW definieren zum ansteuer der Transistoren für Lüfter

GPIO.setmode(GPIO.BCM)
Luefter1 = 18
ein = 23
GPIO.setup(Luefter1, GPIO.OUT)
GPIO.setup(ein, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)


# diff wird später zum bestimmen für w bentötigt
# definieren der variablen
# timetorepeat als variablen in sekunden

diff = 0
w = 0 # soll Wert
y = 0 # Stellgröße (PWM)
x = 0 # Regelgröße
r = 0 # Rückführgröße
e = 0 # Regeldifferenz
timetorepeat = 5000
tempa = 0.0
tempb = 0.0




def Schleifendurchlauf():
        global tempa
        global tempb
        global x
        global y
        global w
        global e
        global r
        




        #tempfühler 1

        tempfile = open("/sys/bus/w1/devices/10-000802de25c6/w1_slave")
        thetext = tempfile.read()
        tempfile.close()
        tempdata = thetext.split("\n")[1].split(" ")[9]
        temp = float(tempdata[2:])
        tempa = temp / 1000

        #tempfühler 2

        tempfile = open("/sys/bus/w1/devices/10-000802de8236/w1_slave")
        thetext = tempfile.read()
        tempfile.close()
        tempdata = thetext.split("\n")[1].split(" ")[9]
        temp = float(tempdata[2:])
        tempb = temp / 1000

        # differenzbildung der Temp.-Sensoren

        diff = tempb - tempa


        # diff Wert nehmen und daraus die PWM zu errechnen für Luefter1 (Master)

        w = 13.33 * diff - 6.66

        # e aus differenz von r und w

        e = w - r

        # muss vor if-schleife definiert sein, da sonst y nicht richtig verarbeitet wird

        y = e
        

        # if schleife wenn y größer als 100% dann 100% PWM da sonst fehler (Regelglied)
        
        
        if y > 20:
                y = y

        if y > 100:
                y = 100

        if y < 20:
                y = 20
                
            
        
        
        p = GPIO.PWM(Luefter1, y)
        p.start (y)

        #damit sich die Schleife nach einer gewissen Zeit wieder selbst aktiviert
        root.after(timetorepeat, Schleifendurchlauf)







root = Tk()
root.geometry('400x300')
root.title("Auswertung Lüftersteuerung")

Label(root, text='temp_a= ' + str(tempa) + '°C').pack()
Label(root, text='temp_b= ' + str(tempb) + '°C').pack()
Label(root, text='y= ' + str(y) + '% der maximalen Drehzahl').pack()


root.after(timetorepeat,Schleifendurchlauf)

root.mainloop()
        
        
        
        

GPIO.cleanup()
sys.exit(0)
meine Tkinter-Oberfläche wird ja auch geöffnet aber die schleife wird nicht gestartet.
Durch die Links bin ich (ich suche seid gester schon danach) noch zu keinem sinnvollen Lösungsansatz gekommen.
Zuletzt geändert von Anonymous am Dienstag 17. März 2015, 09:31, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt
Wer Rechtschreibfehler findet, darf sie behalten.
BlackJack

@mumpey: Ich sehe in `Schleifendurchlauf()` nichts was die Anzeige in der GUI aktualisiert‽

``global`` solltest Du ganz schnell wieder vergessen. Werte betreten Funktionen und Methoden als Argumente und Ergebnisse verlassen sie als Rückgabewerte. Sonst wird es sehr schnell sehr unübersichtlich und einzelne Funktionen lassen sich nicht mehr sinnvoll wiederverwenden oder auch nur isoliert testen.

Das Hauptprogramm gehört nicht auf Modulebene sondern auch in eine Funktion. Auf Modulebene sollten nur Konstanten, Funktionen, und Klassen definiert werden. Dann passiert es auch nicht einmal mehr ausversehen das Funktionen auf etwas zurückgreifen was nicht als Argument übergeben wurde. Die Funktion für das Hauptprogramm heisst üblicherweise `main()`.

Namen sollen dem Leser vermitteln wofür der Wert dahinter im Programmkontext steht. Das tun einbuchstabige Namen nur äusserst selten. Statt einen Kommentar zu schreiben wofür ein Name steht, wäre es besser den Namen gleich so zu wählen das man den Kommentar nicht braucht. Dann kann man den Namen auch überall im Programm direkt verstehen und muss nicht erst irgendwo den erklärenden Kommentar suchen.

Namen „en block” definieren die irgendwann sehr viel später im Programm benutzt werden ist auch unschön. So vergisst man nach Änderungen leichter Namen zu entfernen die nicht mehr gebraucht werden, und das herauslösen von einzelnen Schritten in eigene Funktionen wird schwieriger wenn der Code dazu nicht nahe zusammen steht sondern verteilt über eine Funktion oder Methode, oder gar über ein ganzes Modul.

IMHO kommt man hier nicht um objektorientierte Programmierung herum wenn man das sauber lösen möchte. Das ist Grundlage bei GUI-Programmierung. Ohne wird es entweder ein Hack oder sehr schnell ”unpythonisch” weil OOP der Weg ist den Python hier vorsieht.
BlackJack

Nachtrag: Einige Sachen werden importiert und dann nicht benutzt. Und insbesondere beim Sternchenimport von *allem* was im `Tkinter`-Modul definiert ist, das sind um die 190 Namen, wird nur ein ganz winziger Bruchteil verwendet. Dabei steht die sinnvollere Alternative das Modul unter dem Namen `tk` zu importieren zwar im Quelltext, wird dann aber im weiteren gar nicht verwendet.

``math.pi`` einfach so als Anweisung in den Quelltext zu schreiben ist sinnlos.

Kommentare sollten einen Mehrwert zum Code bieten. Ein Kommentar „Import der Module” vor dem Code mit den ``import``-Anweisungen bringt dem Leser nichts. Als Faustregel sollten Kommentare nicht sagen was der Code tut, denn das sollte man am Code selber ablesen können, sondern *warum* der Code das tut was er tut. Sofern das nicht auch aus dem Code selber schon klar wird.

Das in meinem letzten Beitrag erwähnte Problem wenn man Variablen alle am Anfang auf einem Haufen definiert und dabei dann den Überblick verlieren kann was davon überhaupt gebraucht wird, bestätigt das `x`, das definiert, aber nirgends verwendet wird. Um das festzustellen muss man den ganzen Quelltext absuchen. `diff` wird auch nicht verwendet. Was zwar dokumentiert ist, aber warum etwas definieren von dem man weiss das man es gar nicht verwendet?

Um sicherzustellen das `GPIO.cleanup()` auf jeden Fall aufgerufen wird, auch wenn beispielsweise zwischendurch eine Ausnahme auftritt die das Programm beendet, sollte das in einem ``finally``-Zweig von einem ``try``-Block passieren.

Das auslesen eines Temperaturfühlers wird in dem Quelltext zweimal mit fast identischem Code gemacht. Code- oder Datenwiederholungen sind schlecht. Für Codewiederholungen gibt es Schleifen und/oder Funktionen.

Wenn man Dateien zusammen mit der ``with``-Anweisung öffnet werden sie beim verlassen des ``with``-Blocks, egal aus welchen Gründen, sicher geschlossen.

`e` ist irgendwie überflüssig weil nur ein anderer Name für `y`‽ Die Rückführgrösse ist immer 0 hat also keinen Effekt bei den Berechnungen die damit angestellt werden.

*Ganz* wichtig: http://if-schleife.de/

Werte und Zeichenketten mittels ``+`` und `str()` zusammenzusetzen ist eher BASIC als Python. In Python verwendet man dafür Zeichenkettenformierung mittels der `format()`-Methode auf Zeichenketten.

Ein ``sys.exit(0)`` am Programmende ist überflüssig, das passiert auch ohne den Aufruf.

Ich komme da ungefähr bei so etwas heraus (ungetestet):

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
# 
# Die Sensoren müssen mit "modprobe w1-gpio" und "modprobe w1-therm" aktiviert
# werden!
# 
import Tkinter as tk

from RPi import GPIO

FAN_PIN = 18
INPUT_PIN = 23
TEMPERATURE_SENSOR_PATH_A = '/sys/bus/w1/devices/10-000802de25c6/w1_slave'
TEMPERATURE_SENSOR_PATH_B = '/sys/bus/w1/devices/10-000802de8236/w1_slave'


def read_temperature(sensor_path):
    with open(sensor_path) as lines:
        next(lines)  # Skip first line.
        temperature_line = next(lines)
    return float(temperature_line.split(' ')[9][2:]) / 1000


class FanUI(tk.Frame):
    FAN_MIN_PWM_VALUE = 20

    def __init__(self, master, fan_pin, interval):
        tk.Frame.__init__(self, master)
        self.fan_pin = fan_pin
        self.interval = interval
        self.temperature_a = None
        self.temperature_b = None
        self.pwm_value = None

        self.temperature_a_label = tk.Label(self)
        self.temperature_a_label.pack()
        self.temperature_b_label = tk.Label(self)
        self.temperature_b_label.pack()
        self.fan_speed_label = tk.Label(self)
        self.fan_speed_label.pack()

        self.pwm = GPIO.PWM(self.fan_pin, self.FAN_MIN_PWM_VALUE)
        self.pwm.start(self.FAN_MIN_PWM_VALUE)
        self.regulate_fan()

    def update_values(self):
        for temperature, label in [
            (self.temperature_a, self.temperature_a_label),
            (self.temperature_b, self.temperature_b_label),
        ]:
            label['text'] = 'temp_a= {0}°C'.format(temperature)
        
        self.fan_speed_label['text'] = 'y = {0}% der maximalen Drehzahl'.format(
            self.pwm_value
        )

    def regulate_fan(self):
        self.temperature_a = read_temperature(TEMPERATURE_SENSOR_PATH_A)
        self.temperature_b = read_temperature(TEMPERATURE_SENSOR_PATH_B)
        
        pwm_value = 13.33 * abs(self.temperature_b - self.temperature_a) - 6.66
        #
        # Auf Wertebereich für PWM begrenzen da sonst Fehler auftreten
        # (Regelglied).
        # 
        self.pwm_value = max(min(pwm_value, self.FAN_MIN_PWM_VALUE), 100)

        self.pwm.ChangeFrequency(pwm_value)
        self.pwm.ChangeDutyCycle(pwm_value)

        self.update_values()
        self.after(self.interval, self.regulate_fan)


def main():
    GPIO.setmode(GPIO.BCM)
    try:
        GPIO.setup(FAN_PIN, GPIO.OUT)
        GPIO.setup(INPUT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

        root = tk.Tk()
        root.title('Auswertung Lüftersteuerung')
        fan_ui = FanUI(root, FAN_PIN, 5000)
        fan_ui.pack()
        root.mainloop()
    finally:
        GPIO.cleanup()
Die `read_temperature()`-Funktion orientiert sich an Deinem Quelltext und ist ziemlich sicher nicht wirklich robust.

Und GUI und Programmlogik sind hier noch vermischt, was nicht sein sollte.
mumpey
User
Beiträge: 4
Registriert: Montag 16. März 2015, 09:47

Vielen dank erstmal.
Ich arbeite noch an dem Projekt und wollte die Drehzahl des Lüfters auswerten und damit den Lüfter im Regelkreis laufen lassen. Da ich aber momentan technische Hörden bei der Signalgewinnung des Tachos habe und die Zeit knapp wurde (bin AZUBI und hab immer mal ein zwei Tage Zeit mich mit solchen Projekten auf Arbeit auseinander zu setzen. :)
Mein Programmierkenntnisse in Python sind sehr begrenzt, wie man bereits gesehen hat.

Vielen dank erstmal für deine schnelle Hilfe und die Auflistung, worauf ich mich mehr Konzentrieren muss.

Ich habe dein Script versucht in der Kürze der Zeit zu verstehen, muss mich aber noch etwas mehr damit auseinander setzen um alles zu verstehen.
Habe es auch einmal versucht zu starten aber es ist nix passiert.
Denke zu beginn muss noch der befehl ,,import RPi.GPIO as GPIO" rein, Aber das schaff ich heute nicht mehr.

Vielen dank im vorraus schonmal ;)
Wer Rechtschreibfehler findet, darf sie behalten.
BlackJack

@mumpey: `RPi.GPIO` wird doch importiert. Es fehlt das aufrufen der `main()`-Funktion am Ende des Quelltextes:

Code: Alles auswählen

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