Function to check distance using ultrasonic sensor freezes tkinter - python

I'm using trying to use python with an ultrasonic sensor to measure distance, and then update a tkinter label with the distance value every second. However, I'm having problems; it will run for a while, anything from a couple of seconds up to a few minutes, then freeze.
Here is my code:
from tkinter import *
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO_TRIGGER_X = 4
GPIO_ECHO_X = 27
GPIO.setup(GPIO_TRIGGER_X, GPIO.OUT)
GPIO.setup(GPIO_ECHO_X, GPIO.IN)
def distanceX():
GPIO.output(GPIO_TRIGGER_X, True)
time.sleep(0.0001)
GPIO.output(GPIO_TRIGGER_X, False)
StartTime = time.time()
StopTime = time.time()
while GPIO.input(GPIO_ECHO_X) == 0:
StartTime = time.time()
while GPIO.input(GPIO_ECHO_X) == 1:
StopTime = time.time()
TimeElapsed = StopTime - StartTime
distance = (TimeElapsed * 34300) / 2
return distance
def updateDistance():
dX = distanceX()
print(dX)
lengthValue.configure(text=dX)
root.after(1000, updateDistance)
root = Tk()
root.geometry("200x100")
root.tk_setPalette(background="white", foreground="black")
lengthName = Label(root, text = "Length:")
lengthValue = Label(root, text="start")
lengthName.grid(row=1, column=1)
lengthValue.grid(row=1, column=2)
updateDistance()
root.mainloop()
I have tried running distanceX() alone in a separate script just printing out the values, that works fine. I've also tried the running the script without distanceX() like this:
dX = 0
def updateDistance():
global dX
print(dX)
lengthValue.configure(text=dX)
dX += 1
root.after(1000, updateDistance)
..and that also works fine.
Any ideas?
Apologies in advance if I've left any needed info out, this is my first go at python and tkinter...

Tkinter is single threaded. Your while loop in function distanceX blocks the main thread until it receives a True value and continues with the rest of the function. That's why you are experiencing freezes.
Try run the below:
from tkinter import *
import time
root = Tk()
flag = True
def something():
global flag
while flag:
print ("Hello World")
time.sleep(1)
def set_flag():
global flag
flag = False
something()
root.after(2000,set_flag)
root.mainloop()
And you will see your Tk window won't even pop up due to While loop blocking the main thread.
To solve this, you need to thread your distanceX() function. Something like:
from tkinter import *
import threading, time
root = Tk()
flag = True
def something():
global flag
while flag:
print ("Hello world")
time.sleep(1)
def set_flag():
global flag
flag = False
t = threading.Thread(target=something)
t.start()
root.after(2000,set_flag)
root.mainloop()
You can read more about threading in here.

Turns out the problem was in fact the two while loops in distanceX(). Added a timeout to both and all is well. Working code:
from tkinter import *
import RPi.GPIO as GPIO
import threading, time
GPIO.setmode(GPIO.BCM)
GPIO_TRIGGER_X = 4
GPIO_ECHO_X = 27
GPIO.setup(GPIO_TRIGGER_X, GPIO.OUT)
GPIO.setup(GPIO_ECHO_X, GPIO.IN)
def distanceX():
while True:
timeout = time.time() + 0.1
GPIO.output(GPIO_TRIGGER_X, True)
time.sleep(0.0001)
GPIO.output(GPIO_TRIGGER_X, False)
StartTime = time.time()
StopTime = time.time()
while GPIO.input(GPIO_ECHO_X) == 0:
StartTime = time.time()
if time.time() > timeout:
break
while GPIO.input(GPIO_ECHO_X) == 1:
StopTime = time.time()
if time.time() > timeout:
break
TimeElapsed = StopTime - StartTime
distance = (TimeElapsed * 34300) / 2
print(distance)
lengthValue.configure(text=distance)
time.sleep(1)
def check():
print("All good")
root = Tk()
root.geometry("200x100")
root.tk_setPalette(background="white", foreground="black")
lengthName = Label(root, text = "Length:")
lengthValue = Label(root, text="start")
button = Button(root, text="Check", command=check)
lengthName.grid(row=1, column=1)
lengthValue.grid(row=1, column=2)
button.grid(row=2, column=1)
t1 = threading.Thread(target=distanceX)
t1.start()
root.mainloop()

Related

GUI Tkinter Alarm Clock not responding

