Python Forum
[Tkinter] Help with tkinter for a heating control GUI
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Help with tkinter for a heating control GUI
#1
Hello all,

I am quite new to Python and I am training myself on the internet. So please excuse me if my code is wrong/not very readable. I have also posted this on the RaspberryPi forums.

As I travel a lot I wanted to control my heater remotely and some time ago I built a basic "ON OFF" switch which I could access via VNC remotely. I also bought a temperature sensor but never made time to make a proper program to control the temperature.

I've finally come up with something but I have some questions I cannot answer myself.

I'm using tkinter and I made a simple interface. The base control actually works - I'm impressed - but I am struggling with a few things.

Question 1.
I do not understand how the "window_name.mainloop()" works. I understand it's to keep the graphic elements running but then I do not understand where I should write my other code - the code that decides whether it's time to switch the heating off based on the temperature etc.
I ended up putting everything into different functions. I am not sure it's the correct way of doing it, particularly when I call the main function from the main function itself to make it run all the time. I'm sure some of you would shiver!


Question 2.
Do I understand correctly or variables in functions only live in that function? I am struggling to have the "bypass" button to work - it would set a flag which would prevent the boiler from starting if set to TRUE - but if I understand correctly variables only live into each function independently?

In the end I wonder if you could tell me if my approach is correct - I doubt it. Again, apologies for the messy code - it's still working in progress. The lots of "print" are there for debug purposes! :)

Thanks for your help!

from tkinter import *
import tkinter as tk
import time
import board
import busio
import adafruit_mcp9808
import RPi.GPIO as GPIO

deltaplus = 0.2 #offset before heating status is changed
deltaminus = 0.5 #offset before heating status is changed

#Setting up GPIOs
GPIO.setup(21, GPIO.OUT)
GPIO.output(21, GPIO.LOW)
GPIO.setup(20, GPIO.OUT)
GPIO.output(20, GPIO.HIGH)

#Setting up temperature sensor
i2c_bus = busio.I2C(board.SCL, board.SDA)
mcp = adafruit_mcp9808.MCP9808(i2c_bus)
actualtempC = round(mcp.temperature,1)


# To increase set temperature by one degree    
def increase_temp():
    value = int(entry_set_temp.get())
    value = value + 1
    entry_set_temp.delete(0,2)
    entry_set_temp.insert(0, value)
    
    
# To decrease set temperature by one degree        
def decrease_temp():
    value = int(entry_set_temp.get())
    value = value - 1
    entry_set_temp.delete(0,2)
    entry_set_temp.insert(0, value)

#Heating bypass enable - when BYPASS button is pressed
def bypassOn():
    print("bypassON running")
    bypassFlag = 1
    print("BypassFlag from bypassOn",bypassFlag)
    heatingOff()
    
#def bypassOff():
#    bypassFlag = 0

#Variable initialisation - not working as variables only live in each DEF section?
def variableinit():
    bypassFlag = 0

#Switch heating off
def heatingOff():
    GPIO.output(21,GPIO.HIGH)
    boilerStatus = 0
    print("Boilerstatus into heatingoff", boilerStatus)
 
#Switch heating on IF bypass is not on 
def heatingOn():
    print("BypassFlag from heatingOn before IF",bypassFlag)
    if bypassFlag==0:
        GPIO.output(21,GPIO.LOW)
        boilerStatus = 1
        print("BypassFlag from heatingOn",bypassFlag)

#Main section - I'm sure there's a better way to do this!
def thermostat():
    print("Bypass beginning of THERMOSTAT:",bypassFlag)
    actualtempC = round(mcp.temperature,1)
    setTemp = int(entry_set_temp.get()) #read variable from user set temp field
    label_actual_temp = tk.Label(master, text = actualtempC, font=("Helvetica", 28)) #Display actual sensor temp
    label_actual_temp.place(relx = 0.5, rely = 0.1, anchor=N)

    print("Sensor temperature: ",actualtempC)
    print("Set temperature: ", int(entry_set_temp.get()))
    print("Bypass before main IF:",bypassFlag)
    
    #thermostat algoritm
    if actualtempC < setTemp - deltaminus: #if temp is below set temp, minus offset, then switch heating on
        heatingOn()
        print("one")
    if actualtempC > setTemp + deltaplus: #if temp is above set temp, plus offset, then switch heating off
        heatingOff()
        print("two")
#    print("BoilerStatus", boilerStatus)
#    label_heatingStatus = tk.Label(master, textvariable = boilerStatus, font=("Helvetica", 28))
#    label_heatingStatus.place(relx = 0.2, rely = 0.8, anchor=N)
    print("Bypass:",bypassFlag)
    print("END")
    master.after(3000,thermostat) #repeat this section? I'm sure, again, there's a better way to do this!

