Schalter zum hin und Herschalten

Fragen zu Tkinter.
Antworten
Failix
User
Beiträge: 7
Registriert: Donnerstag 25. Februar 2021, 19:52

Hallo zusammen,

ich habe folgendes Problem.

Ich möchte einen Button machen, der bei jeden klick abwechseln ein Dreieck rot und grün verfärbt.

Mein Ansatz der nicht funktioniert:

Code: Alles auswählen

ventil1_status = 0
farbe="red"
...

Code: Alles auswählen

def schaltung_ventil1():
    global ventil1_status
    global farbe
    if ventil1_status == 1:
        ventil1_status = 0
        farbe="green"
    else:
        ventil1_status = 1
        farbe="red"
....

Code: Alles auswählen

 zeichner.create_polygon(ventil1[0], ventil1[1], ventil1[0]-10, ventil1[1]-20, ventil1[0]+10, ventil1[0]-20, fill="green")
 button_ventil1 = Button(schaltplan, text="Ventil1", command=schaltung_ventil1)
 button_ventil1.place(x=ventil1[0]-10, y=ventil1[0]+10)
 
...

Ich habe schon einiges herum versucht, wenn jemand den Fehler findet oder einen bessern weg weiß wäre das super nett. (Am Ende soll irgendwann noch mal ein raspi mit schalten.)
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Aus den Code-Fragmenten kann man ja nichts rauslesen.
Die Fehlerbeschreibung "funktioniert nicht" ist auch recht unspezifisch.

Du mußt Dir die ID des Polygons merken, damit Du dann die Farbe auch nachträglich setzen kannst.
Failix
User
Beiträge: 7
Registriert: Donnerstag 25. Februar 2021, 19:52

Sirius3 hat geschrieben: Donnerstag 25. Februar 2021, 20:24 Aus den Code-Fragmenten kann man ja nichts rauslesen.
Die Fehlerbeschreibung "funktioniert nicht" ist auch recht unspezifisch.

Du mußt Dir die ID des Polygons merken, damit Du dann die Farbe auch nachträglich setzen kannst.
Hier ist der volle Kot.

Ich dachte nur das den entsprechende Teile übersichtlicher sind:

Code: Alles auswählen

from tkinter import *
import random
#Position Ventile
ventil1 = [50, 50]
ventil1_status = 0
farbe="blue"
#Status Ventile

def schaltung_ventil1():
    global ventil1_status
    global farbe
    if ventil1_status == 1:
        ventil1_status = 0
        farbe="green"
    else:
        ventil1_status = 1
        farbe="red"



def schalt():

    schaltplan = Tk()
    schaltplan.title('Nukular Schaltplan')
    schaltplan.geometry('420x230')
    zeichner = Canvas(schaltplan)
    zeichner.pack()

    # Positionnierung Ventile


    zeichner.create_polygon(ventil1[0]-20, ventil1[1]+10, ventil1[0], ventil1[1], ventil1[0]-20, ventil1[0]-10, fill=farbe)
    zeichner.create_polygon(ventil1[0] + 20, ventil1[1] - 10, ventil1[0], ventil1[1], ventil1[0] + 20, ventil1[0] + 10)
    zeichner.create_polygon(ventil1[0], ventil1[1], ventil1[0]-10, ventil1[1]-20, ventil1[0]+10, ventil1[0]-20, fill="green")

    # Schaltung
    button_ventil1 = Button(schaltplan, text="Ventil1", command=schaltung_ventil1)

    button_ventil1.place(x=ventil1[0]-10, y=ventil1[0]+10)






    mainloop()
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Kot ist Scheisse. Code ist Programmcode. Der *kann* scheisse sein. Muss aber nicht.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

*-Importe sind schlecht, weil sie unkontrolliert Namen in den eigenen Namensraum schaufeln.
random wird importiert aber gar nicht benutzt.
Vergiss gleich wieder, dass es `global` gibt, das löst keine Probleme.

