tkinter mit while True Schleife im Hauptprogramm

Fragen zu Tkinter.
Antworten
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

Anfängerfrage zu tkinter /Raspi:

Ich habe mich jetzt ein paar Stunden in tkinter eingelesen und rumprobiert, stolpere aber über etliche Steine :/

Mein Python Skript hat folgenden Ablauf:

Code: Alles auswählen

import.......

def_main():

       while True:
           # xx sensorabfragen über mqtt  laufend
           function 1()
           
           -> hier gewünscht:  Button von einem Fenster laufend abfragen -> im Sinne von Notaus 
           
           # alle 10 min, Dauer ca. 1 min
           # sensordaten abfragen über bluetooth
           function 2()
           # sensordaten abfragen von Webseiten
           function 3()
                                
           #Aktoren schalten
           function 4 ()
           
           #Daten in msql speichern
           function 5()
           
           #Anzeige formatiert auf der console ausgeben
           function display formatiert auf console()
           function display auf Oled()
 
           -> hier gewünscht:  die Daten auf einem Fenster aktualisieren -> Label
 
 def function 1():
 def function 2()
 usw (.....)
 
 # Programmende
if __name__ == '__main__':
     main()          
           
MQTT wird bei jedem Schleifendurchlauf abgefragt, die anderen Abfragen dauern so ca 1 min bis sie alle durch sind.

Wo muss ich welche tkinter Aktionen einbauen ?
Kann ich das Def main() so stehen lassen oder muss ich die Funktionsaufrufe darin in eine extra Funktion einbauen
und def main() nur für die tkinterloop benutzen und daraus dann das eigentliche Programm aufrufen ?
Wie mache ich es, dass, sobald das tkinter Fenster geöffnet ist, die Hauptschleife weiterläuft ?
Muss ich die While True: Schleife durch die mainloop() von tkinter ersetzen damit die Buttons funktionieren ?
Wie mache ich es, dass die mqtt Abfrage nicht während der GUI Anzeige unterbrochen wird, dann gehen nämlich Daten verloren ?
Ist tkinter überhaupt das richtige Tool für so etwas. Ist ja eher Parallelprozessing ?

Sry für die vielen Fragen, aber ich habe nirgendwo ein passendes Beispiel für solch eine Anwendung gefunden.

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

Die gleiche Frage wird hier im Forum fast täglich gestellt, dementsprechend gibt es hier auch genug passende Beispiele.
So wie Du das denkst, funktioniert GUI-Programmierung halt nicht.
Im anderen Thread willst Du eigentlich einen Web-Server schreiben. Du solltest Dich auf eine Aufgabe konzentrieren.
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

Ja, habe auch schon gemerkt, das die tkinter Programmierung nicht so funktioniert wie ich mir das vorgestellt habe. Ich habe lange in Codesys programmiert, da ging das einfacher :)

Der Webserver ist für die Anzeige schon fertig, das war mit php aus der msql Datenbank recht einfach, nur die Rückmeldung an das python Skript fehlt noch....und ich suche da noch eine bessere Lösung als vom php skript aus eine Datei zu schreiben und diese mit Python auszuwerten..... Da man ab und an über Probleme schalfen sollte bin ich bei tkinter hängen geblieben.....

Gibst Du mir bitte einen Tip wonach ich suchen soll ? Ich habe unter Tkinter und while/Schleife nichts gefunden, sonst hätte ich ja nicht gepostet.

Schönes Wochenende
Heinz
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@eagleflight: Du suchst die `after()`-Methode. Damit kann man sagen das nach einer Anzahl von Millisekunden eine Funktion/Methode aufgerufen werden soll, die dann *kurz* etwas nicht-blockierendes machen darf, um dann die Kontrolle wieder an die GUI-Hauptschleife abzugeben.

Entweder kannst Du Deine Aufgaben so auf kleine Einzelschritte aufteilen, oder die müssen in einem anderen Thread laufen (`threading`-Modul), der allerdings nichts an der GUI machen darf. Dafür kommuniziert man dann üblicherweise mit dem Hauptthread in dem die GUI läuft über Queues (`queue`-Modul). Auch wieder mittels `after()`-Methode und regelmässig das verarbeiten was in der Queue steckt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

@eagleflight: kannst du nicht auch codesys benutzen? Wenn das deinem mentalen Modell besser entspricht, gibt es das ja auch für den PI.
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

@__deets__ Hab ich auch schon dran gedacht, nur funktioniert das Raspberry Codesys nicht mit meiner geliebten Gallileo Visualisierung :/, da fehlen die Targets und eine MQTT Schnittstelle gibt es leider auch nicht.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ist das etwas für Codesys? Dieses Ding von EATON? Aber läuft das nicht eh auf einem anderen Gerät? Und wie kommunizieren codesys Instanzen miteinander?

Dir Frage ist halt, wohin die Energie fließt. Den PI als mqtt Broker zu benutzen, und die Daten dann in codesys zu pumpen. Oder GUI Programmierung zu lernen. Wobei ich dir bei letzterem eher zu Qt raten würde, habe neulich http://www.pyqtgraph.org/ entdeckt und mag das.
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