#Variable initialisation
variableinit()

#define tkinter window
master = tk.Tk()
master.geometry("800x480") 

#CURRENT TEMP label
label_current_temp = tk.Label(master, text="CURRENT TEMP °C", font=("Helvetica", 28))
label_current_temp.place(relx = 0.5, rely = 0.0, anchor=N)

#SET TEMP label
label_set_temp = tk.Label(master, text="SET TEMP °C", font=("Helvetica", 28))
label_set_temp.place(relx = 0.5, rely = 0.34, anchor = CENTER)

#label_set_temp = tk.Label(master, text="SET TEMP °C", font=("Helvetica", 28))
#label_set_temp.place(relx = 0.5, rely = 0.34, anchor = CENTER)

#BYPASS button
button_bypass = tk.Button(master, text="BYPASS", width=15, font=("Helvetica", 28), command=bypassOn)
button_bypass.place(relx = 0.5, rely = 0.68, anchor = CENTER)

#Winter Protection checkbox
winter_protection = IntVar()
check_winter_protection = Checkbutton(master, text="Winter Protection?", variable = winter_protection, font=("Helvetica", 18))
check_winter_protection.place(relx = 0.5, rely = 0.88, anchor = CENTER)

#label_actual_temp = tk.Label(master, text = actualtempC, font=("Helvetica", 28))
#label_actual_temp.place(relx = 0.5, rely = 0.1, anchor=N)

#SET TEMP field
default_temp = StringVar(master, value="19") # Set field to a default of 19 when programme starts
entry_set_temp = tk.Entry(master, font=("Helvetica", 28), width = 7, justify = CENTER, textvariable = default_temp)
entry_set_temp.place(relx = 0.5, rely = 0.44, anchor = CENTER)

# PLUS button for Set Temp field
button_plus = tk.Button(master, text="+", width=1, font=("Helvetica", 8), command=increase_temp)
button_plus.place(relx = 0.62, rely = 0.405, anchor = CENTER)

#MINUS button for Set Temp field
button_minus = tk.Button(master, text="-", width=1, font=("Helvetica", 8), command=decrease_temp)
button_minus.place(relx = 0.62, rely = 0.475, anchor = CENTER)

# main programme
thermostat()

master.mainloop()
İmage
Reply
#2
Quote:I do not understand how the "window_name.mainloop()" works
mainloop is a process that runs the program, then waits for a quit command from tkinter upon which it terminates, basically it's waiting for an event (interrupt).

Quote:Do I understand correctly or variables in functions only live in that function? I am struggling to have the "bypass" button to work - it would set a flag which would prevent the boiler from starting if set to TRUE - but if I understand correctly variables only live into each function independently?

This can be overcome forever, if you use classes, which are not trivial, and I can't explain in this post.
I can however do two things:

Here's an example that displays a virtual environment (or system) package list, and saves to a requirements.txt (or other name) file
that can be used for packaging

import tkinter as tk
import tkinter.filedialog as fd
from pip._internal.operations import freeze
 
 
class InstalledPackages:
    def __init__(self, parent):
        self.w = parent
        self.bgc1 = '#ffffe5'
        self.bgc2 = 'Lavender'
        self.bgc3 = ' LightCyan'
        self.pkglist = None
        self.w.geometry('400x400+10+10')
        self.w.title("Larz60+ Python Package List")
        self.pkglist = freeze.freeze()
        self.t1 = None
 
    def list_packages(self):
        w = self.w
 
        self.t1 = tk.Text(w, wrap=tk.WORD, undo=0, bg=self.bgc1)
        self.t1.pack(expand=1, fill=tk.BOTH)
        self.t1scroll = tk.Scrollbar(self.t1)
        self.t1.configure(yscrollcommand=self.t1scroll.set)
        self.t1scroll.config(command=self.t1.yview)
        self.t1scroll.pack(side=tk.RIGHT, fill=tk.Y)
        b1 = tk.Button(w, text='Save', command=self.save_requirements_file)
        b1.pack(side=tk.BOTTOM)
 
        self.t1.insert(tk.END, "{Package Name} -- {Version Info}\n\n")
        for pkg in self.pkglist:
            self.t1.insert(tk.END, f"   {pkg}\n")
 
    def save_requirements_file(self):
        fp = fd.asksaveasfile(mode='w', defaultextension=".txt")
        gui_text = str(self.t1.get(1.0, tk.END))        
        # Nothing to do if fp is None
        fp.write(f"{gui_text}")


if __name__ == '__main__':
    root = tk.Tk()
    InsPkgs = InstalledPackages(root)
    InsPkgs.list_packages()
    root.mainloop()
output (my virtual environment)
   
Note that all variables are stores and initialized in the __init__ method.
self.t1 is used across methods, in list_packages and save_requirements_file
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020