Sobald man eine GUI mit Zustand hat, braucht man Klassendefinitionen, vor allem, wenn es scheinbar irgendwann mehrere Ventile geben soll.
Wie schon geschrieben, mußt Du Dir die ID merken, um später die Farbe setzen zu können.

mainloop sollte man auf der Tk-Instanz aufgerufen werden. schalte wird gar nicht aufgerufen.

Code: Alles auswählen

import tkinter as tk

class Ventil:
    def __init__(self, canvas, position):
        self.canvas = canvas
        self.status = 0
        x, y = position
        self.id = self.canvas.create_polygon(x - 20, y + 10, x, y, x - 20, y - 10, fill="blue")
        self.canvas.create_polygon(x + 20, y - 10, x, y, x + 20, y + 10)
        self.canvas.create_polygon(x, y, x - 10, y - 20, x + 10, y - 20, fill="green")

    def schalten(self):
        self.status = 1 - self.status
        self.canvas.itemconfig(self.id, fill="green" if self.status == 0 else "red")

def main():
    schaltplan = tk.Tk()
    schaltplan.title('Nukular Schaltplan')
    schaltplan.geometry('420x230')
    zeichner = tk.Canvas(schaltplan)
    zeichner.pack()
    ventil = Ventil(zeichner, [50, 50])
    tk.Button(schaltplan, text="Ventil1",
        command=ventil.schalten
    ).pack()
    schaltplan.mainloop()

if __name__ == "__main__":
    main()
Failix
User
Beiträge: 7
Registriert: Donnerstag 25. Februar 2021, 19:52

Ok danke das hilft mir weiter.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei ich den `status` hier etwas sehr verschleiert finde. Das kann man machen wenn man keine Wahrheitswerte hätte, aber beim Initialisieren ``self.status = False`` und beim Schalten ``self.status = not self.status`` wäre IMHO deutlich weniger kryptisch.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Failix
User
Beiträge: 7
Registriert: Donnerstag 25. Februar 2021, 19:52

Danke noch mal ich kann jetzt auch damit den GPIO von Pi schalten.

Ich würde nur gerne diese Funktion alternativ zum Button noch über die Zeit auslösen können. (Am Ende soll es einen Programmablauf geben, bei dem ich noch eingreifen kann)

Wenn ich das versuche

Code: Alles auswählen

def Pro1():
    Ventil().schalten()
    time.sleep(3)
   
Bekommen ich folgenden Fehlermeldung.
thinker/___init___ return self.func(*args)
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist keine Fehlermeldung, jedenfalls nur ein Teil davon, mit dem man nichts anfangen kann.
Zeige den kompletten Code und die komplette Fehlermeldung.
Failix
User
Beiträge: 7
Registriert: Donnerstag 25. Februar 2021, 19:52

@Sirius3

Das ist im der Code von dir den ich mit import hole (ich habe auf die Namen geachtet):

Code: Alles auswählen

import random
import fake_GPIO as GPIO
#import RPi.GPIO as GPIO
import tkinter as tk
import time
from Schaltplan import *

def Pro1():
    Ventil().schalten()
    time.sleep(3)

Exception in Tkinter callback
Traceback (most recent call last):
File "/home/felix/miniconda3/envs/pythonProject2/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "/home/felix/PycharmProjects/Nukular/Programm1.py", line 9, in Pro1
Ventil2().schalten2()
TypeError: __init__() missing 2 required positional arguments: 'canvas' and 'position'


Alternativ habe ich versucht

Code: Alles auswählen

import random
import fake_GPIO as GPIO
#import RPi.GPIO as GPIO
import tkinter as tk
import time
from Schaltplan import *

def Pro1():
    Ventil2().__init__().schalten2()
    time.sleep(3)

Code: Alles auswählen

def Pro1():
    schalten2()
    time.sleep(3)

Code: Alles auswählen

