Bilder je nach eingelesenem Wert anzeigen

Fragen zu Tkinter.
Antworten
MrVrain
User
Beiträge: 4
Registriert: Donnerstag 18. April 2019, 10:15

Hallo zusammen,

ich bin gerade an einem kleinen pi-Projekt dran in dem ich Werte über einen AD-Wandler auslese.

Nun soll je nach Wert ein anderes Bild gezeigt werden. Da sich die Werte aber regelmäßig ändern muss dann immer ein neues Bild gezeigt werden.

Nun habe ich es bereits hinbekommen mit tkinter Bilder anzuzeigen und diese dann wieder zu schließen damit ein neues Bild angezeigt werden kann.
Das Problem was ich noch habe ist, dass aktuell immer kurz kein Bild angezeigt wird und dann erst das Nächste.

Ziel ist es, dass so lange das eine Bild aktiv bleibt bis sich die Werte ändern und dadurch dann ein neues Bild angezeigt wird.
Gibt es dafür eine geeignete Lösung (es geht um den roten Teil)?

Vielen Dank und falls Fragen oder Anmerkungen sind immer her damit. :)

Code: Alles auswählen

# - *- coding: utf- 8 - *-
#Python Timerklasse importieren
import time
#Python Raspberry Pi GPIO Klasse importieren
import RPi.GPIO as GPIO

import tkinter as tk
from PIL import ImageTk, Image
#ermoeglicht bilder zu importieren

# Festlegung der Nutzung der vorgegebenen Nummerierung der GPIOs
GPIO.setmode(GPIO.BCM)

# Namen von True und False zum besseren Verständnis festlegen (Klarnamen)
HIGH = True  
LOW  = False 

# SCI Funktion
def getAnalogData(adCh, CLKPin, DINPin, DOUTPin, CSPin):
    # Pegel definieren
    GPIO.output(CSPin,   HIGH)    
    GPIO.output(CSPin,   LOW)
    GPIO.output(CLKPin, LOW)
        
    cmd = adCh
    cmd |= 0b00011000 # Kommando zum Abruf der Analogwerte des Datenkanals adCh

    # Bitfolge senden
    for i in range(5):
        if (cmd & 0x10): # 4. Bit prüfen und mit 0 anfangen
            GPIO.output(DINPin, HIGH)
        else:
            GPIO.output(DINPin, LOW)
        # Clocksignal negative Flanke erzeugen   
        GPIO.output(CLKPin, HIGH)
        GPIO.output(CLKPin, LOW)
        cmd <<= 1 # Bitfolge eine Position nach links verschieben
            
    # Datenabruf
    adchvalue = 0 # Wert auf 0 zurücksetzen
    for i in range(11):
        GPIO.output(CLKPin, HIGH)
        GPIO.output(CLKPin, LOW)
        adchvalue <<= 1 # 1 Postition nach links schieben
        if(GPIO.input(DOUTPin)):
            adchvalue |= 0x01
    time.sleep(0.01)
    return adchvalue

# Konfiguration Eingangskanal und GPIOs
CH = 0  # Analog/Digital-Channel
CLK     = 18 # Clock
DIN     = 24 # Digital in
DOUT    = 23  # Digital out
CS      = 25  # Chip-Select

# Pin-Programmierung
GPIO.setup(CLK, GPIO.OUT)
GPIO.setup(DIN, GPIO.OUT)
GPIO.setup(DOUT, GPIO.IN)
GPIO.setup(CS,   GPIO.OUT)