@__deep__ Galileo von EATON gibt es auch als open-version für alle möglichen CPU's, allerdings nur die wirklich Leistungsfähigen. Codesys hat eine Socketverwaltung und auch noch extrem praktischeund stabile UDP Netzvariablen.

Zum Projekt:
Ich habe jetzt einfach ein zweites Programm gemacht um die Werte vom MQTT auszulesen und über tkinter auf einer Seite darzustellen.
Ausser viel Schreibarbeit war es relativ einfach die bidirektionale Kommunikation hinzubekommen. Läuft auch trotz der vielen Programmzeilen stabil. Sind halt meine ersten Schritte mit tkinter :/

Vielleicht habt ihr eine Idee wie man das Ganze kürzen kann.
Es werden ximmer fast gleiche json Strings eingelesen, die als Kenner immer eine Sensornr haben und dann, je nacj angeschlossenen Sensoren, ab und zu unterschiedliche, aber bekannte Key's haben, für die einzelnen Werte. Das müsste ja irgendwie über eine Schleife machbar sein, wenn es schon zu Fuss fast nur aus copy and paste besteht. Meine uralte programmierbare Alphakey Tastatur hatte mächtig damit zu tun die Blöcke zu generieren :)

Beim Import habe ich tkinter ganz importiert, weil sonst sticky nicht funktioniert hat.
Gibt es die Möglichkeit ein Template anzulegen, wenn man immer gleiche Label hat ?
Kann man dem Grid Manager beibringen, Nachkommastellen immer mit 2 digits anzuzeigen ?

Schönes weekend

Code: Alles auswählen

#!/usr/bin/python3
import paho.mqtt.client as mqtt
from tkinter import *                         # wegen sticky usw.
import time
import json
import sys

def on_connect(mqttsub, obj, flags, rc):
    print("on Connect", "rc: " + str(rc))
    pass

def on_message(mqttsub, obj, msg):
    print("message", msg.topic + " " + str(msg.qos) + " " + msg.payload.decode('UTF-8'))
    json_payload = json.loads(str(msg.payload.decode('UTF-8')))
    # print("payload", payload)   # {"Sensor":2,"Temperature":20.40,"Humidity":37.40,"Moisture":28.13,"Tmp_DS18B20":19.50}
    sensor = int(json_payload['Sensor'])
        
    title0.config(text="Plant Sensors Group 1 : ")
    topic0.config(text= "")                              
    
    # Sensor Titles in column 1
    title1_01.config(text="NodeMcu Sensoren : ")
    title1_02.config(text="Temperature : ")
    title1_03.config(text="Humidity    : ")
    title1_04.config(text="Moisture    : ")
    title1_05.config(text="Soil_Temp   : ")
    title1_06.config(text="Weight      : ")
    title1_07.config(text="CO₂ ppm     : ")
    title1_08.config(text="Conductivity: ")
    title1_09.config(text="pH Value    : ")
    
    title1_10.config(text="Air Temperature    :")
    title1_11.config(text="Air Humidity       :")
    title1_12.config(text="Air Pressure       :")
    title1_13.config(text="Sunlight Intensity :")
    title1_14.config(text="UV Intensity       :")
    title1_15.config(text="Particle 10.0 µg/m³:")
    title1_16.config(text="Particle  2.5 µg/m³:")
    title1_17.config(text="Windspeed          :")
    title1_18.config(text="------------------ :")
    title1_19.config(text="Rain Yesterday     :")
    title1_20.config(text="Ŕain Today         :")
    title1_21.config(text="Add. Water Today   :")
    title1_22.config(text="Ionisation         :")
    
    # Sensor values from ModeMCU's in columns 2-16
    if sensor == 1 :
        topic1_1.config(text= json_payload['Sensor'])
        topic1_2.config(text= json_payload['Temperature'])       
        topic1_3.config(text= json_payload['Humidity'])        
        topic1_4.config(text= json_payload['Moisture'])        
        topic1_5.config(text= json_payload['Tmp_DS18B20'])
        topic1_6.config(text= "")                              # empty for further use
    elif sensor == 2 :
        topic2_1.config(text= json_payload['Sensor'])
        topic2_2.config(text= json_payload['Temperature'])       
        topic2_3.config(text= json_payload['Humidity'])        
        topic2_4.config(text= json_payload['Moisture'])        
        topic2_5.config(text= json_payload['Tmp_DS18B20'])
        topic2_6.config(text= "")  
    elif sensor == 3 :
        topic3_1.config(text= json_payload['Sensor'])
        topic3_2.config(text= json_payload['Temperature'])       
        topic3_3.config(text= json_payload['Humidity'])        
        topic3_4.config(text= "")         #json_payload['Moisture'])        not installed
        topic3_5.config(text= "")         #json_payload['Tmp_DS18B20'])     not installed
        topic3_6.config(text= json_payload['CO2ppm'])                      # CO2 Sensor
    elif sensor == 4 :
        topic4_1.config(text= json_payload['Sensor'])
        topic4_2.config(text= json_payload['Temperature'])       
        topic4_3.config(text= json_payload['Humidity'])        
        topic4_4.config(text= "")        # json_payload['Moisture'])        not installed
        topic4_5.config(text= "")        #json_payload['Tmp_DS18B20'])      not installed 
        topic4_6.config(text= json_payload['Weight'])                      # scale sensor     
    elif sensor == 5 :
        topic5_1.config(text= json_payload['Sensor'])
        topic5_2.config(text= json_payload['Temperature'])       
        topic5_3.config(text= json_payload['Humidity'])        
        topic5_4.config(text= json_payload['Moisture'])        
        topic5_5.config(text= json_payload['Tmp_DS18B20'])
        topic5_6.config(text= "")  
    elif sensor == 6 :
        topic6_1.config(text= json_payload['Sensor'])
        topic6_2.config(text= json_payload['Temperature'])       
        topic6_3.config(text= json_payload['Humidity'])        
        topic6_4.config(text= json_payload['Moisture'])        
        topic6_5.config(text= json_payload['Tmp_DS18B20'])
        topic6_6.config(text= "")  
    elif sensor == 7 :
        topic7_1.config(text= json_payload['Sensor'])
        topic7_2.config(text= json_payload['Temperature'])       
        topic7_3.config(text= json_payload['Humidity'])        
        topic7_4.config(text= json_payload['Moisture'])        
        topic7_5.config(text= json_payload['Tmp_DS18B20'])
        topic7_6.config(text= "")
      
        
    #additional Sensorvalues from Raspberry and Homematic
    #json string via mqtt
    #topic8_1 - topic22_6
    