def Pro1():
    Ventil2().__init__(position=1).schalten2()
    time.sleep(3)
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Programmieren durch Raten funktioniert einfach nicht.
Der Code macht so gar keinen Sinn, weil Du jetzt gar keine GUI mehr hast.
Und ein Ventil braucht eine GUI.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Und was heisst „auf die Namen geachtet“? Im Beispiel Sirius3 war keiner der Namen nummeriert, sollten sie auch nicht, und sie hielten sich an die Konventionen. Und Sternchen-Importe sind auch aus eigenen Modulen keine gute Idee.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Failix
User
Beiträge: 7
Registriert: Donnerstag 25. Februar 2021, 19:52

Ich bekomme es nicht hin die Funktion def schalten1() ohne Buttom auszulösen.

Darum habe ich es jetzt auf GPIO umgestellt. Sieht so aus:

Code: Alles auswählen

class Ventil1:

    def __init__(self, canvas, position):
        self.canvas = canvas
        self.status = 0
        self.id1_ventil1 = self.canvas.create_polygon(ventil1[0]-20, ventil1[1]+10, ventil1[0], ventil1[1], ventil1[0]-20, ventil1[1]-10, fill="green")
        self.id2_ventil1 = self.canvas.create_polygon(ventil1[0]+20, ventil1[1]-10, ventil1[0], ventil1[1], ventil1[0] + 20, ventil1[1] + 10, fill="red")
        self.canvas.create_polygon(ventil1[0], ventil1[1], ventil1[0]-10, ventil1[1]-20, ventil1[0]+10, ventil1[1]-20, fill="green")

    def schalten1(self):
        self.status = 1 - self.status
        self.canvas.itemconfig(self.id1_ventil1, fill="green" if GPIO.input(2) == GPIO.HIGH else "red")
        self.canvas.itemconfig(self.id2_ventil1, fill="red" if GPIO.input(2,) == GPIO.HIGH else "green")
        GPIO.output(2, GPIO.HIGH) if self.status == 1 else GPIO.output(2, GPIO.LOW)
Und funktioniert, aber nur solange ich den GPIO über diese Funktion schalte. Wenn ich ihn mit einem Befehl außerhalb diese Funktion anspreche ändert sich die Visualisierung nicht. Die Funktion mainloop sollte doch def schalt() bzw. das Fenster darin aktualisieren oder habe ich was falsch verstanden?

Hier ist der komplette CODE.

Code: Alles auswählen

import tkinter as tk
import random
import time
#import fake_GPIO as GPIO
import RPi.GPIO as GPIO
#Position Ventile
ventil1 = [50, 50]
ventil2 = [50, 200]
ventil3 = [200, 50]

Status2=0

class Ventil1:

    def __init__(self, canvas, position):
        self.canvas = canvas
        self.status = 0
        self.id1_ventil1 = self.canvas.create_polygon(ventil1[0]-20, ventil1[1]+10, ventil1[0], ventil1[1], ventil1[0]-20, ventil1[1]-10, fill="green")
        self.id2_ventil1 = self.canvas.create_polygon(ventil1[0]+20, ventil1[1]-10, ventil1[0], ventil1[1], ventil1[0] + 20, ventil1[1] + 10, fill="red")
        self.canvas.create_polygon(ventil1[0], ventil1[1], ventil1[0]-10, ventil1[1]-20, ventil1[0]+10, ventil1[1]-20, fill="green")

    def schalten1(self):
        self.status = 1 - self.status
        self.canvas.itemconfig(self.id1_ventil1, fill="green" if GPIO.input(2) == GPIO.HIGH else "red")
        self.canvas.itemconfig(self.id2_ventil1, fill="red" if GPIO.input(2,) == GPIO.HIGH else "green")
        GPIO.output(2, GPIO.HIGH) if self.status == 1 else GPIO.output(2, GPIO.LOW)