while True:
    if(getAnalogData(CH, CLK, DIN, DOUT, CS)< 500):
        #This creates the main window of an application
        window = tk.Tk()
        window.title("Join")
        window.geometry("1200x1200")
        window.configure(background='grey')
        path = "sonne.jpg"
        #Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
        img = ImageTk.PhotoImage(Image.open(path))
        #The Label widget is a standard Tkinter widget used to display a text or image on the screen.
        panel = tk.Label(window, image = img)
        #The Pack geometry manager packs widgets in rows or columns.
        panel.pack(side = "bottom", fill = "both", expand = "yes")
        window.after(1000, window.destroy)
        window.mainloop()
    else:
        window = tk.Tk()
        window.title("Join")
        window.geometry("1200x1200")
        window.configure(background='grey')
        path = "mond.jpg"
        #Creates a Tkinter-compatible photo image, which can be used everywhere Tkinter expects an image object.
        img = ImageTk.PhotoImage(Image.open(path))
        #The Label widget is a standard Tkinter widget used to display a text or image on the screen.
        panel = tk.Label(window, image = img)
        #The Pack geometry manager packs widgets in rows or columns.
        panel.pack(side = "bottom", fill = "both", expand = "yes")
        window.after(1000, window.destroy)
        window.mainloop()[/color][/color]


    

Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Zum Code:
Kommentare sollten richtig sein. Es gibt keine TimerKlasse und auch keine GPIO-Klasse. Wenn Du HIGH und LOW verwenden willst, dann nimm die aus RPi.GPIO und definier sie nicht selbst. if ist keine Funktion, sollte also nicht wie eine geschrieben werden, die Klammern gehören weg.
Es sollte im ganzen Programm nur ein Tk-Exemplar geben. Immer wieder ein neues zu erzeugen ist umständlich bis fehleranfällig.
Erzeuge ein Fenster und ändere nur das Bild im Label, das angezeigt werden soll: after kennst Du ja schon.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MrVrain: Anmerkungen zum Quelltext:

Der Kodierungskommentar ist bei Python 3 redundant wenn da UTF-8 angegeben wird.

Mit ``import time`` wird keine Timerklasse importiert. Der Kommentar ist also falsch. Auch `GPIO` ist keine Klasse, auch der Kommentar ist falsch. Das ``as`` macht bei dem Import auch keinen Sinn weil das zum Umbenennen da ist, `GPIO` wird aber gar nicht umbenannt.

Kommentare stehen vor dem Code den sie kommentieren, nicht danach. Aber auch der Kommentar der nach dem `PIL`-Import steht ist überflüssig, denn das ist mehr oder weniger offensichtlich was `PIL` ist/macht/ermöglicht. Wenn man das nicht weiss, kann man das in der Dokumentation nachlesen.

Fausregel zu Kommentaren: Die sollten nicht kommentieren *was* gemacht wird, denn das steht da ja bereits als Code, sondern *warum* der Code das so macht. Sofern das nicht offensichtlich ist.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Wenn man Code und solche Definitionen auf Modulebene dann auch noch mischt, wird es sehr unübersichtlich. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

`HIGH` und `LOW` braucht man nicht selbst als Konstanten definieren, die gibt es so bereits im `GPIO`-Modul.

Bei den anderen Konstanten: Wenn man kryptische Abkürzungen verwendet und die jeweils mit einem Kommentar erklärt was die ausgeschrieben bedeuten, dann sollte man die Namen gleich so wählen, dass man keinen Kommentar braucht und der Leser dann an den Stellen wo die Konstanten verwendet werden nicht raten, oder die Erklärung suchen muss.

Man sollte immer dafür sorgen das die GPIOs am Programmende in einem ”sauberen” Zustand sind, in dem man, egal warum das Programm endet, die `cleanup()`-Funktion aufruft.

``if`` ist keine Funktion, also sollte man das nicht so schreiben als wäre es eine. Nach dem Schlüsselwort sollte ein Leerzeichen stehen und um die Bedingung keine unnötigen Klammern.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen: Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Also `get_analog_data()` statt `getAnalogData()`.

`get_analog_data()` ist sehr verwirrend was die Argumente für Eingabe- und Ausgabepins angeht, denn die werden innerhalb der Funktion genau falsch herum verwendet.

Wenn man aus syntaktischen Gründen einen Variablennamen bschreiben muss, den Wert dann aber gar nicht braucht/verwendet, nennt man die Variable(n) per Konvention `_`. Dann weiss der Leser sofort das er die Variable nicht weiter beachten muss und das es kein Fehler ist, das die nirgends verwendet wird.

Das ``if``/``else`` in der ersten Schleife ist überflüssig weil man den Wert für den Pin gleich aus dem Ergebnis der Bedingung ableiten kann.

Auch das jeweilige empfangene Bit lässt sich einfacher in das Ergebnis integrieren. Die letzten drei Zeilen der zweiten Schleife lassen sich in einer Anweisung erledigen.

Das `time.sleep()` am Ende der Funktion macht keinen Sinn. Bei GUIs sollte man von dieser Funktion sowieso die Finger lassen und mit `after()` arbeiten.