I created a tkinter alarm clock which is not responding after setting the alarm. The alarm sound is playing but after that I have to exit the application and re-run the code to set alarm once again .I have shared the code and screenshot of the app.
from tkinter import
import datetime
import time
from playsound import playsound
def Alarm(set_alarm_timer):
while True:
time.sleep(1)
actual_time = datetime.datetime.now()
cur_time = actual_time.strftime("%H:%M:%S")
cur_date = actual_time.strftime("%d/%m/%Y")
msg="Current Time: "+str(cur_time)
print(msg)
if cur_time == set_alarm_timer:
playsound("Audio.mp3")
break
def get_alarm_time():
alarm_set_time = f"{hour.get()}:{min.get()}:{sec.get()}"
Alarm(alarm_set_time)
window = Tk()
window.title("Alarm Clock")
window.geometry("400x160")
window.config(bg="#922B21")
window.resizable(width=False,height=False)
time_format=Label(window, text= "Remember to set time in 24 hour format!", fg="white",bg="#922B21",font=("Arial",15)).place(x=20,y=120)
addTime = Label(window,text = "Hour Min Sec",font=60,fg="white",bg="black").place(x = 210)
setYourAlarm = Label(window,text = "Set Time for Alarm: ",fg="white",bg="#922B21",relief = "solid",font=("Helevetica",15,"bold")).place(x=10, y=40)
hour = StringVar()
min = StringVar()
sec = StringVar()
hourTime= Entry(window,textvariable = hour,bg = "#48C9B0",width = 4,font=(20)).place(x=210,y=40)
minTime= Entry(window,textvariable = min,bg = "#48C9B0",width = 4,font=(20)).place(x=270,y=40)
secTime = Entry(window,textvariable = sec,bg = "#48C9B0",width = 4,font=(20)).place(x=330,y=40)
submit = Button(window,text = "Set Your Alarm",fg="Black",bg="#D4AC0D",width = 15,command = get_alarm_time,font=(20)).place(x =100,y=80)
window.mainloop()
.mainloop is some sort of while loop. So time.sleep() and while ... will mess with it. Use .after().
Edit: .place(),.pack() and .grid() geometry managers return None. And in python, the value of the last function is assigned.
here, it would be None, and might raise errors in future
def Alarm(set_alarm_timer):
actual_time = datetime.datetime.now()
cur_time = actual_time.strftime("%H:%M:%S")
if cur_time != set_alarm_timer:
msg="Current Time: "+str(cur_time)
print(msg)
window.after(1000,Alarm, set_alarm_timer)
else:
playsound("Audio.mp3")
...
submit = Button(window,text = "Set Your Alarm",fg="Black",bg="#D4AC0D",width = 15,command = lambda: Alarm(f"{hour.get()}:{min.get()}:{sec.get()}"),font=(20))
submit.place(x =100,y=80)

How to make a speedrun timer stay to the front

I made an in game timer for Minecraft to use while speedrunning and it works great but the one problem is that when I make Minecraft fill my whole screen NOT FULLSCREEN just cover it the timer disappears.
I knew this would happen and I am wondering if this is possible to fix and make the pygame window go to the front even if it is blocked by an app that you are currently using.
You can do this with Tkinter:
This way, you can make a timer:
Always put to front
Transparent
Furthermore, tkinter is in the Standard library.
You can use this code:
from tkinter import *
from threading import Thread
from time import time
tk = Tk()
tk.title('Timer')
tk.wm_attributes('-topmost', 1) # put the window to front
pause = True
start = time()
time_when_paused = time() # time displayed when in pause
def restart(): # restart to 0
global pause, start
pause = False
start = time()
def toggle_pause():
global pause, start, time_when_paused
pause = not pause
if pause:
time_when_paused = time() # update the time displayed
else:
start += time() - time_when_paused # forget the time passed in pause
def timer():
while True:
if pause:
label['text'] = '%.2f' %(time_when_paused - start) # %.2f for 2 decimals
label['fg'] = 'orange'
else:
label['text'] = '%.2f' %(time() - start)
label['fg'] = 'green'
label = Label(tk, '', font=('Helvetica', 30), fg='orange')
label.grid(columnspan=2) # display the timer
# buttons
restart_button = Button(tk, text='Restart', width=20, command=restart)
restart_button.grid(padx=5, pady=5)
pause_button = Button(tk, text='Pause', width=20, command=toggle_pause)
pause_button.grid(padx=(0, 5), column=1, row=1)
timer_thread = Thread(target=timer)
timer_thread.start() # put in a thread because of...
tk.mainloop() # ...this which acts like "while window is not closed"

Python Program Breaks After 1 Run