def on_publish(mqttsub, obj, mid):
    pass

def on_subscribe(mqttsub, obj, mid, granted_qos):
    print("Subscribed: " + str(mid) + " " + str(granted_qos))
    pass

def publish_message_water_on():
    out_topic = 'ESP_01/nodemcu6'    # waterpump connected on client 6
    message = ('{"DHT-TEMP":0,"DHT-HUM":0, "DS18B20":0,"ANALOG":0,"TIME":10000,"SWITCH":1}')    
    mqttsub.publish(out_topic, message)
    print(message, " to Client 6 sent" )
    
def publish_message_water_off():
    out_topic = 'ESP_01/nodemcu6'
    message = ('{"DHT-TEMP":0,"DHT-HUM":0, "DS18B20":0,"ANALOG":0,"TIME":0,"SWITCH":0}')   # 
    mqttsub.publish(out_topic, message)
    print(message, " to Client 6 sent" )
   
#----------------------------------------------------------    
# Main prog
mqttsub = mqtt.Client()
mqttsub.on_message = on_message
mqttsub.on_connect = on_connect
mqttsub.on_publish = on_publish
mqttsub.on_subscribe = on_subscribe
mqttsub.connect("192.168.10.125", 1883, 60)
mqttsub.subscribe("ESP_01", 0)

rootWindow =  Tk()
rootWindow.title('MQTT monitor')
rootWindow.geometry("1600x800+200+100")

title0 =  Label(rootWindow, font = ('fixed', 11),)                               # show group
title0.grid(sticky = W, row = 1, column = 1, padx = 3, pady = 3)
topic0 =  Label(rootWindow, font = ('fixed', 11),)                              
topic0.grid(sticky = W, row = 1, column = 2, padx = 3, pady = 3)

# sensor names in column 1
title1_01 =  Label(rootWindow, font = ('fixed', 11),)
title1_01.grid(sticky = W, row = 2, column = 1, padx = 3, pady = 3)
title1_02 =  Label(rootWindow, font = ('fixed', 11),)
title1_02.grid(sticky = W, row = 3, column = 1, padx = 3, pady = 3)
title1_03 =  Label(rootWindow, font = ('fixed', 11),)
title1_03.grid(sticky = W, row = 4, column = 1, padx = 3, pady = 3)
title1_04 =  Label(rootWindow, font = ('fixed', 11),)
title1_04.grid(sticky = W, row = 5, column = 1, padx = 3, pady = 3)
title1_05 =  Label(rootWindow, font = ('fixed', 11),)
title1_05.grid(sticky = W, row = 6, column = 1, padx = 3, pady = 3)
title1_06 =  Label(rootWindow, font = ('fixed', 11),)
title1_06.grid(sticky = W, row = 7, column = 1, padx = 3, pady = 3)
title1_07 =  Label(rootWindow, font = ('fixed', 11),)
title1_07.grid(sticky = W, row = 8, column = 1, padx = 3, pady = 3)
title1_08 =  Label(rootWindow, font = ('fixed', 11),)
title1_08.grid(sticky = W, row = 9, column = 1, padx = 3, pady = 3)
title1_09 =  Label(rootWindow, font = ('fixed', 11),)
title1_09.grid(sticky = W, row = 10, column = 1, padx = 3, pady = 3)
title1_10 =  Label(rootWindow, font = ('fixed', 11),)
title1_10.grid(sticky = W, row = 11, column = 1, padx = 3, pady = 3)
title1_11 =  Label(rootWindow, font = ('fixed', 11),)
title1_11.grid(sticky = W, row = 12, column = 1, padx = 3, pady = 3)
title1_12 =  Label(rootWindow, font = ('fixed', 11),)
title1_12.grid(sticky = W, row = 13, column = 1, padx = 3, pady = 3)
title1_13 =  Label(rootWindow, font = ('fixed', 11),)
title1_13.grid(sticky = W, row = 14, column = 1, padx = 3, pady = 3)
title1_14 =  Label(rootWindow, font = ('fixed', 11),)
title1_14.grid(sticky = W, row = 15, column = 1, padx = 3, pady = 3)
title1_15 =  Label(rootWindow, font = ('fixed', 11),)
title1_15.grid(sticky = W, row = 16, column = 1, padx = 3, pady = 3)
title1_16 =  Label(rootWindow, font = ('fixed', 11),)
title1_16.grid(sticky = W, row = 17, column = 1, padx = 3, pady = 3)
title1_17 =  Label(rootWindow, font = ('fixed', 11),)
title1_17.grid(sticky = W, row = 18, column = 1, padx = 3, pady = 3)
title1_18 =  Label(rootWindow, font = ('fixed', 11),)
title1_18.grid(sticky = W, row = 19, column = 1, padx = 3, pady = 3)
title1_19 =  Label(rootWindow, font = ('fixed', 11),)
title1_19.grid(sticky = W, row = 20, column = 1, padx = 3, pady = 3)
title1_20 =  Label(rootWindow, font = ('fixed', 11),)
title1_20.grid(sticky = W, row = 21, column = 1, padx = 3, pady = 3)
title1_21 =  Label(rootWindow, font = ('fixed', 11),)
title1_21.grid(sticky = W, row = 22, column = 1, padx = 3, pady = 3)
title1_22 =  Label(rootWindow, font = ('fixed', 11),)
title1_22.grid(sticky = W, row = 23, column = 1, padx = 3, pady = 3)

# sensor values in columns 2-6
topic1_1 =  Label(rootWindow, font = ('fixed', 12),)   #sensor nr bigger and in middle      
topic1_1.grid(sticky = N, row = 2, column = 2, padx = 3, pady = 3)
topic1_2 =  Label(rootWindow, font = ('fixed', 11),)
topic1_2.grid(sticky = W, row = 3, column = 2, padx = 3, pady = 3)
topic1_3 =  Label(rootWindow, font = ('fixed', 11),)
topic1_3.grid(sticky = W, row = 4, column = 2, padx = 3, pady = 3)
topic1_4 =  Label(rootWindow, font = ('fixed', 11),)
topic1_4.grid(sticky = W, row = 5, column = 2, padx = 3, pady = 3)
topic1_5 =  Label(rootWindow, font = ('fixed', 11),)
topic1_5.grid(sticky = W, row = 6, column = 2, padx = 3, pady = 3)
topic1_6 =  Label(rootWindow, font = ('fixed', 11),)
topic1_6.grid(sticky = W, row = 7, column = 2, padx = 3, pady = 3)

topic2_1 =  Label(rootWindow, font = ('fixed', 12),)
topic2_1.grid(sticky = N, row = 2, column = 3, padx = 3, pady = 3)
topic2_2 =  Label(rootWindow, font = ('fixed', 11),)
topic2_2.grid(sticky = W, row = 3, column = 3, padx = 3, pady = 3)
topic2_3 =  Label(rootWindow, font = ('fixed', 11),)
topic2_3.grid(sticky = W, row = 4, column = 3, padx = 3, pady = 3)
topic2_4 =  Label(rootWindow, font = ('fixed', 11),)
topic2_4.grid(sticky = W, row = 5, column = 3, padx = 3, pady = 3)
topic2_5 =  Label(rootWindow, font = ('fixed', 11),)
topic2_5.grid(sticky = W, row = 6, column = 3, padx = 3, pady = 3)
topic2_6 =  Label(rootWindow, font = ('fixed', 11),)
topic2_6.grid(sticky = W, row = 7, column = 3, padx = 3, pady = 3)

topic3_1 =  Label(rootWindow, font = ('fixed', 12),)
topic3_1.grid(sticky = N, row = 2, column = 4, padx = 3, pady = 3)
topic3_2 =  Label(rootWindow, font = ('fixed', 11),)
topic3_2.grid(sticky = W, row = 3, column = 4, padx = 3, pady = 3)
topic3_3 =  Label(rootWindow, font = ('fixed', 11),)
topic3_3.grid(sticky = W, row = 4, column = 4, padx = 3, pady = 3)
topic3_4 =  Label(rootWindow, font = ('fixed', 11),)
topic3_4.grid(sticky = W, row = 5, column = 4, padx = 3, pady = 3)
topic3_5 =  Label(rootWindow, font = ('fixed', 11),)
topic3_5.grid(sticky = W, row = 6, column = 4, padx = 3, pady = 3)
topic3_6 =  Label(rootWindow, font = ('fixed', 11),)
topic3_6.grid(sticky = W, row = 8, column = 4, padx = 3, pady = 3)    # co2ppm

topic4_1 =  Label(rootWindow, font = ('fixed', 12),)
topic4_1.grid(sticky = N, row = 2, column = 5, padx = 3, pady = 3)
topic4_2 =  Label(rootWindow, font = ('fixed', 11),)
topic4_2.grid(sticky = W, row = 3, column = 5, padx = 3, pady = 3)
topic4_3 =  Label(rootWindow, font = ('fixed', 11),)
topic4_3.grid(sticky = W, row = 4, column = 5, padx = 3, pady = 3)
topic4_4 =  Label(rootWindow, font = ('fixed', 11),)
topic4_4.grid(sticky = W, row = 5, column = 5, padx = 3, pady = 3)
topic4_5 =  Label(rootWindow, font = ('fixed', 11),)
topic4_5.grid(sticky = W, row = 6, column = 5, padx = 3, pady = 3)
topic4_6 =  Label(rootWindow, font = ('fixed', 11),)
topic4_6.grid(sticky = W, row = 7, column = 5, padx = 3, pady = 3)   # scale

topic5_1 =  Label(rootWindow, font = ('fixed', 12),)
topic5_1.grid(sticky = N, row = 2, column = 6, padx = 3, pady = 3)
topic5_2 =  Label(rootWindow, font = ('fixed', 11),)
topic5_2.grid(sticky = W, row = 3, column = 6, padx = 3, pady = 3)
topic5_3 =  Label(rootWindow, font = ('fixed', 11),)
topic5_3.grid(sticky = W, row = 4, column = 6, padx = 3, pady = 3)
topic5_4 =  Label(rootWindow, font = ('fixed', 11),)
topic5_4.grid(sticky = W, row = 5, column = 6, padx = 3, pady = 3)
topic5_5 =  Label(rootWindow, font = ('fixed', 11),)
topic5_5.grid(sticky = W, row = 6, column = 6, padx = 3, pady = 3)
topic5_6 =  Label(rootWindow, font = ('fixed', 11),)
topic5_6.grid(sticky = W, row = 7, column = 6, padx = 3, pady = 3)

topic6_1 =  Label(rootWindow, font = ('fixed', 12),)
topic6_1.grid(sticky = N, row = 2, column = 7, padx = 3, pady = 3)
topic6_2 =  Label(rootWindow, font = ('fixed', 11),)
topic6_2.grid(sticky = W, row = 3, column = 7, padx = 3, pady = 3)
topic6_3 =  Label(rootWindow, font = ('fixed', 11),)
topic6_3.grid(sticky = W, row = 4, column = 7, padx = 3, pady = 3)
topic6_4 =  Label(rootWindow, font = ('fixed', 11),)
topic6_4.grid(sticky = W, row = 5, column = 7, padx = 3, pady = 3)
topic6_5 =  Label(rootWindow, font = ('fixed', 11),)
topic6_5.grid(sticky = W, row = 6, column = 7, padx = 3, pady = 3)
topic6_6 =  Label(rootWindow, font = ('fixed', 11),)
topic6_6.grid(sticky = W, row = 7, column = 7, padx = 3, pady = 3)

topic7_1 =  Label(rootWindow, font = ('fixed', 12),)
topic7_1.grid(sticky = N, row = 2, column = 8, padx = 3, pady = 3)
topic7_2 =  Label(rootWindow, font = ('fixed', 11),)
topic7_2.grid(sticky = W, row = 3, column = 8, padx = 3, pady = 3)
topic7_3 =  Label(rootWindow, font = ('fixed', 11),)
topic7_3.grid(sticky = W, row = 4, column = 8, padx = 3, pady = 3)
topic7_4 =  Label(rootWindow, font = ('fixed', 11),)
topic7_4.grid(sticky = W, row = 5, column = 8, padx = 3, pady = 3)
topic7_5 =  Label(rootWindow, font = ('fixed', 11),)
topic7_5.grid(sticky = W, row = 6, column = 8, padx = 3, pady = 3)
topic7_6 =  Label(rootWindow, font = ('fixed', 11),)
topic7_6.grid(sticky = W, row = 7, column = 8, padx = 3, pady = 3)

#additional Sensorvalues from Raspberry and Homematic
#topic8_1 - topic22_6

#Buttons
button01 =  Button(rootWindow, text="Close Window", width=25, command= rootWindow.destroy).grid(row=30, column=10)
button02 =  Button(rootWindow, text="Stop Programm", width=25, command = sys.exit).grid(row=31, column=10)
button03 =  Button(rootWindow, text="Run Water for 10 secs", width=25, command = publish_message_water_on).grid(row=32, column=10)
button03 =  Button(rootWindow, text="Stop Water", width=25, command = publish_message_water_off).grid(row=33, column=10)
    

mqttsub.loop_start() 
rootWindow.mainloop()
# end of prog
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@eagleflight: Wenn das tatsächlich läuft, dann nur zufällig, denn entweder blockiert `mqttsub.loop_start()`, oder das startet im Hintergrund einen Thread. Im ersten Fall wird die GUI-Hauptschleife nicht ausgeführt, im zweiten Fall veränderst Du die GUI aus einem anderen Thread heraus als dem in dem die Hauptschleife läuft. Das kann scheinbar funktionieren, kann aber auch spektakulär abstürzen, oder sich einfach nur irgendwie komisch verhalten. Es ist letztlich undefiniertes Verhalten.

Zu den ganzen Codewiederholungen und nummerierten Namen: Es gibt Schleifen und Datenstrukturen in Python.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Keine Variablen! Für GUI-Programme bedeutet das man braucht für so ziemlich jedes nicht-triviale Programm objektorientierte Programmierung (OOP).

Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