Die beiden ``if``/``else``-Zweige im Hauptprogramm enthalten viel Code der bis auf den Dateinamen für das Bild identisch ist. Das ist also falsch strukturiert. Man würde den Code nur einmal schreiben und nur an der Stelle wo der Dateiname festgelegt wird eine Fallunterscheidung machen.

Innerhalb von Argumentlisten werden bei Schlüsselwortargumenten keine Leerzeichen um das Gleichheitszeichen gesetzt.

Das Programm überarbeitet (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk

from PIL import ImageTk, Image
from RPi import GPIO

CHANNEL = 0
CLOCK_PIN = 18
INPUT_PIN = 24
OUTPUT_PIN = 23
CHIP_SELECT_PIN = 25

SUN_MOON_THRESHOLD = 500


def get_analog_data(channel, clock_pin, input_pin, output_pin, chip_select_pin):
    # 
    # FIXME `output_pin` wird für `input()` und `input_pin` für `output()`
    #   verwendet – wo ist der Fehler?
    # 
    GPIO.output(chip_select_pin, GPIO.HIGH)    
    GPIO.output(chip_select_pin, GPIO.LOW)
    GPIO.output(clock_pin, GPIO.LOW)
    # 
    # Kommando zum Abruf der Analogwerte des Datenkanals.
    # 
    command = channel | 0b0001_1000
    # 
    # Bitfolge senden.
    # 
    for _ in range(5):
        GPIO.output(input_pin, bool(command & 0b0001_0000))  # 4. Bit ausgeben.
        # 
        # Clocksignal negative Flanke erzeugen
        # 
        GPIO.output(clock_pin, GPIO.HIGH)
        GPIO.output(clock_pin, GPIO.LOW)
        command <<= 1
    #       
    # Datum abrufen.
    # 
    result = 0
    for _ in range(11):
        # 
        # Clocksignal negative Flanke erzeugen
        # 
        GPIO.output(clock_pin, GPIO.HIGH)
        GPIO.output(clock_pin, GPIO.LOW)
        result = (result << 1) | GPIO.input(output_pin)

    return result


def main():
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(CLOCK_PIN, GPIO.OUT)
        GPIO.setup(INPUT_PIN, GPIO.OUT)
        GPIO.setup(OUTPUT_PIN, GPIO.IN)
        GPIO.setup(CHIP_SELECT_PIN, GPIO.OUT)

        while True:
            window = tk.Tk()
            window.title('Join')
            window.geometry('1200x1200')
            window.configure(background='grey')
            
            if get_analog_data(
                CHANNEL, CLOCK_PIN, INPUT_PIN, OUTPUT_PIN, CHIP_SELECT_PIN
            ) < SUN_MOON_THRESHOLD:
                path = 'sonne.jpg'
            else:
                path = 'mond.jpg'
            image = ImageTk.PhotoImage(Image.open(path))
            tk.Label(window, image=image).pack(fill=tk.BOTH, expand=True)
            window.after(1000, window.destroy)
            window.mainloop()
    finally:
        GPIO.cleanup()


if __name__ == '__main__':
    main()
Dass das Fenster nach einer Sekunde wieder verschwindet hast Du ja selbst so programmiert. Letztlich würde man aber auch nicht immer wieder neue Fenster aufmachen, sondern eines und dort dann regelmässig schauen ob das Bild gewechselt werden muss. Das macht man mit der `after()`-Methode.

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk

from PIL import ImageTk, Image
from RPi import GPIO

CHANNEL = 0
CLOCK_PIN = 18
INPUT_PIN = 24
OUTPUT_PIN = 23
CHIP_SELECT_PIN = 25

SUN_MOON_THRESHOLD = 500


def get_analog_data(channel, clock_pin, input_pin, output_pin, chip_select_pin):
    # 
    # FIXME `output_pin` wird für `input()` und `input_pin` für `output()`
    #   verwendet – wo ist der Fehler?
    # 
    GPIO.output(chip_select_pin, GPIO.HIGH)    
    GPIO.output(chip_select_pin, GPIO.LOW)
    GPIO.output(clock_pin, GPIO.LOW)
    # 
    # Kommando zum Abruf der Analogwerte des Datenkanals.
    # 
    command = channel | 0b0001_1000
    # 
    # Bitfolge senden.
    # 
    for _ in range(5):
        GPIO.output(input_pin, bool(command & 0b0001_0000))  # 4. Bit ausgeben.
        # 
        # Clocksignal negative Flanke erzeugen
        # 
        GPIO.output(clock_pin, GPIO.HIGH)
        GPIO.output(clock_pin, GPIO.LOW)
        command <<= 1
    #       
    # Datum abrufen.
    # 
    result = 0
    for _ in range(11):
        # 
        # Clocksignal negative Flanke erzeugen
        # 
        GPIO.output(clock_pin, GPIO.HIGH)
        GPIO.output(clock_pin, GPIO.LOW)
        result = (result << 1) | GPIO.input(output_pin)

    return result


def update_image(label, sun_image, moon_image):
    if get_analog_data(
        CHANNEL, CLOCK_PIN, INPUT_PIN, OUTPUT_PIN, CHIP_SELECT_PIN
    ) < SUN_MOON_THRESHOLD:
        label['image'] = sun_image
    else:
        label['image'] = moon_image

    label.after(1000, update_image, label, sun_image, moon_image)


def main():
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(CLOCK_PIN, GPIO.OUT)
        GPIO.setup(INPUT_PIN, GPIO.OUT)
        GPIO.setup(OUTPUT_PIN, GPIO.IN)
        GPIO.setup(CHIP_SELECT_PIN, GPIO.OUT)

        window = tk.Tk()
        window.title('Join')
        window.geometry('1200x1200')
        window.configure(background='grey')
        
        label = tk.Label(window)
        label.pack(fill=tk.BOTH, expand=True)
        
        sun_image, moon_image = (
            ImageTk.PhotoImage(Image.open(path))
            for path in ['sonne.jpg', 'mond.jpg']
        )
        update_image(label, sun_image, moon_image)
        window.mainloop()
    finally:
        GPIO.cleanup()


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
MrVrain
User
Beiträge: 4
Registriert: Donnerstag 18. April 2019, 10:15

Vielen Dank für die schnelle Antwort.
Genau so einen Weg, wie mit der Aftermethode und dann Bild wechseln habe ich gesucht. Theoretisch sollte nämlich dann ja kein Bild gewechselt werden wenn der Wert noch dem Alten entspricht.

Der Code läuft zwar aktuell noch nicht und gibt folgenden Error aus.
command = channel | 0b0001_1000
^
SyntaxError: invalid syntax
aber da muss ich dann nur noch mal ran und schauen wo dran es liegt.
Zuletzt geändert von MrVrain am Donnerstag 18. April 2019, 14:58, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

SyntaxError hat nichts mit fehlenden Paketen zu tun. Sondern in dem Fall wohl etwas mit einer Zeile davor, ich tippe mal auf falsch gesetzte/fehlende Klammer. Bei sowas bitte immer den GESAMTEN Stacktrace UND den Code posten. Denn nur so kann man sowas wirklich abschaetzen.
MrVrain
User
Beiträge: 4
Registriert: Donnerstag 18. April 2019, 10:15

Wunderbar habe es schon gelöst bekommen in dem ich das ganze zwei geteilt habe.

Code: Alles auswählen

    command = channel
    command |= 0b00011000
Danke euch :)
MrVrain
User
Beiträge: 4
Registriert: Donnerstag 18. April 2019, 10:15

__deets__ hat geschrieben: Donnerstag 18. April 2019, 14:49 SyntaxError hat nichts mit fehlenden Paketen zu tun. Sondern in dem Fall wohl etwas mit einer Zeile davor, ich tippe mal auf falsch gesetzte/fehlende Klammer. Bei sowas bitte immer den GESAMTEN Stacktrace UND den Code posten. Denn nur so kann man sowas wirklich abschaetzen.
der Fehler bezieht sich auf den Code von "__blackjack__" :)
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MrVrain: Das aufteilen auf zwei Anweisungen ist nicht die Lösung für den Syntaxfehler. Du hast da ja noch etwas anderes geändert. Und hier hat der ^ auch tatsächlich *genau* auf das Problem gezeigt: Du verwendest eine Python-Version wo man noch keinen Unterstrich in Zahlenliteralen schreiben kann/darf.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten