I'm currently trying to build a security system on my raspberry pi. I have 9 buttons to enter a code. Once the code is entered u can press "arm" to arm the system. Then a function checks a PIR sensor for movement. When movement is detected an alarm should go off, for which I need time.sleep.
So my actual problem is, with time.sleep I block the programm for the time its sleeping, thus i cant disarm the system during alarm mode.
My idea so far was just to put everything into threads. But without success so far. Are there any better solutions to time.sleep?
You don't need to use threads. With tkinter you can easily schedule a function to run every couple of seconds in the main thread to check the sensor or do any other work that you want.
Here's a short contrived example, which will work just fine assuming that the sensor check doesn't take more than a couple hundred milliseconds. It's not exactly how I would do it, but it illustrates how you can have some function run periodically without having to put the UI to sleep.
import tkinter as tk
class App():
def __init__(self):
self._job_id = None
self.init_gui()
def init_gui(self):
self.root = tk.Tk()
self.button = tk.Button(self.root, width=6, text="Arm", command=self.arm)
self.button.pack(padx=20, pady=20)
def start(self):
self.root.mainloop()
def arm(self):
self.button.configure(text="Disarm", command=self.disarm)
self.poll()
def disarm(self):
self.button.configure(text="Arm", command=self.arm)
if self._job_id:
self.root.after_cancel(self._job_id)
def poll(self):
# ... check the sensor here ...
movement = True
if movement:
print("beep!")
self._job_id = self.root.after(2000, self.poll)
app = App()
app.start()
If your problem is simply looking for a better method to use then time.sleep, you could consider using time.time rather than time.sleep and then use checks to see what actions should occur. This would avoid using time.sleep which blocks all events (including your GUI). As a quickly written example to demonstrate this idea:
from time import time
millis = lambda: int(time() * 1000)
def updateAlarm(lastTime, beepRate, currentState):
now = millis()
if now > (lastTime + beepRate):
return (not(currentState), now)
return (currentState, now)
last = millis() #set the first time for the alarm
rate = 2000 # change state every two seconds
state = False # the alarm is currently off
while True: # just for demonstration purposes, while True: won't work inside of a tkinter GUI
change = updateAlarm(last, rate, state)
if change[0] != state: # if the state has changed, update it and print
state = change[0]
last = change[1]
print(state)
Depending on your implementation, this may make more sense, especially if you are not using tkinter. Personally, I think Bryan's solution is much more elegant, especially since it doesn't require constantly checking the alarm to see if it needs to update.
Related
I created an example code because my original is too big and has private information(My own) in it.
While running a program from a Tkinter GUI, it runs the program but makes the GUI unresponsive because of time.sleep() blocking the GUI from updating.
I am trying to avoid using timers because it fires a different function after a duration instead of simply pausing the function and then continuing the same function.
Is there an alternative that does not block the GUI but still adds a delay inside of the function?
Example Code:
from tkinter import *
import time
wn = Tk()
wn.geometry("400x300")
MyLabel = Label(wn, text="This is a Status Bar")
MyLabel.pack()
def MyFunction():
Value = 1
while Value < 10:
print("Do something")
time.sleep(1) **# - here blocks everything outside of the function**
MyLabel.config(text=Value)
# A lot more code is under here so I cannot use a timer that fires a new function
Value = 1
MyButton = Button(wn, text="Run Program", command=MyFunction)
MyButton.pack()
wn.mainloop()
Edit: Thanks so much, you're answers were fast and helpful, I changed the code and added "wn.mainloop()" after the delay and replaced "time.sleep(1)" with wn.after(100, wn.after(10, MyLabel.config(text=Value))
here is the final code:
from tkinter import *
import time
wn = Tk()
wn.geometry("400x300")
MyLabel = Label(wn, text="This is a Status Bar")
MyLabel.pack()
def MyFunction():
Value = 0
while Value < 10:
print("Do something")
wn.after(10, MyLabel.config(text=Value))
Value += 1
wn.mainloop()
MyButton = Button(wn, text="Run Program", command=MyFunction)
MyButton.pack()
wn.mainloop()
The short answer is that you can use wn.after() to request a callback after a certain amount of time. That's how you handle it. You get a timer tick at a one-per-second rate, and you have enough state information to let you proceed to the next state, then you go back to the main loop.
Put another way, timers are exactly how you have to solve this problem.
Fundamentally, any callback function in Tkinter runs in the main GUI thread, and so the GUI thread will block until the function exits. Thus you cannot add a delay inside the function without causing the GUI thread to be delayed.
There are two ways to solve this. One would be to refactor your function into multiple pieces so that it can schedule the remaining work (in a separate function) via .after. This has the advantage of ensuring that all of your functions are running in the main thread, so you can perform GUI operations directly.
The other way is to run your function in a separate thread that is kicked off whenever your main callback is executed. This lets you keep all the logic inside the one function, but it can no longer perform GUI operations directly - instead, any GUI operations would have to go through an event queue that you manage from the main thread.
You can combine after() and wait_variable() to simulate time.sleep() without blocking tkinter from handling pending events and updates:
def tk_sleep(delay):
v = wn.IntVar()
# update variable "delay" ms later
wn.after(delay, v.set, 0)
# wait for update of variable
wn.wait_variable(v)
Using tk_sleep() in your while loop:
def MyFunction():
Value = 1
while Value < 10:
print("Do something")
tk_sleep(1000) # waits for one second
MyLabel.config(text=Value)
# A lot more code is under here so I cannot use a timer that fires a new function
Value += 1
I'm making a GUI to play videos using the python bindings for VLC, and I've run into a snag with the progress slider. Due to VLC's extreme inaccuracy and inconsistency when it comes to reporting a video's progress, I've been faking the QSlider by simply incrementing it + updating the rest of the UI (which includes multiple QLabels, QLineEdits, and QSpinBoxes) once per frame in a separate thread (not a QThread).
However, something strange has been happening: eventually, the entire UI "freezes"... except the video continues playing, the console continues outputting, and the UI instantly jumps back to life after manually interacting with it (such as by pressing the pause button). Qt is running and keeping track of the current state of the UI, just without painting anything.
I've tried manually updating the UI, manually repainting the UI (which always crashes...?), auto-resizing the UI, running processEvents(), running QTest.qWait(1), but nothing has worked. Using a QTimer to increment the progress bar, however, DOES prevent the UI from freezing, which confirms I'm not crazy. Of course, I can't actually use a QTimer though, since even a Qt.PreciseTimer results in... a very imprecise timer.
I've read that this is likely caused by Qt mistakenly trying to optimize my code by clumping the rapid updates into one, which would explain why the UI seemingly stops being painted despite clearly being active under the hood.
Though I have a feeling that the solution is probably something obvious (maybe even a different implementation of something I mentioned trying above) that doesn't require a code sample, here's a significantly stripped down version of what I'm currently working with:
def setup(self):
self.progress_thread = Thread(target=self.update_slider_thread, daemon=True)
self.progress_thread.start()
def update_slider_thread(self):
current_frame = self.progress_slider.value
is_playing = self.vlc.player.is_playing
update_progress = self.update_progress
while True:
start = time.time()
while not is_playing():
sleep(0.01)
start = time.time()
while is_playing() and not self.lock_progress_updates:
frame_multiplier = self.frame_rate
next_frame = current_frame() + 1
if next_frame <= self.frame_count: # don't update if we're at the end
update_progress(next_frame)
delay = self.delay # frame rate / total frames
try:
sleep(0.0001) # forces time.time() to update (otherwise we get the same "time" for several loops)
sleep(delay - (time.time() - start) - 0.00075) # 0.00075 to account for executing this line of code
except: pass
finally:
start = time.time()
continue
def update_progress(self, frame):
if self.get_player_state() == vlc.State.Ended:
if self.ready_to_restart and self.vlc.is_paused:
self.restart_video(frame=frame, pause=True)
self.current_time = round(self.duration * (frame / self.frame_count), 2)
h, m, s, ms = get_hms(self.current_time)
current_time_string = f'{m:02}:{s:02}.{ms:02}'
# these are just aliases for things like setValue() for each widget (for performance)
self.set_progress_slider(frame)
if not self.current_time_text_has_focus(): self.set_current_time_text(current_time_string)
self.set_hour_spin(h)
self.set_minute_spin(m)
self.set_second_spin(s)
self.set_frame_spin(frame)
Uhhhhhhhhhhhhhhhhh Ok so some basics. 1st of all, any interaction with widgets have to happen in main thread. So if you are changing slider value/etc. from daemon/worker thread. Then you are messing up Qt insides.
I would suggest that you use signals/slots. Here is a small example of it
class sliderUpdate(QObject):
handleUpdate = Signal(int)
def __init__(self):
print "Current thread (Should be main thread ) : " QThread.currentThread()
# Lets conect our signal to function/slot.
self.handleUpdate.connect(self.doUpdate,Qt.QueuedConnection) # We force it in to QueuedConnection so that Qt pass the data from worker thread to Main thread.
self.slider = QSlider()
def doUpdate(self,val):
print "Current thread (Should be main thread ) : " QThread.currentThread()
self.slider.setValue(val)
def processInThread(self):
print "Current thread (Should be worker thread ) : " QThread.currentThread()
self.handleUpdate.emit(10)
2nd. If VLC is sending updates A LOT per second. Then you may be bombarding Qt with refresh requests & lagging app. I would suggest implementing some sort of... delayed report... A sudo example :
timer = QElapsedTimer()
timer.start()
...
...
if timer.elapsed()>500:
emit new Value:
timer.restart()
else:
skip, emit on next call if timeout over 500 ms.
So I want to know how I can make a delay between executing two functions. The goal is to replace regular, blank button by black after it was on screen for one second. My current program, simplified looks like this, and it just delays the the execution of CreateInterface():
class Program(Frame):
def __init__(self,root):
self.root=root
self.root.title('Test')
super().__init__(self.root)
self.grid()
self.Start()
return
def Start(self):
startbtn=Button(self,width=5, font=('Calibri',16,'bold'), height=2, text='start',command=lambda:self.CreateInterface())
startbtn.grid(row=1,column=1)
def CreateInterface(self):
time.import
btn1=Button()
btn1.grid(row=1,column=1)
time.sleep(10)
self.Function2(self)
return
def Function2(self):
btn2=Button(bg='black')
btn2.grid(row=1,column=1)
return
In a GUI interface, calling time.sleep makes the whole process wait, so the application appears to freeze. With Tk in Python, a way to do is to use the Tk after method on a window or frame, and then call a function that makes the necessary change to your Button. There are examples of how to do this at How to create a timer using tkinter
Use time.sleep to pause program execution for a certain amount of time. If you wanted to pause for 1 second after calling CreateInterface, change it to this:
def CreateInterface(self):
btn1=Button()
btn1.grid(row=1,column=1)
time.sleep(10)
self.Function2(self)
time.sleep(1)
Don't forget to import time when you do this.
The code below is a stripped down version (for clarity reasons) of a small application I am working on; an application for spelling words for children.
The problem
The problem I am having is in the function flash_correct(); its purpose is to show a word for 5 seconds, then hide again.
I must have a silly blind spot, but no matter where I put the time.sleep(5), the function starts with the break of 5 seconds, while the entry: self.entry2 never shows up:
Without the time.sleep(5) however, it shows up correctly:
Where is my blind spot?
The (stripped down) code:
#!/usr/bin/env python3
from gi.repository import Gtk, Pango, Gdk
import subprocess
import time
class InterFace(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Woorden raden")
maingrid = Gtk.Grid()
self.add(maingrid)
maingrid.set_border_width(10)
self.entry2 = Gtk.Entry()
self.entry2.set_size_request(500,60)
self.entry2.set_child_visible(False)
self.entry2.modify_font(Pango.FontDescription('Ubuntu 30'))
maingrid.attach(self.entry2, 0, 4, 4, 1)
quitbutton = Gtk.Button("Stop", use_underline=True)
quitbutton.modify_font(Pango.FontDescription('Ubuntu 20'))
quitbutton.connect("clicked", self.on_close_clicked)
maingrid.attach(quitbutton, 3, 7, 1, 1)
showword_button = Gtk.Button("↺", use_underline=True)
showword_button.modify_font(Pango.FontDescription('Ubuntu 25'))
showword_button.connect("clicked", self.flash_correct)
showword_button.set_size_request(60,20)
maingrid.attach(showword_button, 0, 6, 1, 1)
def flash_correct(self, button):
# the time.sleep(5) seems to take place at the beginning
# no matter in which order I set the commands
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
time.sleep(5)
self.entry2.set_child_visible(False)
def on_close_clicked(self, button):
Gtk.main_quit()
window = InterFace()
window.connect("delete-event", Gtk.main_quit)
window.set_default_size(330, 330)
window.set_resizable(False)
window.show_all()
Gtk.main()
You can use time.time to hide for roughly 5 seconds calling Gtk.main_iteration() in the loop to avoid your app becoming unresponsive.
def hide(self, time_lapse):
start = time.time()
end = start + time_lapse
while end > time.time():
Gtk.main_iteration()
def flash_correct(self, button):
# the time.sleep(5) seems to take place at the beginning
# no matter in which order I set the commands
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
self.hide(5)
self.entry2.set_child_visible(False)
There is a good explanation in the pygtk faq 7. How can I force updates to the application windows during a long callback or other internal operation?
If you have a long-running callback or internal operation that tries to modify the application windows incrementally during its execution, you will notice that this doesn't happen; the windows of your app freeze for the duration.
This is by design: all gtk events (including window refreshing and updates) are handled in the mainloop, and while your application or callback code is running the mainloop can't handle window update events. Therefore nothing will happen in the application windows.
The trick here is to realize where your operation can take a while to return, or where it is dynamically changing the window contents, and add a code fragment like this wherever you want an update forced out:
while gtk.events_pending():
gtk.main_iteration(False)
This tells gtk to process any window events that have been left pending. If your handler has a long loop, for instance, inserting this snippet as part of the loop will avoid it hanging the window till the callback has finished.
More eloquently, in the words of the great Malcolm Tredinnick, 'this requires using what should be called "Secret Technique #1 For Making Your Application Look Responsive"(tm):
Adding while gtk.events_pending(): may be no harm also.
It would be better to use a timer that integrates with the main loop, rather than busy-waiting until the time has elapsed. Luckily there is just such a facility in GLib:
def flash_correct(self, button):
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
GLib.timeout_add_seconds(5, self.flash_end)
def flash_end(self):
self.entry2.set_child_visible(False)
return GLib.SOURCE_REMOVE
I am building a GUI in Python and using threads to generate multiple countdown timers that work independently. I have a minimized module for testing which i am including bellow. I want the button to start the count down and then when its clicked again stop it, then reset so that it can be started on the next click. Also reset on its own once time runs out. The only problem is that after it stops, I cannot get it to restart. I get the "cannot start a thread twice" error. I have been trying to use a conditional loop to get the thread to exit its self but it hasn't been working. I would really appreciate some insight
There are actually two things that i want this program to be able to do.
1) run all the way through the timer then automatically reset so that it can be restarted
2) be stopped in the middle of a countdown, have it automatically reset so that it can be restarted
I think that solving these issues will be valuable for the community to see because it is an example of a real world solution to the issue that a lot of people talk about on the forum which is how to get around the no restarting threads.
__author__ = 'iKRUSTY'
'''
there are two things i would like this code to be able to do
1) run all the way through the timer and then reset so that it can be restarted
2) be stopped before completing and then reset so that it can be restarted
'''
from tkinter import *
import time
import os
import threading
#Variables
global FRYER_ONE_TIME_VAR # holds the value for changing label text to update timer
global BASKET_ONE_TARGET_TIME #this is a user input that will determine the length of the countdown
global timerOneStayAlive #this is the value that i am attempting to use so that the thread closes after it is set to false
timerOneStayAlive = FALSE #initializes to false because the the invert function changes it to true once the button is clicked
FRYER_ONE_TIME_VAR=" " #used to pass time between functiuons
#Font Profiles
SMALLEST_FONT = ("Verdana", 9)
SMALL_FONT = ("Verdana", 10)
LARGE_FONT = ("Verdana", 12)
LARGEST_FONT = ("Verdana", 18)
class timer():
global BASKET_ONE_TARGET_TIME
BASKET_ONE_TARGET_TIME = 5 #Just setting it manually for now
def __init__(self):
self.s = 0 #these values are used to convert from seconds to a minute:second format
self.m = 0 #these values are used to convert from seconds to a minute:second format
def SetTime(self, seconds):
self.seconds=seconds #this is a counter that will be used to calculate remaining time
def TimerReset(self):
self.seconds = BASKET_ONE_TARGET_TIME #resets counter to target time
def StartCountdown(self, FryerLabel): #takes a label as an argumet to tell it where to display the countdown
global timerOneStayAlive
print("StartCountdown Started!") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME #set start value for seconds counter
self.seconds=self.seconds+1 #makes the full time appear upon countdown start
while self.seconds > 0:
FRYER_ONE_TIME_VAR = self.CalculateTime() #Calculate time reduces the counter by one and reformats it to a minute:second format. returns a string
FryerLabel.config(text=FRYER_ONE_TIME_VAR) #Update Label with current value
print(self.seconds) #USED FOR TROUBLE SHOOTING
time.sleep(1)
# reset label with default time
if self.seconds == 0: #Reset once counter hits zero
print("resetting time") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME + 1
FRYER_ONE_TIME_VAR = self.CalculateTime()
FryerLabel.config(text=FRYER_ONE_TIME_VAR)
break
print("TimerStayAlive before invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
timerOneStayAlive = invert(timerOneStayAlive) #inverts the value back to FALSE so that Ideally the loop breaks
# and the thread completes so that it can be called again
print("TimerStayAlive after invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
print("end startcountdown") #USED FOR TROUBLE SHOOTING
def CalculateTime(self):
#print("CalculateTime has been called")
lessThanTen=0
self.seconds = self.seconds - 1
self.m, self.s = divmod(self.seconds, 60)
if self.s<10:
lessThanTen=1
#create time String Variables
colonVar=':'
minutesString = str(self.m)
secondsString = str(self.s)
#insert variables into string array
timeArray = []
timeArray.append(minutesString)
timeArray.append(colonVar)
if lessThanTen == 1:
timeArray.append("0")
timeArray.append(secondsString)
#prepare for output
self.timeString = ''.join(timeArray)
return self.timeString
def invert(boolean):
return not boolean
def BasketOneButtonClicked():
print("button clicked") #USED FOR TROUBLE SHOOTING
global timerOneStayAlive
timerOneStayAlive = invert(timerOneStayAlive) #Changes from FALSE to TRUE
if timerOneStayAlive == TRUE:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
basketOneThread.start()
updateButtonStatus() #changes text of button depending on whether timerOneStayAlive is TRUE or FALSE
else:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
return
def updateButtonStatus():
global timerOneStayAlive
if timerOneStayAlive == FALSE:
basketOneStartButton.config(text="Start")
if timerOneStayAlive == TRUE:
basketOneStartButton.config(text="Stop")
def BasketOneThreadComShell(): # I used this so that i can ideally call multiple functions with a single thread
'''
This is where i think the problem may be. this is what is called when the thread is initialized and theoretically
when this completes the thread should come to a halt so that when the button is reset, the thread can be called again
I think that the function is completing but for some reason the thread keeps on running.
'''
global timerOneStayAlive
print("ComShell Started") #USED FOR TROUBLE SHOOTING
while timerOneStayAlive:
basketOneTimer.StartCountdown(countdownLabelBasket1)
updateButtonStatus()
if timerOneStayAlive == FALSE: #redundant because while loop should do it. i just tried it because i couldnt get the process to end
break
print("Threadshell has ended") #USED FOR TROUBLE SHOOTING
return
print("after return check") #USED FOR TROUBLE SHOOTING
root = Tk()
'''
the following is all just building the GUI Stuff
'''
Container = Frame(root)
Container.grid(row=1, column=0, padx=10, pady=10)
countdownContainerBasket1 = Label(Container, width=10, height=5)
countdownContainerBasket1.grid(row=2, column=0, sticky=NSEW)
countdownLabelBasket1 = Label(countdownContainerBasket1, text="Basket 1", background="white", anchor=CENTER, width=10, height=6, font=LARGE_FONT, padx=20)
countdownLabelBasket1.pack()
basketOneTimer = timer()
basketOneTimer.SetTime(5)
basketOneStartButton = Button(Container, text="Start", font=LARGE_FONT, command=BasketOneButtonClicked)
basketOneStartButton.grid(row=3, column=0, padx=10, pady=10)
basketOneThread = threading.Thread(target=BasketOneThreadComShell) #this is where the thread is initialized. start() is called in BasketOneButtonClick
print(threading.active_count) #tried to use this to see if the thread was exiting but it didnt help
root.mainloop()
You'll probly want to give this a run just to see what the GUI looks like and what i mean when i say timer. Try letting it run all the way through and resetting to get an idea of what im talking about.
Let me know if there is any other info that would be helpful, and keep in mind this is my first week with python so im no expert. Thanks all
The python community is very lucky to have a style guide that is very well accepted https://www.python.org/dev/peps/pep-0008/. It is much harder for me to quickly understand the problems you are talking about due to your non pep8 naming and formatting.
Other threads cannot interact with your widgets, only the tkinter mainloop can do that. You can use the after method provided by tkinter to run a function after a given time period in the mainloop.
see this example
Mutli-threading python with Tkinter for an example!
To be clear threading with tkinter may work, but it is unreliable and you will get unexpected behavior that is hard to debug. Don't do it.
Suggestions
the tkinter after method would be called to start a function that indefinitely checks a queue for functions to run. It then schedules these functions to be run by tkinter using the after method
I would have a timer class with start, stop and pause methods.
When started it would count up/down and simply restart itself on completion. Each timer instance you create would also need a reference to a tkinter button.
to update the button you put a function on the queue that may look like
tkinter_queue.put(lambda: but.config(text=i))
now the function that checks the queue will update your button timers
Also as you are in your first week of python programming you can be sure that most problems you encounter will have answers here if not elsewhere. Please do your research before hand. Gui programming and threading are not the easiest topics so even more reason to do your research.