class Ventil2:

    def __init__(self, canvas, position):
        global Status2
        self.canvas = canvas
        self.status = Status2
        self.id1_ventil2 = self.canvas.create_polygon(ventil2[0]-20, ventil2[1]+10, ventil2[0], ventil2[1], ventil2[0]-20, ventil2[1]-10, fill="green" if self.status == 0 else "red")
        self.id2_ventil2 = self.canvas.create_polygon(ventil2[0]+20, ventil2[1]-10, ventil2[0], ventil2[1], ventil2[0] + 20, ventil2[1] + 10, fill="red" if self.status == 0 else "green")
        self.canvas.create_polygon(ventil2[0], ventil2[1], ventil2[0]-10, ventil2[1]-20, ventil2[0]+10, ventil2[1]-20, fill="green")

    def schalten2(self):
        self.status = 1 - self.status
        self.canvas.itemconfig(self.id1_ventil2, fill="green" if self.status == 0 else "red")
        self.canvas.itemconfig(self.id2_ventil2, fill="red" if self.status == 0 else "green")


class Ventil3:

    def __init__(self, canvas, position):
        self.canvas = canvas
        self.status = 0
        x, y = position
        self.id1_ventil3 = self.canvas.create_polygon(x-20, y+10, x, y, x-20, y-10, fill="green")
        self.id2_ventil3 = self.canvas.create_polygon(x+20, y-10, x, y, x + 20, y + 10, fill="red")
        self.canvas.create_polygon(x, y, x-10, y-20, x+10, y-20, fill="green")

    def schalten3(self):
        self.status = 1 - self.status
        self.canvas.itemconfig(self.id1_ventil3, fill="green" if self.status == 0 else "red")
        self.canvas.itemconfig(self.id2_ventil3, fill="red" if self.status == 0 else "green")


def Programm():
   GPIO.output(2, GPIO.HIGH)
   time.sleep(3)
   GPIO.output(2, GPIO.LOW)




def schalt():
    schaltplan = tk.Tk()
    schaltplan.title('Nukular Schaltplan')
    schaltplan.geometry('600x600')
    zeichner = tk.Canvas(schaltplan)
    zeichner.pack(fill='both')
    ventil_1 = Ventil1(zeichner, ventil1)
    tk.Button(schaltplan, text="Ventil1", command=ventil_1.schalten1).place(x=ventil1[0]-30, y=ventil1[1]+10)
    ventil_2 = Ventil2(zeichner, ventil2)
    tk.Button(schaltplan, text="Ventil2", command=ventil_2.schalten2).place(x=ventil2[0]-30, y=ventil2[1] + 10)
    ventil_3 = Ventil3(zeichner, ventil3)
    tk.Button(schaltplan, text="Ventil3", command=ventil_3.schalten3).place(x=ventil3[0]-30, y=ventil3[1] + 10)

    tk.Button(schaltplan, text="Programm", command=Programm).place(x=500, y=500)

    schaltplan.mainloop()

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

Du hast Klassen noch nicht ganz verstanden. Du brauchst nicht für jedes Ventil eine eigene Klasse, weil die ja alle exakt identisch sind.

Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht mal 3 und mal 4.
Bei GUI-Proramm darf es kein sleep geben, das muß man mit after lösen.
Warum sollte sich ein Ventil ändern, nur weil Du irgendwo einen GP-Ausgang setzt?
Wenn Du so etwas möchtest, dann mußt Du das schon explizit programmieren.
Was ist jetzt PIN 2? Ein Eingang oder ein Ausgang?

Vergiss gleich wieder, dass es global gibt. Hat hier auch gar keinen Nutzen.
Konstanten werden nach Konvention komplett GROSS geschrieben.
Ein if-else-Ausdruck ist kein Ersatz für ein richtiges if, wenn Du mit dem Ergebnis des Ausdrucks gar nichts machst.

Wenn zu jedem Ventil ein Knopf gehört, kann man den auch in die Klasse packen.

Code: Alles auswählen

import tkinter as tk
from functools import partial
#import fake_GPIO as GPIO
from RPi import GPIO

#Position Ventile
VENTIL_POSITIONS = [
    [50, 50],
    [50, 200],
    [200, 50],
]