Thanks so much for Helping, It's fixed now thanks to you guys.
The problem was after I got my cps count the program delayed and froze. It stopped showing the cps.
I can't find out why this happens or how to fix it. The program is a cps counter, it works after 1 try then it breaks and freezes if I use it a second time.
I also have no idea where the code breaks, thank you so much.
Here is the code:
import threading
from tkinter import *
import time
clicks = 0
cps = 0
start = True
wn = Tk()
wn.title("Cps Counter")
wn.geometry("400x300")
def cps5():
global start
global cps
global clicks
global t1
start = False
time.sleep(5)
cps = (clicks / 5)
clicks = 0
CpsButton.config(text=clicks)
CpsAmount.config(text=cps)
start = True
time.sleep(.1
t1 = threading.Thread(target=cps5)
t1.start()
def AddClicks():
global clicks
global start
clicks += 1
CpsButton.config(text=clicks)
if start == True:
cps5()
CpsButton = Button(wn, text=clicks, command=AddClicks, pady=15, padx=15)
CpsAmount = Label(wn, text=cps, pady=15, padx=15)
CpsAmount.pack()
CpsButton.pack()
wn.mainloop()
import threading
from tkinter import *
import time
clicks = 0
cps = 0
start = True
wn = Tk()
wn.title("Cps Counter")
wn.geometry("400x300")
clicks = 0
def cps5():
global start
global cps
global clicks
global t1
start = False
CpsButton.config(text=clicks)
start = True
def AddClicks():
global clicks
global start
clicks += 1
CpsButton.config(text=clicks)
if start == True:
cps5()
CpsButton = Button(wn, text=clicks, command=AddClicks, pady=15, padx=15)
CpsAmount = Label(wn, text=cps, pady=15, padx=15)
CpsAmount.pack()
CpsButton.pack()
def Timer():
global CpsAmount, cps, clicks
for i in range(0, 5):
time.sleep(1)
cps = (clicks / 5)
CpsAmount.config(text=cps)
clicks = 0
Timer()
t1 = threading.Thread(target=cps5)
t1.start()
t2 = threading.Thread(target=Timer)
t2.start()
wn.mainloop()
I created a second thread for the counter, because time.sleep freezes the program and caused trouble.

Python While loop within mainloop causing lag

When I run the code with the while True: loop included within the root.mainloop() it is making my GUI lag heavily and the code does not run as smoothly as I would like. I am wondering how I make my code run smooth and without lag.
for the purpose of this test I have commented out large sections of the code that only work when it is hooked up to my raspberry pi.
Thank you in advance for your help.
import os
import glob
import time
#import RPi.GPIO as GPIO
from datetime import datetime
from tkinter import *
'''
#Set gpio's
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT)#RED
GPIO.setup(22,GPIO.OUT)#GREEN
GPIO.setup(27,GPIO.OUT)#BLUE
#grab temp probe information
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'
# Read temperature from device
def read_temp_raw():
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines
def read_temp():
lines=read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.1)
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000
#temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_c#, temp_f
'''
temp = 18
desiredtemp = 17
deg = u'\xb0'#utf code for degree
#increase button press
def increase():
global desiredtemp
desiredtemp += 0.5
tmpstr.set("%s" % desiredtemp)
#Decrease button press
def decrease():
global desiredtemp
desiredtemp -= 0.5
tmpstr.set("%s" % desiredtemp)
#Tkinter start
root = Tk()
root.wm_title("Temp") #Name the title bar
#code to add widgets will go here....
#make 3 frames for text and buttons
topFrame = Frame(root)
topFrame.pack(side=TOP)
middleFrame = Frame(root)
middleFrame.pack()
bottomFrame = Frame(root)
bottomFrame.pack(side=BOTTOM)
tmpstr = StringVar(value="%s" % desiredtemp)
crtmpstr = StringVar(value="%s" % temp)
#Set labels
label1 = Label(topFrame, text="Desired Temp = ", fg="black")
label2 = Label(middleFrame, text="Actual Temp = ", fg="black")
label3 = Label(topFrame, textvariable=tmpstr, fg="black")
label4 = Label(middleFrame, textvariable=crtmpstr, fg="black")
#use to put labels on screen
label1.pack(side=LEFT)
label2.pack(side=LEFT)
label3.pack(side=LEFT)
label4.pack(side=LEFT)
#Set buttons
button1 = Button(bottomFrame, text="Increase (0.5"+ deg +"C)", fg="black", command=increase)
button2 = Button(bottomFrame, text="Decrease (0.5"+ deg +"C)", fg="black", command=decrease)
#use to put buttons on screen
button1.pack(side=LEFT)
button2.pack(side=LEFT)
#Tkinter End
# Open file to be logged
'''
file = open("/home/pi/Desktop/Templog.csv", "a")
if os.stat("/home/pi/Desktop/Templog.csv").st_size == 0:
file.write("Date, Time, TemperatureSensor1\n")
'''
# Continuous print loop
while 1:
print(temp)
if(temp<=desiredtemp):
#GPIO.output(17,GPIO.LOW)
#GPIO.output(22,GPIO.HIGH)
temp += 5
crtmpstr.set("%s" % temp)
else:
#GPIO.output(17,GPIO.HIGH)
#GPIO.output(22,GPIO.LOW)
temp -=0.5
crtmpstr.set("%s" % temp)
#now = datetime.now()
#file.write(str(now.day)+"-"+str(now.month)+"-"+str(now.year)+","+str(now.hour)+":"+str(now.minute)+":"+str(now.second)+","+str(read_temp())+"\n")
#file.flush()
time.sleep(1)
root.update()
root.mainloop()
Simply use the after method of TK object. This will not impact redrawing and will not require calling any manual update functions, as it defers the execution of that code until the gui thread is not busy.
Split the code to be executed independently into a separate function and pass it to root.after along with a time delay. The first time I used a delay of 0 so it executes immediately. Then at the end of the function call it again, this time passing the value 1000 (milliseconds) as a delay. It will execute repeatedly until you end the tkinter app.
# ... other code here
def gpiotask():
global temp
print(temp)
if(temp <= desiredtemp):
GPIO.output(17, GPIO.LOW)
GPIO.output(22, GPIO.HIGH)
temp += 5 # <- did you mean 0.5 here ?
crtmpstr.set("%s" % temp)
else:
GPIO.output(17, GPIO.HIGH)
GPIO.output(22, GPIO.LOW)
temp -= 0.5
crtmpstr.set("%s" % temp)
root.after(1000, gpiotask)
root.after(0, gpiotask)
root.mainloop()

Update time for recording

I doing a simple python GUI using tkinter to do screen recording.Basically, I am using ffmpeg commands at the backend with tkinter as the front end triggering the ffmpeg commands.There is something that I stuck with.I dont know why my time is unable to trigger off if I program in this way.
The code below is basically the recording method.You will notice that I am actually trying to update my tkinter GUI in the while loop.This method is actually in my class named Gui_Rec() which contains other methods I need for my screen recording program.
def rec(self):
global videoFile
mydate = datetime.datetime.now()
videoFile = mydate.strftime("\%d%b_%Hh%Mm.avi")
self.l['text']=os.path.expanduser('~')+"\Videos"
self.l1['text']=videoFile
self.b.config(state=DISABLED)
self.b1.config(state=ACTIVE)
t = Thread(target=self.rec_thread)#trigger another method using thread which will run ffmpeg commands here
t.start()
while True:
if self.count_flag == False:
break
self.label['text'] = str("%02dm:%02ds" % (self.mins,self.secs))
if self.secs == 0:
time.sleep(0)
else:
time.sleep(1)
if(self.mins==0 and self.secs==1):
self.b1.config(fg="white")
self.b1.config(bg="red")
self.b.config(fg="white")
self.b.config(bg="white")
if self.secs==60:
self.secs=0
self.mins+=1
self.label['text'] = str("%02dm:%02ds" % (self.mins,self.secs))
main.gui.update()
self.secs = self.secs+1
other method in the class Gui_Rec() then this below
def main():
gui = Gui_Rec()
gui.minsize(300,155)
gui.maxsize(390,195)
gui.title("Desktop REC")
gui.attributes("-topmost", 1)
gui.mainloop() #start mainloop of program
if __name__ == '__main__':
main()
Strangely, if I don't put the above section of code in the the def main(), the GUI will be update with the duration of the time running when rec button is pressed.I don't really know how to go about solving this.Tried putting it in another thread yet it doesn't work as well.Thank you everyone for your help.
The while loop is creating a conflict with Tkinter's mainloop. Threading or multiprocessing are solutions, but I'd recommend looking into Tkinter's after() method. Here's a simplified example of how to handle a timer using after:
from Tkinter import *
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.mins = 0
self.secs = 0
# make a stringvar instance to hold the time
self.timer = StringVar()
self.timer.set('%d:%d' % (self.mins, self.secs))
Label(self, textvariable=self.timer).pack()
Button(self, text='Start', command=self._start_timer).pack()
Button(self, text='Stop', command=self._stop_timer).pack()
def _start_timer(self):
self.secs += 1 # increment seconds
if self.secs == 60: # at every minute,
self.secs = 0 # reset seconds
self.mins += 1 # and increment minutes
self.timer.set('%d:%d' % (self.mins, self.secs))
# set up the after method to repeat this method
# every 1000 ms (1 second)
self.repeater = self.after(1000, self._start_timer)
def _stop_timer(self):
self.after_cancel(self.repeater)
root = Tk()
App(root).pack()
mainloop()

Categories

Resources