@blackjack
Dazu ein Spruch: "Etwas funktioniert solange nicht, bis jemand, der das nicht weiß, es einfach macht " :)
Spaß beiseite:
Ich habe mich auch gewundert, habe aber irgendwo gelesen, dass die mqtt Routinen in einem eigenen Thread laufen. Sonst wäre es ja gar nicht möglich, dass der Server permanent im Hintergrund abgefragt wird. Dasselbe hatte ich ja auch schon bei der Datenabfrage. Dort brauchte es eine queue als Puffer um keine messages zu verlieren, wenn die While True: Schleife gerade woanders war während eine message ankam.
Die mqtt_loop wirkt ausschließlich auf die Serverabfrage, nicht auf die GUI. Vermutlich sitzt da noch irgendwo ein eventhandler, denn die Variablen im mqtt Sektor werden erst -nach- dem Eintreffen einer message upgedated. z.B. hat ein time.sleep(10) innerhalb der mqtt Sektion hat keinen Einfluss auf die GUI. Es gehen nur messages verloren die in dieser Schlaf-Zeit ankommen. Das spricht auch für eine Thread Verarbeitung. Mehr kann ich Dir leider nicht sagen, da ich ja erst seit kurzem überhaut mit Python arbeite und keinen Plan habe was da im Hinterrgung abgeht.

Das Programm läuft seit heute Mittag stabil und bekommt etwa alle 3-5 sec ein update. Die Last auf dem RAspi liegt bei unter 15% (mit der parallel laufenden Datenabfage) und sogar direkt aufeinander folgende/gleichzeitge messages werden verarbeitet. Man sieht beim Start gut wie der Screen erst leer ist, und sich dann, nach dem Eintreffen der Nachrichten nach und nach aufbaut. Das bezihet sich auch auf die Titelvariablen. Weder die Datenabfrage im Hintergrund noch die Ausgabe von Push Nachrichten über die Tasten haben eine spürbare Verzögerung.

Nachdem es zuerst mit einer Variablen geklappt hat, habe ich dann durch einfaches copy&paste das Ganz vergrößert um zu sehen ob es abstürzt. :)
Hat mich leider enttäuscht :) Ist wie gesagt ein Testprogramm dass sich aufgebläht hat. main() und die Auslagerung in Klassen und Funktionen werde ich dann machen wenn es ne Woche oder so Störungsfrei gelaufen hat.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@eagleflight: Natürlich wirkt die mqtt-Schleife auf die GUI. Von dieser Schleife aus wird `on_message()` aufgerufen und das verändert die GUI. Und das darf halt nicht sein. Es ist wirklich egal wie lange das bei Dir störungsfrei läuft, das Problem ist einfach das das Zufall ist. Das kann nach einem Tag krachen. Das kann auch 10 Jahre durchlaufen. Die Freuden von nebenläufiger Programmierung. Das kannst Du nicht durch einfaches laufen lassen testen, weil man damit Fehler nicht zuverlässig findet.

Beseitigen von globalen Variablen macht man nicht nach dem Testen. Schon gar nicht in nebenläufigen Programmen. Man hat eigentlich von Anfang an keine globalen Variablen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
onur22
User
Beiträge: 19
Registriert: Freitag 28. Dezember 2018, 21:25

Gibt es hier irgendwo ein gutes Beispiel für die after Methode? Ich kriege das nicht hin.

Mal ein Beispiel:

Code: Alles auswählen

from tkinter import *
import time

def Zahlen():
    count = 1
    while True:
        time.sleep(0.2)
        count += 1
   



def TKInter():
    master = Tk()
    Label(master, text="Zähler").grid()
    Button(master, text='Zahl:', command=Zahlen,).grid(row=7, column=1, sticky=W, pady=4)
    T = Text( height=10, width=50)
    T.grid()

    mainloop()
  
TKInter()
Was müsste ich hier noch beifügen, damit
1) Die GUI die Zahlen Funktion in sich printed, also T.Insert(Wie geht das ohne eine Globale Variable zu verwenden?)
2) Wie updated sich dann die GUI ohne sich direkt aufzuhängen bzw. so lange zu laden? Das soll ja mit after gehen, aber das kriege ich nicht hin!

Vielen Dank schonmal
alles ist als Einsteiger möglich. Es ist nur die Frage, wie lange es dauert, bis man die nötigen Vorkenntnisse erworben hat.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi eagleflight

Um dein Vorhaben besser zu verstehen habe ich speziell was den Skriptteil der GUI-Part betrifft ein wenig umstrukturiert. Das MQTT kenne ich leider nicht näher. Bin deshalb nicht weiter darauf eingegangen. Wenn du aber deine Daten in die 'data_receive_loop' reinziehen kannst können diese relativ einfach auf den MQTT-Monitor ausgegeben werden:

Code: Alles auswählen

#!/usr/bin/python3
#import paho.mqtt.client as mqtt
from tkinter import *                         # wegen sticky usw.
import tkinter as tk
from random import randint
import time
import json
import sys

from functools import partial

import tkinter as tk

APP_TITLE = "MQTT monitor"
APP_XPOS = 200
APP_YPOS = 100
APP_WIDTH = 1600
APP_HEIGHT = 800

class Mqtt(object):
    
    def __init__(self, app):
        self.app = app
        return
        self.mqttsub = mqtt.Client()
        self.mqttsub.on_message = self.on_message
        self.mqttsub.on_connect = self.on_connect
        
        self.mqttsub.on_publish = on_publish
        self.mqttsub.on_subscribe = self.on_subscribe
        self.mqttsub.connect("192.168.10.125", 1883, 60)
        self.mqttsub.subscribe("ESP_01", 0)

    def on_connect(self, mqttsub, obj, flags, rc):
        print("on Connect", "rc: " + str(rc))
        pass

    def on_message(self, mqttsub, obj, msg):
        print("message", msg.topic + " " + str(msg.qos) + " " + msg.payload.decode('UTF-8'))
        json_payload = json.loads(str(msg.payload.decode('UTF-8')))
        # print("payload", payload)   # {"Sensor":2,"Temperature":20.40,"Humidity":37.40,"Moisture":28.13,"Tmp_DS18B20":19.50}
        sensor = int(json_payload['Sensor'])

    def on_subscribe(mqttsub, obj, mid, granted_qos):
        print("Subscribed: " + str(mid) + " " + str(granted_qos))
        pass

    def publish_message_water_on(self):
        out_topic = 'ESP_01/nodemcu6'    # waterpump connected on client 6
        message = ('{"DHT-TEMP":0,"DHT-HUM":0, "DS18B20":0,"ANALOG":0,"TIME":10000,"SWITCH":1}')    
        mqttsub.publish(self, out_topic, message)
        print(message, " to Client 6 sent" )
        
    def publish_message_water_off(self):
        out_topic = 'ESP_01/nodemcu6'
        message = ('{"DHT-TEMP":0,"DHT-HUM":0, "DS18B20":0,"ANALOG":0,"TIME":0,"SWITCH":0}')   # 
        mqttsub.publish(out_topic, message)
        print(message, " to Client 6 sent" )


class Application(object):
    SENSOR_GROUP_TITLES = [
        "NodeMcu Sensoren :",
        "Temperature :",
        "Humidity :",
        "Moisture :",
        "Soil_Temp :",
        "Weight :",
        "CO₂ ppm :",
        "Conductivity :",
        "pH Value :",
        "Air Temperature :",
        "Air Humidity :",
        "Air Pressure :",
        "Sunlight Intensity :",
        "UV Intensity :",
        "Particle 10.0 µg/m³ :",
        "Particle  2.5 µg/m³ :",
        "Windspeed :",
        "------------------ :",
        "Rain Yesterday :",
        "Ŕain Today :",
        "Add. Water Today :",
        "Ionisation :"
        ]

    SENSOR_NAMES = [
        'Sensor',
        'Temperature',       
        'Humidity',        
        'Moisture',        
        'Tmp_DS18B20'
        ]

    NUM_OF_SENSORS = 7
        
    def __init__(self, main_win):
        self.main_win = main_win
        
        self.sensors = list()
        
        self.mqtt = Mqtt(self)
        self.build()
        self.data_receive_loop()
        
    def build(self):
        main_frame = tk.Frame(self.main_win)
        main_frame.pack(fill='both', expand=True, padx=10, pady=10)
        self.main_frame = main_frame
        
        monitor_frame = tk.Frame(main_frame, relief='groove', bd=2)
        monitor_frame.pack()
        
        title0 = tk.Label(monitor_frame, text="Plant Sensors Group 1",
            width=20, font=('Helvetica', 11, 'bold'), anchor='e', bg='gray95')
        title0.grid(sticky='w', row=0, column=0, padx=3, pady=3)
        
        for index, title in enumerate(self.SENSOR_GROUP_TITLES):
            row = index + 1
            titel_label = tk.Label(monitor_frame, text=title, width=20,
                font=('Helvetica',11), anchor='e')
            titel_label.grid(sticky='w', row=row, column=0, padx=3, pady=3)
        
        for sensor_index in range(self.NUM_OF_SENSORS):
            column = sensor_index + 1
            tk.Label(monitor_frame, text="Sensor-{}".format(sensor_index+1),
                width=10, font=('Helvetica', 11, 'bold'), bg='gray95'
                ).grid(sticky='w', row=0, column=column, padx=3, pady= 3)
            rows = len(self.SENSOR_NAMES)
            sensor_frame = tk.Frame(monitor_frame)
            sensor_frame.grid(row=1, column=column, rowspan=rows)
            sensor_data = dict()
            for index, sensor_name in enumerate(self.SENSOR_NAMES):
                row = index + 1
                sensor_value_var = tk.StringVar(self.main_win, "?")
                tk.Label(sensor_frame, textvariable=sensor_value_var,
                    font=('Helvetica', 11)
                    ).grid(sticky='w', row=row, column=0, padx=3,
                        pady=3)
                sensor_data[sensor_name] = sensor_value_var
            #print(sensor_data)
            self.sensors.append(sensor_data)
            #print(self.sensors)
        button_frame = tk.Frame(main_frame)
        button_frame.pack(pady=(10, 0))
        
        tk.Button(button_frame, text="Close Window",
            command=self.main_win.destroy).pack(side='left')
            
        tk.Button(button_frame, text="Stop Programm",
            command=sys.exit).pack(side='left')
        
        tk.Button(button_frame, text="Run Water for 10 secs",
            command = self.mqtt.publish_message_water_on).pack(side='left')
        
        tk.Button(button_frame, text="Stop Water",
            command = self.mqtt.publish_message_water_off).pack(side='left')

    def data_receive_loop(self):
        # For simulation only!
        for sensor_nr in range(self.NUM_OF_SENSORS):
            sensor_data = self.sensors[sensor_nr]
            for sensor_name in self.SENSOR_NAMES:
                random_data = randint(0, 1000)
                print(sensor_nr, sensor_name, random_data)
                self.sensors[sensor_nr][sensor_name].set(random_data)
            print()
                
        self.main_win.after(1000, self.data_receive_loop)

        
