ich bin gerade dabei mein erstes größeres Projekt mit Hilfe eines Raspberry PI's als Steuerung umzusetzten. Das ganze ist eine Steuerung für eine etwas komplexere Heizungsanlage mit Wasserführendem Ofen, Wärmepumpe und Pufferspeicher... Aus dem Pufferspeicher kann aber auch eine Brauchwasserwärmepumpe mit einem Zusätzlichen Wärmetauscher erwärmt werden und zusätzlich sollen noch weitere Komfortfunktionen wie Automatisches Pufferspeicher aufheizen durch die Wärmepumpe bei PV Strom Überschuss in abhängigkeit zur Außentemperatur passieren... Also sehr komplex und auch mit keiner fertigen Heizungssteuerung so umsetzbar...
Jetzt zu meinem eigentlichen Problem. Ich habe die Steuerung in einzelne skripte aufgeteilt und das ganze Wartungsfreundlich und "modular" zu gestalten, eines dieser Skripte ist die Haupt Visualisierung, in der die ganzen Messwerte und Betriebsarten usw. der Pumpen angezeigt werden.
Die GUI läuft also dauerhaft... Leider wird sie mit der Zeit sehr träge, das heißt nach ca. 2 bis 3 Stunden Laufzeit merkt man bereits, dass sie langsam auf eingaben an den Button reagiert und nach mehr als 5 Stunden dauert es dann gute 5 Sekunden bis sie auf die Eingaben reagiert und auch die Aktualiserung der Daten kommt dann irgendwann ganz zum erliegen. Ich vermute es liegt an der Datenbank abfrage, bin dort aber noch nicht so tief im Thema um es besser hin zu bekommen.
Wenn jemand einen Tipp für mich hat, wie ich das ganze besser gestaltet bekomme, freue ich mich über Tipps.
Hier der Code:
Code: Alles auswählen
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *
import os.path
import sqlite3
import subprocess
import threading
sys.path.append('/home/PI/Heizungssteuerung/Log')
from logger_config import setup_logger
# Logger für dieses spezifische Unterprogramm einrichten
logger = setup_logger('Übersicht','main.log')
_location = os.path.dirname(__file__)
_bgcolor = '#d9d9d9'
_fgcolor = '#000000'
_tabfg1 = 'black'
_tabfg2 = 'white'
_bgmode = 'light'
_tabbg1 = '#d9d9d9'
_tabbg2 = 'gray40'
_style_code_ran = 0
def _style_code():
global _style_code_ran
if _style_code_ran:
return
style = ttk.Style()
style.theme_use('default')
style.configure('.', font="TkDefaultFont")
if sys.platform == "win32":
style.theme_use('winnative')
_style_code_ran = 1
class Übersicht:
def __init__(self, top=None):
self.top = top
self.top.attributes('-fullscreen', True)
top.minsize(1, 1)
top.maxsize(1024, 600)
top.resizable(1, 1)
top.title("Heizungssteuerung Übersicht")
_style_code()
self.notaus_state = 0
self._create_separators()
self._create_buttons()
self.create_exit_button()
self._create_frames()
self._create_labels()
self._update_labels_from_database()
self.update_hk_pumpe_status()
self.start_update_loop()
def _create_separators(self):
separators = [
("TSeparator2", 0.137, 0.25, 0.282, None),
("TSeparator2_1", 0.132, 0.765, 0.282, None),
("TSeparator1_1", 0.135, 0.613, 0.06, None),
("TSeparator3", 0.361, 0.1, 0.059, None),
("TSeparator1", 0.137, 0.1, 0.06, None),
("TSeparator3_1", 0.362, 0.61, 0.059, None),
("TSeparator6", 0.938, 0.1, None, 0.45),
("TSeparator7", 0.91, 0.3, 0.025, None),
("TSeparator8", 0.713, 0.317, 0.029, None),
("TSeparator9", 0.625, 0.383, None, 0.267),
("TSeparator10", 0.566, 0.65, 0.059, None),
("TSeparator4", 0.566, 0.767, 0.175, None),
("TSeparator5", 0.566, 0.1, 0.367, None)
]
for name, relx, rely, relwidth, relheight in separators:
sep = ttk.Separator(self.top)
sep.place(relx=relx, rely=rely, relwidth=relwidth, relheight=relheight)
if relheight:
sep.configure(orient="vertical")
setattr(self, name, sep)
def _create_buttons(self):
buttons = [
("Settings", "Einstellungen", 0.869, 0.917, self.open_settings),
("PufferLaden", "Puffer Laden", 0.869, 0.85, None),
("NotAus", "Not-Aus", 0.742, 0.917, self.toggle_notaus),
# ("Beenden", "Beenden", 0.742, 0.85, None)
]
for name, text, relx, rely, command in buttons:
btn = tk.Button(self.top, text=text, font="-family {DejaVu Sans} -size 10")
btn.place(relx=relx, rely=rely, height=31, width=111)
btn.configure(activebackground="#d9d9d9")
if command:
btn.configure(command=command)
setattr(self, name, btn)
def create_exit_button(self):
self.EXIT = tk.Button(self.top, text='Beenden', font="-family {DejaVu Sans} -size 10", command=self.set_stop_flag)
self.EXIT.place(relx=0.742, rely=0.85, height=31, width=111)
def _create_frames(self):
frames = [
("LGThermaV", self.top, 0.742, 0.55, 0.242, 0.22),
("HKPumpeFrame", self.top, 0.752, 0.217, 0.158, 0.155),
("HKFrame", self.top, 0.605, 0.217, 0.158, 0.104),
("PumpeBWWPFrame", self.top, 0.195, 0.583, 0.17, 0.171),
("OfenPumpeFrame", self.top, 0.195, 0.067, 0.17, 0.171),
("OfenFrame", self.top, 0.02, 0.067, 0.208, 0.114),
("BWWPFrame", self.top, 0.02, 0.583, 0.208, 0.114),
("WetterFrame", self.top, 0.02, 0.85, 0.125, 0.67),
("PufferFrame", self.top, 0.42, 0.067, 0.725, 0.146)
]
for name, parent, relx, rely, relheight, relwidth in frames:
frame = tk.Frame(parent, relief='groove', borderwidth="2")
frame.place(relx=relx, rely=rely, relheight=relheight, relwidth=relwidth)
setattr(self, name, frame)
self.Frame2_2_1 = tk.Frame(self.PumpeBWWPFrame, relief='groove', borderwidth="2")
self.Frame2_2_1.place(relx=1.897, rely=4.635, relheight=1.0, relwidth=1.0)
self.Frame2_2 = tk.Frame(self.OfenPumpeFrame, relief='groove', borderwidth="2")
self.Frame2_2.place(relx=1.897, rely=4.635, relheight=1.0, relwidth=1.0)
self.Frame2_1 = tk.Frame(self.OfenFrame, relief='groove', borderwidth="2")
self.Frame2_1.place(relx=1.154, rely=2.84, relheight=1.0, relwidth=1.0)
def _create_labels(self):
labels = [
(self.LGThermaV, "WPStatus", 0.089, 0.276, "Label", 21, 100),
(self.LGThermaV, "WPFlow", 0.089, 0.483, "Label", 21, 100),
(self.LGThermaV, "WPText", 0.044, 0.069, "LG Therma V Wärmepumpe", 21, 189),
(self.top, "WPWaterIN", 0.684, 0.717, "Label", 21, 60),
(self.top, "WPWaterOUT", 0.88, 0.5, "Label", 21, 60),
(self.HKPumpeFrame, "HKPumpeBetriebsart", 0.074, 0.4, "Label", 21, 100),
(self.HKPumpeFrame, "HKPumpeBetr", 0.074, 0.685, "Label", 21, 140),
(self.HKPumpeFrame, "HKPumpeText", 0.074, 0.211, "Heizkreis Pumpe", 11, 119),
(self.HKFrame, "HKText", 0.075, 0.137, "Heizkreis", 26, 75),
(self.PumpeBWWPFrame, "StatusBWWPPumpe", 0.057, 0.38, "Label", 21, 100),
(self.PumpeBWWPFrame, "BetrBWWPPumpe", 0.057, 0.65, "Label", 21, 100),
(self.PumpeBWWPFrame, "PumpeBWWPText", 0.057, 0.118, "Pumpe BWWP", 17, 102),
(self.Frame2_2_1, "PumpeOfen_1_1", 0.08, 0.129, "Pumpe Ofen", 17, 122),
(self.OfenPumpeFrame, "StatusOfenPumpe", 0.057, 0.38, "Label", 21, 100),
(self.OfenPumpeFrame, "BetrOfenPumpe", 0.057, 0.65, "Label", 21, 100),
(self.Frame2_2, "PumpeOfen_1", 0.08, 0.129, "Pumpe Ofen", 17, 123),
(self.OfenPumpeFrame, "PumpeOfenText", 0.057, 0.118, "Pumpe Ofen", 17, 93),
(self.Frame2_1, "Label1_1", 0.085, 0.096, "Ofen", 23, 91),
(self.OfenFrame, "T21", 0.085, 0.4, "Label", 21, 89),
(self.OfenFrame, "OfenText", 0.094, 0.08, "Ofen", 13, 91),
(self.BWWPFrame, "T22", 0.085, 0.4, "Label", 21, 89),
(self.BWWPFrame, "BWWPText", 0.094, 0.08, "BWWP", 11, 86),
(self.WetterFrame, "WetterInfoText", 0.015, 0.133, "Wetter", 21, 59),
(self.WetterFrame, "OutTempText", 0.015, 0.533, "Außentemperatur:", 21, 129),
(self.WetterFrame, "PufferHeizStatus", 0.845, 0.533, "Label", 21, 59),
(self.WetterFrame, "PufferHeizText", 0.802, 0.133, "Puffer Heizen", 21, 109),
(self.WetterFrame, "WetterMorgenText", 0.437, 0.133, "Wetter morgen", 21, 109),
(self.WetterFrame, "WetterMaxText", 0.452, 0.4, "Max. Temp.:", 21, 89),
(self.WetterFrame, "WetterMinText", 0.452, 0.667, "Min. Temp.:", 21, 89),
(self.WetterFrame, "WetterMaxWert", 0.583, 0.4, "Label", 21, 69),
(self.WetterFrame, "WetterMinWert", 0.583, 0.667, "Label", 21, 70),
(self.WetterFrame, "T23", 0.204, 0.533, "Label", 21, 89),
(self.PufferFrame, "T12", 0.133, 0.368, "Label", 21, 109),
(self.PufferFrame, "T13", 0.133, 0.621, "Label", 21, 109),
(self.PufferFrame, "T11", 0.133, 0.138, "Label", 21, 109),
(self.PufferFrame, "PufferText", 0.02, 0.018, "Pufferspeicher 600L", 18, 140),
(self.PufferFrame, "T14", 0.133, 0.851, "Label", 21, 99),
(self.top, "UPobentext", 0.02, 0.49, "WW Umwälzung Oben:", 21, 180),
(self.top, "UPuntentext", 0.02, 0.54, "WW Umwälzung Unten:", 21, 180),
(self.top, "UPobenStatus", 0.18, 0.49, "Label", 21, 89),
(self.top, "UPuntenStatus", 0.18, 0.54, "Label", 21, 89)
]
for parent, name, relx, rely, text, height, width in labels:
lbl = tk.Label(parent, text=text, anchor='w', font="-family {DejaVu Sans} -size 10")
lbl.place(relx=relx, rely=rely, height=height, width=width)
lbl.configure(activebackground="#d9d9d9", compound='left')
setattr(self, name, lbl)
def set_stop_flag(self):
try:
conn = sqlite3.connect('/home/PI/Heizungssteuerung/DB/sqlite-database.db')
cursor = conn.cursor()
cursor.execute("UPDATE DatenundBits SET Stop = 1 WHERE id = (SELECT MAX(id) FROM DatenundBits)")
conn.commit()
conn.close()
print("Stop-Flag wurde in der Datenbank gesetzt.")
except Exception as e:
print(f"Fehler beim Aktualisieren der Datenbank: {e}")
self.top.quit()
def toggle_notaus(self):
conn = sqlite3.connect('/home/PI/Heizungssteuerung/DB/sqlite-database.db')
cursor = conn.cursor()
cursor.execute("SELECT Notaus FROM DatenundBits ORDER BY id DESC LIMIT 1")
current_state = cursor.fetchone()[0]
new_state = 1 if current_state == 0 else 0
cursor.execute("UPDATE DatenundBits SET Notaus =? WHERE id = (SELECT MAX(id) FROM DatenundBits)", (new_state,))
conn.commit()
conn.close()
self.update_notaus_button(new_state)
logger.warning("Not-Aus betätigt")
def update_notaus_button(self, state):
if state == 1:
self.NotAus.configure(background="red", activebackground="red")
else:
self.NotAus.configure(background=self.top.cget('background'), activebackground="#d9d9d9")
self.notaus_state = state
def _check_notaus_state(self):
conn = sqlite3.connect('/home/PI/Heizungssteuerung/DB/sqlite-database.db')
cursor = conn.cursor()
cursor.execute("SELECT Notaus FROM DatenundBits ORDER BY id DESC LIMIT 1")
notaus_state = cursor.fetchone()[0]
conn.close()
if notaus_state != self.notaus_state:
self.update_notaus_button(notaus_state)
def start_update_loop(self):
self._update_labels_from_database()
self._check_notaus_state()
self.top.after(1000, self.start_update_loop) # Aktualisiert jede Sekunde
def open_settings(self):
try:
settings_script_path = "/home/PI/Heizungssteuerung/GUI/Einstellungen.py"
subprocess.Popen(["python", settings_script_path])
except Exception as e:
print(f"Fehler beim Öffnen der Einstellungen: {e}")
def _update_labels_from_database(self):
# Verbindung zur Datenbank herstellen
conn = sqlite3.connect('/home/PI/Heizungssteuerung/DB/sqlite-database.db')
cursor = conn.cursor()
# Abfrage aller Temperaturen
cursor.execute("""
SELECT
T11,
T12,
T13,
T14,
T21,
T22,
T23,
T24,
WPWaterInTemp,
WPWaterOutTemp,
WPFlow,6
`K1.1 (Ladepumpe BWWP)`,
`K1.2 (Ladepumpe Ofen)`,
`K1.3 (Umwälzpumpe oben)`,
`K1.4 (Umwälzpumpe unten)`
FROM ModbusRTUDaten
ORDER BY id DESC
LIMIT 1
""")
ModbusDaten = cursor.fetchone()
cursor.execute("""
SELECT
`WettermorgenMAX`,
`WettermorgenMIN`,
`HeizenPuffer`,
`HKPumpeStatus`,
`OfenPumpeStatus`,
`BWWPPumpeStatus`,
`StatusWP`,
`PrztHKPumpe`
FROM DatenundBits
ORder BY id DESC
Limit 1
""")
DatenundBits = cursor.fetchone()
#print("Debug - Datenbankwerte:", output) # Debugging-Ausgabe
# Aktualisieren der Labels mit Werten
if ModbusDaten:
self.T11.config(text=f"{ModbusDaten[0]:.1f}°C")
self.T12.config(text=f"{ModbusDaten[1]:.1f}°C")
self.T13.config(text=f"{ModbusDaten[2]:.1f}°C")
self.T14.config(text=f"{ModbusDaten[3]:.1f}°C")
self.T21.config(text=f"{ModbusDaten[4]:.1f}°C")
self.T22.config(text=f"{ModbusDaten[5]:.1f}°C")
self.T23.config(text=f"{ModbusDaten[6]:.1f}°C")
self.WetterMaxWert.config(text=f"{DatenundBits[0]:.1f}°C")
self.WetterMinWert.config(text=f"{DatenundBits[1]:.1f}°C")
self.WPWaterIN.config(text=f"{ModbusDaten[8]:.1f}°C")
self.WPWaterOUT.config(text=f"{ModbusDaten[9]:.1f}°C")
self.WPFlow.config(text=f"{ModbusDaten[10]:.1f} m³/h")
self.update_status_label(self.UPobenStatus, ModbusDaten[13])
self.update_status_label(self.UPuntenStatus, ModbusDaten[14])
self.update_status_label(self.PufferHeizStatus, DatenundBits[2])
self.update_status_label(self.HKPumpeBetriebsart, DatenundBits[3])
self.update_status_label(self.StatusOfenPumpe, DatenundBits[4])
self.update_status_label(self.BetrOfenPumpe, ModbusDaten[12])
self.update_status_label(self.StatusBWWPPumpe, DatenundBits[5])
self.update_status_label(self.BetrBWWPPumpe, ModbusDaten[11])
self.update_status_label(self.WPStatus, DatenundBits[6])
# Verbindung schließen
conn.close()
# Regelmäßige Aktualisierung (z.B. alle 5 Sekunden)
self.top.after(5000, self._update_labels_from_database)
def update_status_label(self, label, value):
if value == 1:
status_text = "Betrieb"
status_farbe = "green"
elif value == 2:
status_text = "Handetrieb"
status_farbe = "orange"
elif value == 3:
status_text = "Automatik"
status_farbe = "green"
elif value == 4:
status_text = "Abtauen"
status_farbe = "blue"
else:
status_text = "Aus"
status_farbe = "red"
label.config(text=status_text, fg=status_farbe)
def update_hk_pumpe_status(self):
conn = sqlite3.connect('/home/PI/Heizungssteuerung/DB/sqlite-database.db')
cursor = conn.cursor()
cursor.execute("SELECT PrztHKPumpe FROM DatenundBits ORDER BY id DESC LIMIT 1")
pwm_value = cursor.fetchone()[0]
conn.close()
if pwm_value == 0:
status = "Automatikbetrieb"
color = "green"
elif 1 <= pwm_value <= 91:
drehzahl = 100 - ((pwm_value - 10) * 100 / 74) if pwm_value >= 10 else 100
status = f"Sollwertvorg {drehzahl:.0f}%"
color = "orange"
elif 96 <= pwm_value <= 99:
status = "Aus"
color = "red"
else:
status = "Unbekannt"
color = "black"
self.HKPumpeBetr.config(text=status, fg=color)
self.top.after(5000, self.update_hk_pumpe_status) # Regelmäßige Aktualisierung (z.B. alle 5 Sekunden)
def start_Übersicht():
global root
root = tk.Tk()
root.protocol('WM_DELETE_WINDOW', root.destroy)
global _top1, _w1
_top1 = root
_w1 = Übersicht(_top1)
root.mainloop()
if __name__ == '__main__':
start_Übersicht()
