Python While loop within mainloop causing lag - python

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()

Related

Python tKinter: How to pause countdown timer

I made a countdown timer that starts whenever I press on the space key on my keyboard, but the problem is that I can't do anything on the program until the timer ends. I want make it pause when the space key is pressed a second time.
The countdown timer that I made works in a while loop that ends when the timer reach 0, so the program waits until the loop ends before doing anything else, even if I want to stop the timer I can't do it while it's running.
Here's the code
from tkinter import *
from tkinter import ttk
import tkinter as tk
from PIL import ImageTk, Image
def StartTimer():
if (root.turn % 2) == 0: #Turn to white
root.number = '1'
root.color = 'white'
else: #Turn to black
root.number = '2'
root.color = 'black'
doTimer()
def doTimer():
root.time = root.minute *60 + root.second
root.turn = root.turn+1
number = root.number
root.rndsquare1.configure(image=root.green)
root.timer1.configure(bg='#1C953D')
root.white.configure(bg='#1C953D')
r=0
while r < root.time:
root.update_idletasks()
root.after(1000)
root.second = root.second - 1
if root.second == -1:
root.minute = root.minute -1
root.second = 59
root.time1 = ''
if len(str(root.minute)) == 1:
root.time1 = '0' + str(root.minute)
else:
root.time1 = str(root.minute)
if len(str(root.second)) == 1:
root.time1 = root.time1 + ':' + '0' + str(root.second)
else:
root.time1 = root.time1 + ':' + str(root.second)
root.timer1.configure(text=root.time1)
r=r+1
root.timer1.configure(bg='#454545')
root.white.configure(bg='#454545')
root.rndsquare1.configure(image=root.grey)
class root(Tk):
def __init__(self):
super(root, self).__init__()
self.title("Chess Clock")
self.minsize(1539,600)
self.windowBG = '#313131'
self.state('zoomed')
self.configure(bg=self.windowBG)
self.CreateWindow()
def CreateWindow(self):
self.grey = ImageTk.PhotoImage(Image.open(r"D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square grey.png"))
self.green = ImageTk.PhotoImage(Image.open(r"D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square green.png"))
self.turn=0
self.rndsquare1 = Label(self, image=self.grey, borderwidth=0)
self.rndsquare1.place(x=65, y=120)
self.rndsquare2 = Label(self, image=self.grey, borderwidth=0)
self.rndsquare2.place(x=809, y=120)
self.bind('<space>',lambda event:StartTimer())
self.createTimers()
def createTimers(self):
self.minute = 1
self.second = 5
self.time1 = ''
if len(str(self.minute)) == 1:
self.time1 = '0' + str(self.minute)
else:
self.time1 = str(self.minute)
if len(str(self.second)) == 1:
self.time1 = self.time1 + ':' + '0' + str(self.second)
else:
self.time1 = self.time1 + ':' + str(self.second)
self.time2 = ''
if len(str(self.minute)) == 1:
self.time2 = '0' + str(self.minute)
else:
self.time2 = str(self.minute)
if len(str(self.second)) == 1:
self.time2 = self.time2 + ':' + '0' + str(self.second)
else:
self.time2 = self.time2 + ':' + str(self.second)
self.timer1 = Label(self, text=self.time1, bg='#454545', fg='white', font ="Gadugi 40 bold")
self.timer1.place(x=330, y=420)
self.timer2 = Label(self, text=self.time2, bg='#454545', fg='white', font ="Gadugi 40 bold")
self.timer2.place(x=1080, y=420)
self.white = Label(self, text='White', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.white.place(x=325, y=160)
self.black = Label(self, text='Black', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.black.place(x=1075, y=160)
root=root()
root.mainloop()
D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square grey.png
D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square green.png
You can solve this by heavily refacturing your code. You can add 2 clocks to your widget, each clock tracks how much is spent on itself. The spacebar listener simply switches between which clock is currently in use. By also having a timed do_clock_logic every 200ms or so it checks if a current clock is set, if so if the time is up and if that is the case, switch over to the other clock. In any case it will trigger the clocks tick() method to update its internal states that also handle ui updates.
This way there is no "blocking" while loop and all timing stuff is handled by tk:
from tkinter import Tk, Label
import tkinter as tk
from PIL import ImageTk, Image
from datetime import datetime, timedelta
class clock():
"""A single clock that handles updating/timekeeping itself. It uses
the both class-level memebrs as active/inactive image and has
references provided to place the image and the timing text."""
active_img = None
deactive_img = None
#staticmethod
def format_time(delta, ms = False):
"""Returns a formatted strng for a timedelta instance,
optionally with milliseconds"""
return f"{delta.seconds//60:02}:{delta.seconds%60:02}" + (
f".{(delta.microseconds // 1000):04}" if ms else "")
def __init__(self, minutes, seconds, bg_lbl, text_lbl):
"""Set the clocks max duration providing 'minutes' and 'seconds'.
Provide tk-labels with a background image 'bg_lbl' and
'text_lbl' for the time display."""
self.max_duration = timedelta(seconds=seconds+minutes*60)
# UI
self.bg_lbl = bg_lbl,
self.text_lbl = text_lbl
# reset to inactive image and no text
self.bg_lbl[0].config(image = clock.deactive_img)
self.text_lbl.config(text = "")
# internal time keeping of total spent time1
self.total = timedelta() # 0 delta at start
def update_lbl(self, spent):
# update the image if needed
self.bg_lbl[0].config(image = clock.active_img if self.started is not None else clock.deactive_img)
# update labels - if not active - show with milliseconds
if self.started is not None:
self.text_lbl.config( text = clock.format_time(self.max_duration - spent))
else:
self.text_lbl.config(text = f"Total:\n{clock.format_time(self.total, True)}")
def start_clock(self):
# starts the clock
self.started = datetime.now()
self.update_lbl(timedelta())
def tick(self):
# ticks the clock - stops it if time has run out
if self.started is not None:
spent = datetime.now() - self.started
if spent > self.max_duration:
self._stop_clock(spent)
return False
self.update_lbl(spent)
return True
return None
def stop_clock(self):
# stop clock from the outside if <space> is hit
if self.started is not None:
spent = datetime.now() - self.started
self._stop_clock(spent)
def _stop_clock(self, spent):
# internal method that stops the clock, adds total & updates
spent = min(spent, self.max_duration) # fix it
self.total += spent
self.started = None
self.update_lbl(None)
class root(Tk):
def __init__(self):
super(root, self).__init__()
self.title("Chess Clock")
self.minsize(1539,600)
self.windowBG = '#313131'
self.state('zoomed')
self.configure(bg=self.windowBG)
self.CreateWindow()
def CreateWindow(self):
self.grey = ImageTk.PhotoImage(Image.open(r"grey.png"))
self.green = ImageTk.PhotoImage(Image.open(r"green.png"))
# used to determine player
self.turn = 0
# give the clock class the two images to switch
# if changing between active/inactive state
clock.deactive_img = self.grey
clock.active_img = self.green
# one clocks UI
self.white_bg = Label(self, image=self.grey, borderwidth=0)
self.white_bg.place(relx=.3, rely=.55, anchor="center")
self.white = Label(self, text='White', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.white.place(relx=.3, rely=.2, anchor="center")
self.white_timer = Label(self.white_bg, text="", bg='#454545', fg='white', font ="Gadugi 40 bold")
self.white_timer.place(relx=.5, rely=.5, anchor="center")
# seconds clock UI
self.black_bg = Label(self, image=self.grey, borderwidth=0)
self.black_bg.place(relx=.7, rely=.55, anchor="center")
self.black = Label(self, text='Black', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.black.place(relx=.7, rely=.2, anchor="center")
self.black_timer = Label(self.black_bg, text="", bg='#454545', fg='white', font ="Gadugi 40 bold")
self.black_timer.place(relx=.5, rely=.5, anchor="center")
# provide the background-label and the text label
# for time and create two clocks for the players
self.clock1 = clock(1, 5, self.white_bg, self.white_timer)
self.clock2 = clock(1,5, self.black_bg, self.black_timer)
# which clock is currently in use?
self.who_is_it = None
# handles switching to next players clock
self.bind('<space>', lambda _: self.next_player())
self.bind('<Control-Key-q>', lambda _: self.stop())
# check every 200ms if clocks need to be switched over
self.after(200, self.do_clock_logic)
def do_clock_logic(self):
# do nothing if no clock startet
# check if clock has run out, then switch to next players clock
if self.who_is_it is not None:
# tick() returns False if the player spent all his time
# tick() returns True if the player still has time
# tick() returns None if clock is not yet started
if self.who_is_it.tick() == False:
self.next_player()
# recheck clocks in 200ms
self.after(200, self.do_clock_logic)
def stop(self):
"""First Ctrl+q will stop clocks, second will quit."""
if self.who_is_it is not None:
self.who_is_it.stop_clock()
self.who_is_it = None
self.do_clock_logic = lambda _: None is None
else:
self.destroy()
def next_player(self):
if self.who_is_it is not None:
self.who_is_it.stop_clock()
self.turn += 1
# player 1 on "odd turns", player 2 on "even turns"
self.who_is_it = self.clock1 if self.turn % 2 else self.clock2
self.who_is_it.start_clock()
root=root()
root.mainloop()
to get
after the first CTRL+q you'll get the results - a second time CTRL+q closes your window:
This can be better structured regarding UI/logic stuff - but it works as proof of concept.

How to collect data only when I click the start button using Tkinter and py Serial?

I have an USB GPS sensor and I'm trying to do a human/machine interface where the user click on the Start button and the data start collecting. To do that, I helped myself with this code http://robotic-controls.com/learn/python-guis/tkinter-serial.
I managed to get the data and have it on screen only when the user click on start button but I saw that when I click the button, lots of previous datas are displayed on the screen. My sensor provide me data each second, when I run my program, if I wait 5 seconds before clicking on the start button, I will have 5 rows displayed on the screen when I click on it.
Here is my code :
import serial
import tkinter
from serial import *
from tkinter import *
gps = list()
serialPort = "COM12"
baudRate = 4800
ser = Serial(serialPort , baudRate, timeout=0, writeTimeout=0)
root = Tk()
root.wm_title("Signal GPS")
etat = StringVar()
etat.set("Démarrer acquisition")
def StartStop():
j=0
if etat.get() == "Arreter acquisition" :
etat.set("Reprendre acquisition")
j=1
if (etat.get() == "Démarrer acquisition" or etat.get() == "Reprendre acquisition") and j == 0 :
etat.set("Arreter acquisition")
readSerial()
def quitter():
root.destroy()
valeurs = Canvas(root)
valeurs.grid(column=0,row=0, padx=10)
texte_lati = Label(valeurs, text='Latitude')
texte_lati.grid(column=0, row=0)
lati = Text ( valeurs, width=20, height=20, takefocus=0)
lati.grid(column=0, row=1)
texte_longi = Label(valeurs, text='Longitude')
texte_longi.grid(column=1, row=0)
longi = Text ( valeurs, width=20, height=20, takefocus=0)
longi.grid(column=1, row=1)
start = Button(root, textvariable=etat, text=etat, command=StartStop)
start.grid(column=0,row=2)
quitte = Button(root, text='Quitter', command=quitter)
quitte.grid(column=1,row=2)
serBuffer = ""
def readSerial():
while etat.get() == "Arreter acquisition" :
c = ser.read().decode("latin1")
if len(c) == 0:
break
global serBuffer
if c == '\r':
c = ''
if c == '\n':
if serBuffer[0:6] == '$GPGGA' :
gps.append(serBuffer.split(','))
serBuffer += "\n"
if gps[-1][2] == '' :
lati.insert('0.0', 'Pas de signal GPS\n')
else :
lati.insert('0.0', str(gps[-1][2]) + '\n')
if gps[-1][4] == '' :
longi.insert('0.0', 'Pas de signal GPS\n')
else :
longi.insert('0.0', str(gps[-1][4]) + '\n')
serBuffer = ""
else:
serBuffer += c
root.after(100, readSerial)
root.mainloop()
ser.close()
Can anyone explain me why I'm getting all the previous datas ? I expected that using while etat.get() == "Arreter acquisition" : will not store the data but it actually does...

make a window flash like wrning signal light specific number of times and carry out some function after it

i want to make a notifying application in python using tkinter.
i want to check the system time if it matches the time from database time field of any row a window show flash on and off some specific number of times like danger warning lights.after the flashes finished those number of times a window should come showing about the events/scheduled that are to notified to the user.
i have tried to write the code but it makes the window appear only once.
i have tried using the root.after method of tkinter but dont know how to implement the requirements i want in the application.
#repeatedly checking if system time matched any teachers scedule time--------------------------------
def repeatedquery():
def alertmsg():
for j in range (4):
newteacheradd.withdraw()
messagewindow = Toplevel()
#to come out press alt+escape
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification")
msgflash = Label(messagewindow, text='Notice', bg="#1f618d", fg='white', font=("Verdana", 28, 'bold'),
height=5,width=40, relief=RAISED)
msgflash.place(x=150, y=300)
print ("hjgj")
time.sleep(10)
messagewindow.after(15000, messagewindow.withdraw)
messagewindow.mainloop()
def showMesagewin():
newteacheradd.withdraw()
global messagewindow
messagewindow = Toplevel()
# Covers whole screen to come out press Alt + Esc
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification Screen")
tt = '{:3}'.format(str(i[0])) + ' {:15}'.format(str(i[1])) + ' {:15}'.format(
str(i[2])) + ' {:15}'.format(str(i[3])) + '\n'
msg = 'YOUR LECTURE/PRAC DETAILS \n\n'+" "+tt
# to place message at centre
msgflash = Label(messagewindow, text=msg, bg="#1f618d",fg='white', font=("Verdana", 28,'bold'),height=5,relief = RAISED)
msgflash.place(x=250, y=300)
#Bell rings ___________________________________________________________________________________________
pygame.init()
pygame.mixer.init()
sounda = pygame.mixer.Sound("bell.wav")
sounda.play()
time.sleep(5)
#Belll rings____________________________________________________________________________________________
messagewindow.after(5000, messagewindow.withdraw)
# schedule closing of showMesagewin event in 5 seconds
messagewindow.mainloop()
currentDT = datetime.datetime.now()
t=currentDT.strftime("%H")
conn = sq.connect("Teacher.db")
curr = conn.cursor()
tid = __userip.get()
day2 = datetime.datetime.now()
day3 = day2.strftime("%A")
curr.execute("select Time,Subject,Lecture_prac,Venue from "+day3+" where Teacher_ID=?", (tid,))
sc = curr.fetchall()
t1=0
for i in sc:
if (t == i[0]):
alertmsg()
print ('gbhj')
showMesagewin()
newteacheradd.after(1000,repeatedquery)
#-------------------------------------------------------------------------------------------------
repeatedquery()
Edited Code
#repeatedly checking if system time matched any teachers scedule time--------------------------------
from tkinter import *
import datetime
import time
def mainwin():
newteacherappend=Tk()
i=[]
i.append(2)
i.append('Phy')
i.append('L')
i.append('301')
def repeatedquery():
def alertmsg():
for j in range (4):
newteacherappend.withdraw()
messagewindow = Toplevel()
# Covers whole screen to come out press Alt + Esc
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification")
msgflash = Label(messagewindow, text='Notice', bg="#1f618d", fg='white', font=("Verdana", 28, 'bold'),
height=5,width=40, relief=RAISED)
msgflash.place(x=150, y=300)
print ("hjgj")
time.sleep(5)
messagewindow.after(5000, messagewindow.withdraw)
messagewindow.mainloop()
def showMesagewin():
newteacherappend.withdraw()
global messagewindow
messagewindow = Toplevel()
# Covers whole screen to come out press Alt + Esc
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification Screen")
tt = '{:3}'.format(str(i[0])) + ' {:15}'.format(str(i[1])) + ' {:15}'.format(
str(i[2])) + ' {:15}'.format(str(i[3])) + '\n'
msg = 'YOUR LECTURE/PRAC DETAILS \n\n'+" "+tt
# to place message at centre
msgflash = Label(messagewindow, text=msg, bg="#1f618d",fg='white', font=("Verdana", 28,'bold'),height=5,relief = RAISED)
msgflash.place(x=250, y=300)
messagewindow.after(5000, messagewindow.withdraw)
# schedule closing of showMesagewin event in 5 seconds
messagewindow.mainloop()
currentDT = datetime.datetime.now()
t=currentDT.strftime("%H")
t=2
if (t == i[0]):
alertmsg()
print ('gbhj')
showMesagewin()
newteacherappend.after(1000,repeatedquery)
repeatedquery()
newteacherappend.mainloop()
mainwin()
I made code which use after without sleep to display flashing messages - first after after 5 seconds, second after 15 second. And it has only one mainloop().
More information in code
import tkinter as tk
import datetime
# --- functions ----
# function which creates window with message
def message_start(text):
global repeates
global message_window
global message_label
repeates = 3 # how many times change background color
# create window with messages
message_window = tk.Toplevel()
message_label = tk.Label(message_window, text=text, bg='red')
message_label.pack()
# update window after 500ms
root.after(500, message_update)
# function which changes background in displayed window
def message_update():
global message_window
global repeates
if repeates > 0:
repeates -= 1
if message_label['bg'] == 'red':
message_label['bg'] = 'green'
else:
message_label['bg'] = 'red'
# update window after 500ms
root.after(500, message_update)
else:
# close window
message_window.destroy()
# inform `check_time` that window is not busy
message_window = None
# loop which updates current time and checks which message it has to display
def check_time():
# display current time
current_time = datetime.datetime.now()
root_label_time['text'] = current_time.strftime('%Y.%m.%d %H:%M:%S')
# check if there is message to display
for message in messages:
if current_time >= message['start']: # it is time to display message
if message['state'] == 'waiting': # message is not displayed at this moment
if message_window is None: # window is not busy
message['state'] = 'displayed' # don't display it again
message_start(message['text']) # display message
# update time after 1000ms (1s)
root.after(1000, check_time)
# --- main ---
messages = [
{
'text': 'Time for coffee',
'start': datetime.datetime.now() + datetime.timedelta(seconds=5),
'state': 'waiting',
},
{
'text': 'Back to work',
'start': datetime.datetime.now() + datetime.timedelta(seconds=15),
'state': 'waiting',
},
]
message_window = None
repeates = 3
# ---
root = tk.Tk()
# label with current time
root_label_time = tk.Label(root, text='- wait -')
root_label_time.pack()
# label with sheduler
root_label_sheduler = tk.Label(root)
root_label_sheduler.pack()
# displa messages in sheduler
for message in messages:
root_label_sheduler['text'] += "\n" + message['start'].strftime('%Y.%m.%d %H:%M:%S ') + message['text']
# start displaying time
check_time()
root.mainloop()

Function to check distance using ultrasonic sensor freezes tkinter

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()

TkInter Command makes Python crash when using lambda

I have a problem using lambda: ... in my program. As I don't want my TkInter GUI to execute the functions automatically, I am using lambda. Problem is though that for Button3 it makes my program crash and I have no idea why. Without lambda it works absolutely fine. Here is my code:
import ogr, osr, sys, os
import psycopg2
import ppygis
from datetime import datetime, date
import time
import re
from Tkinter import *
import tkFileDialog
from tkFileDialog import askopenfilename # Open dialog box
from tkMessageBox import showerror
class trip_calculator:
def __init__(self):
global root
root = Tk()
def open_file_dialog():
returned_values = {}
returned_values['filename'] = askopenfilename()
Label(root, text= returned_values.get('filename')[52:] + ' selected').grid(row=2)
filepath = returned_values.get('filename')
#OPEN GPX DRIVER
driver = ogr.GetDriverByName('GPX')
datasource = driver.Open(filepath)
if datasource is None:
print 'not open'
else:
print 'open'
#GEOMETRY
datasource_layer = datasource.GetLayer(2)
#GRAB TIMESTAMPS, ELEVATION, CADENCE ETC.
datasource_layer2 = datasource.GetLayer(4)
#GRAB GEOMETRY INFORMATION AND TRANSFORM TO UTM
datasource_feature = datasource_layer.GetNextFeature()
geoSR = osr.SpatialReference()
geoSR.ImportFromEPSG(4326)
utmSR = osr.SpatialReference()
utmSR.ImportFromEPSG(32633)
coordTrans = osr.CoordinateTransformation(geoSR, utmSR)
geom = datasource_feature.GetGeometryRef()
geom1 = geom.Simplify(0)
geom.Transform(coordTrans)
geom1.Transform(coordTrans)
Label(root, text= 'geometries transformed successfully').grid(row=2, column=5)
#
# This is where the crash of Python occurs,
# `lambda: calculation(...)` won't start.
# It crashes at `features = iter(datasource_layer2)`
#
self.button3 = Button(root, text='calculate attributes', command=lambda:calculation(self,geom1,datasource_layer2)).grid(row=10, column=10, pady=10, padx=10)
def quit_me():
root.quit()
def calculation(self, geom1, datasource_layer2):
#NET AND GROSS TIME CALCULATION
timestamps_net = []
timestamps_net_helper = []
timestamps_elapsed = []
elevation_helper = []
print datasource_layer2
features = iter(datasource_layer2)
next(features)
for feature in features:
if len(timestamps_net_helper) == 2:
timestamps_net_helper = timestamps_net_helper[-1:]
timestamp = feature.GetField(4)
elevation = feature.GetField(3)
elevation_helper.append(elevation)
timestamp_stripped = timestamp[:-3]
day = timestamp[:-11]
#FOR GROSS CALCULATION
timestamps_elapsed.append(timestamp_stripped)
#FOR NET CALCULATION
timestamps_net_helper.append(timestamp_stripped)
if len(timestamps_net_helper) == 2:
#CALCULATE SECONDS BETWEEN
time_a = datetime.strptime(timestamps_net_helper[0], "%Y/%m/%d %H:%M:%S")
time_b = datetime.strptime(timestamps_net_helper[1], "%Y/%m/%d %H:%M:%S")
time_difference = time.mktime(time_b.timetuple()) - time.mktime(time_a.timetuple())
#IF SECONDS LESS THAN 20 BETWEEN GPS TIMESTAMP THEN ADD UP NET TIME
if time_difference < 20:
timestamps_net.append(time_difference)
seconds = sum(timestamps_net)
hours = seconds/60/60
time_length_net = time.strftime('%H:%M:%S', time.gmtime(seconds))
#CLIMB.....
positive_climb = []
negative_climb = []
for a, b in zip(elevation_helper, elevation_helper[1:]):
if a > 0.0 and b > 0.0:
if b > a:
positive_climb.append(b-a)
elif b == a:
pass
else:
negative_climb.append(a-b)
positive_climb = sum(positive_climb)
negative_climb = sum(negative_climb)
#GROSS (ELAPSED TIME)
start = datetime.strptime(timestamps_elapsed[0], "%Y/%m/%d %H:%M:%S")
end = datetime.strptime(timestamps_elapsed[-1], "%Y/%m/%d %H:%M:%S")
time_length = end - start
#LENGTH
length_km = float(geom1.Length()/1000)
#AVERAGE SPEED
avg_speed = (geom1.Length()/1000)/hours
#CREATE LINESTRING FOR PPYGIS AND OGR LINESTRING
myLine = ogr.Geometry(ogr.wkbLineString)
polyline = []
for z in range(geom1.GetPointCount()):
x = geom1.GetX(z)
y = geom1.GetY(z)
myLine.AddPoint(x, y)
point = ppygis.Point(x, y)
polyline.append(point)
myLine_ppy = ppygis.LineString(polyline)
Label(root, text= time_length).grid(row=10, column=5)
Label(root, text= length_km).grid(row=11, column=5)
Label(root, text= avg_speed).grid(row=12, column=5)
self.button1 = Button(root, text='browse', command= open_file_dialog).grid(row=0,pady=10, padx=25)
self.button2 = Button(root, text='close', command= quit_me).grid(row=3, pady=10, padx=25)
root.mainloop()
trip_calculator()
The error occuring is libc++abi.dylib: pure virtual method called but only using lambda in the command of button3. Any ideas how to fix this?
The problem is likely due to the fact that some of the arguments you have in thelambdaexpression -- namelygeom1anddatasource_layer2-- are variables local to the nestedopen_file_dialog() function and don't exist later when the button is pressed and it has returned.
A simple fix would be to make them attributes of the trip_calculator instance, by adding a self.datasource_layer2 = datasource_layer2andself.geom1 = geom1 statements somewhere before the function returns (or just assigning them toselfand referencing them that way everywhere else).

Categories

Resources