def main():
    main_win = tk.Tk()
    main_win.title(APP_TITLE)
    main_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    #main_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
    
    app = Application(main_win)
    
    main_win.mainloop()
 
 
if __name__ == '__main__':
    main()      
Gruss wuf :-)
Take it easy Mates!
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@onur22: Als erstes würde man den Sternchen-Import loswerden. Damit holt man sich gerade bei `tkinter` Unmengen an Namen ins Modul die gar nicht gebraucht werden.

Dann ist das Layout ziemlich wirr. Die Elemente werden komisch platziert und es werden auch viele Zellen mitten drin einfach frei gelassen.

Die meisten Namen widersprechen den üblichen Konventionen, sowohl was die Schreibweise angeht, als auch inhaltlich. Alles ausser Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase) wird klein_mit_unterstrichen geschrieben.

Funktionen und Methoden werden in der Regel nach der Tätigkeit benannt die sie ausführen. Weder `Zahlen` noch `TKInter` sind Tätigkeiten. Die Funktion mit dem Einstiegspunkt in das Hauptprogramm heisst üblicherweise `main()`.

`T` zu ändern ohne das global zu machen geht in dem man es als Argument an die Funktion übergibt, die es aktualisieren soll. Und den aktuellen Zählerstand muss man auch übergeben, denn der ändert sich ja auch von einem Aufruf zum nächsten. Die dauerhaft laufende ``while``-Schleife darf man ja nicht haben. Jeder Schleifendurchlauf dieser Schleife muss durch *einen* Aufruf einer Funktion/Methode abgearbeitet werden. Und dann kann diese Funktion/Methode mit `after()` dafür sorgen, dass sie in 200 ms erneut aufgerufen wird um die nächste Änderung an `T` vorzunehmen.

Die Schaltfläche muss man auch erst einmal an einen Namen binden, weil man das `command` erst festlegen kann, wenn man das `Text`-Objekt hat was von dem `command` verändert werden soll. Und da `command` eine Funktion *ohne* Argumente erwartet, man aber das `Text`-Objekt und den Zählerstand übergeben muss, braucht man `functools.partial()`.

Da kommt dann so etwas bei heraus:

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
import tkinter as tk


def update_text(text, count):
    text.insert(tk.END, f'{count} ')
    text.after(200, update_text, text, count + 1)


def main():
    root = tk.Tk()
    tk.Label(root, text='Zähler').pack()
    button = tk.Button(root, text='Zahl:')
    button.pack()
    text = tk.Text(height=10, width=50)
    text.pack()
    button['command'] = partial(update_text, text, 1)

    root.mainloop()


if __name__ == '__main__':
    main()
Wobei hier nicht verhindert wird das man mit der Schaltfläche bei jedem Klick einen Zähler mehr hat, der in das Textfeld schreibt. Wenn man das verhindern möchte, müsste man noch eine Funktion dazwischen schalten die auch noch die Schaltfläche übergeben bekommt und die nach dem ersten Klick deaktiviert.

Wenn man die Schaltfläche zum starten und stoppen des Zählers verwenden will, dann ist am auch schon an der Grenze dessen angelangt was man sinnvoll ohne objektorientierte Programmierung erreichen kann.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
eagleflight
User
Beiträge: 28
Registriert: Dienstag 12. Februar 2019, 19:45

@wuf
Danke für den Vorschlag, ich werde das Ende der Woche mal ausprobieren.
Wenn man die paho-mqtt Anleitung liest https://pypi.org/project/paho-mqtt/ , sieht man, das dort ein eigener Thread erzeugt wird, der im Hintergrund läuft und mit dem Hauptprogramm über die Funktionen kommuniziert. Es handelt sich also bei jedem Durchgang um eine begrenzte Abfrageschleife, keine Dauerschleife. die innerhalb der TKinter Schleife aufgerufen wird. Das müsste in die data_loop auszulagern sein. Vielleicht braucht man dort auch gar keine loop, sondern es reicht die Variablen im MQTT Teil einfach zu beschreiben. Das Ganze sieht mir wie eine Art Eventhandler aus, der immer dann kommuniziert, wenn eine message eingetroffen ist. Ich probiers einfach mal aus.

@blackjack
Du hast absolut recht. Softwareengineering geht immer, naja meistens, von einem Plan aus und die Konventionen müssen auch schon eingehalten werden, sonst blickt später niemand mehr durch. Für Anfänger ist es leider schwierig das Alles auf einmal zu schnallen und nicht ständig das Neue mit den Erfahrung durcheinander zu werfen.

Gruß
Heinz
Antworten