class Ventil:
    def __init__(self, canvas, position, text, pin=None):
        self.canvas = canvas
        self.status = False
        self.pin = pin
        x, y = position
        self.id1 = canvas.create_polygon(x - 20, y + 10, x, y, x - 20, y - 10, fill="green")
        self.id2 = canvas.create_polygon(x + 20, y - 10, x, y, x + 20, y + 10, fill="red")
        canvas.create_polygon(x, y, x - 10, y - 20, x + 10, y - 20, fill="green")
        tk.Button(canvas, text=text, command=self.schalten).place(x=x - 30, y=y + 10)

    def schalten(self):
        self.status = not self.status
        self.canvas.itemconfig(self.id1, fill="green" if self.status else "red")
        self.canvas.itemconfig(self.id2, fill="red" if self.status else "green")
        if self.pin:
            GPIO.output(self.pin, GPIO.HIGH if self.status else GPIO.LOW)


def blink(widget):
   GPIO.output(2, GPIO.HIGH)
   widget.after(3000, GPIO.output, 2, GPIO.LOW)


def main():
    schaltplan = tk.Tk()
    schaltplan.title('Nukular Schaltplan')
    zeichner = tk.Canvas(schaltplan, width=250, height=250)
    zeichner.pack(fill='both')
    for i, position in enumerate(VENTIL_POSITIONS, 1):
        ventil = Ventil(zeichner, position, f"Ventil{i}", 2 if i == 1 else None)
    tk.Button(schaltplan, text="Programm", command=partial(blink, schaltplan)).pack()
    schaltplan.mainloop()

if __name__ == "__main__":
    main()
Failix
User
Beiträge: 7
Registriert: Donnerstag 25. Februar 2021, 19:52

Erstmal vielen Dank für die Antwort. Ich habe absolut keine Ahnung vom programmieren und noch weniger von Python. Ich werde mir aber noch das mit den Klasse noch ganz genau anschauen.

Vorab mal 2 Frage an deinen Code.

Bei mein PC wird er Fehlerfrei ausgeführt bei dem PI bekomme ich die Meldung das bei Line 50 ein colon fehlt. Das ist bei mir diese Line:

Line 50 bei mir ist das

Code: Alles auswählen

ventil = Ventil(zeichner, position, f"Ventil{i}", 2 if i == 1 else None)
Das nächste was ich überhaupt nicht verstanden habe, wie sage ich ihm welches Ventil er schalten soll?

Kann ich das so machen?

Code: Alles auswählen

import tkinter as tk
from functools import partial
#import fake_GPIO as GPIO
from RPi import GPIO

#Position Ventile
VENTIL_PIN = [
    [2],
    [3],
    [4],
]

VENTIL_POSITIONS = [
    [50, 50],
    [50, 200],
    [200, 50],
]


class Ventil:
    def __init__(self, canvas, position, text, pin=None):
        self.canvas = canvas
        self.status = False
        self.pin = VENTIL_PIN
        x, y = position
        self.id1 = canvas.create_polygon(x - 20, y + 10, x, y, x - 20, y - 10, fill="green")
        self.id2 = canvas.create_polygon(x + 20, y - 10, x, y, x + 20, y + 10, fill="red")
        canvas.create_polygon(x, y, x - 10, y - 20, x + 10, y - 20, fill="green")
        tk.Button(canvas, text=text, command=self.schalten).place(x=x - 30, y=y + 10)

    def schalten(self):
        self.status = not self.status
        self.canvas.itemconfig(self.id1, fill="green" if self.status else "red")
        self.canvas.itemconfig(self.id2, fill="red" if self.status else "green")
        if self.pin:
            GPIO.output(self.pin, GPIO.HIGH if self.status else GPIO.LOW)
            
            .....
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Dann ist auf dem Pi deine Python-Version zu alt und kennt noch keine Formatstrings.
Und pin ist ein Parameter von __init__, geht also so ähnlich wie mit der Position.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Failix: In der gezeigten Zeile ist kein Syntaxfehler. Du müsstest schon die gesamte Fehlermeldung und den tatsächlichen Code der die auslöst zeigen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten