Python - How do I continuously repeat a sequence without a While loop and still be able to stop the sequence at any time - python

I have a Raspberry Pi with the Piface adaptor board. I have made a GUI which controls the LED's on the Piface board.
I wrote a small piece of code to make the LED's run up and down continuously, like Knight Riders car, using a While loop.
I then wrote another piece of code that created a GUI. In the GUI is a button that starts the LED's running up and down continuously with the While loop piece of code.
What I want to do is to have that GUI button start the LED running sequence, and then the same button stop the sequence at any time.
I do understand that the code is sitting/stuck in the While loop. And hence any buttons in the GUI are not going to have an effect.
So is there a better way of doing it? Any pointers would be appreciated.
Thanks in advance.

Another option is to run the LED while loop in a separate thread. Like in
the next code. The while loop is stopped by toggling the shared led_switch
variable.
"""
blinking LED
"""
import tkinter as tk
import threading
import time
led_switch=False
def start_stop():
global led_switch
led_switch=not led_switch
if led_switch:
t=threading.Thread(target=LED)
t.start()
def LED():
while led_switch:
print('LED on')
time.sleep(1)
print('LED off')
time.sleep(1)
root=tk.Tk()
button=tk.Button(root,command=lambda: start_stop(),text='start/stop')
button.pack()
tk.mainloop()

If you have a while loop and a GUI you can use generators to still use the loop and let the GUI run properly.
I sketch the Idea here and create an example for the Tkinter GUI.
You want to write your code as a loop and still use it in a GUI:
from Tkinter import *
from guiLoop import guiLoop # https://gist.github.com/niccokunzmann/8673951
#guiLoop
def led_blink(argument):
while 1:
print("LED on " + argument)
yield 0.5 # time to wait
print("LED off " + argument)
yield 0.5
t = Tk()
led_blink(t, 'shiny!') # run led_blink in this GUI
t.mainloop()
Output while the GUI is responsive:
LED on shiny!
LED off shiny!
LED on shiny!
LED off shiny!
...
Sadly Tkinter is the only GUI I know and it is a bad example because you can always update the GUI in your loop with the update() method of GUI elements:
root = Tk()
while 1:
print("LED on")
t = time.time() + 0.5
while t > time.time(): root.update()
print("LED off")
t = time.time() + 0.5
while t > time.time(): root.update()
But with such a guiLoop you can have multiple loops:
t = Tk()
led_blink(t, 'red')
led_blink(t, 'blue')
led_blink(t, 'green')
t.mainloop()
Here are some examples for starting and stopping the loop with a button.

If you're using Tkinter, there's a very easy pattern for running a loop. Given that the UI (in just about every UI toolkit) is already running an infinite loop to process events, you can leverage this to run code periodically.
Let's assume you have a python object "led" which has a method for toggling it on and off. You can have it switch from on to off every 100ms with something as simple as these three lines of code:
def blink(led):
led.toggle()
root.after(100, blink, led)
The above code will run forever, causing the led to blink every 100ms. If you want to be able to start and stop the blinking with a button, introduce a flag:
def blink(led):
if should_blink:
led.toggle()
root.after(100, blink, led)
When you set the toggle to True, the led will start blinking. When it's False, it will stop blinking.
The main thing to take away from this is that you already have an infinite loop running, so there's no need to create one of your own, and no need to use something as complex as threading. Simply create a function that does one frame of animation, or calls some function or does some unit of work, then have the function request that it be run again in the future. How far in the future defines how fast your animation or blink will run.

Related

Python how to make repeat timer GUI

I am learning Python and still in level beginner.
I try to make timer program which the timer will be repeated continuously after certain of time.
I write as follow:
from tkinter import *
import threading
import time
mgui = Tk()
def refresh():
threading.Timer(20, refresh).start()
detik = 19
while detik >= 0:
menit,detik = divmod (detik,60)
timer ='{:02d}:{:02d}'.format(menit, detik)
time.sleep(1)
detik -= 1
refresh_waktu=Entry(mgui, width= 6, font=("Helvetica bold", 40), fg='red', bd=2)
refresh_waktu.place(x=155, y=152)
refresh_waktu.insert(END, str(f"{timer:>6}"))
refresh()
mgui.geometry('450x450')
mgui.title('Test')
mgui.mainloop()
When I run the program, the interface GUI seem delay about 20 seconds although after that the program timer running as my expected.
I try to make the Interface also appear when start the program, but I still fail to do so.
Please if anyone can help me to give some advice or suggestion.
I sincerely thank for your help.
Best regards
Tk, like all GUI systems, is event driven. When you create or modify a window, nothing visible happens. All that does is send messages. It is the job of the main loop to extract and dispatch those messages, and that's where the drawing is done. In your case, you are blocking for 20 seconds before you enter the main loop, so nothing will be drawn until then. You need to use something like mgui.after to request a callback after a second, and use that for your timing.

How does a thread close when using Tkinter

I know this question has been asked numerous times before, but they all use people's code and classes/various functions, etc, so its been a bit difficult to try and follow/understand what's going on. I wanted a more simpler setup to understand what's happening here and how it all works.
When you thread a function, once the function is complete, the thread is closed:
import threading
def fun():
x=0
while x<1000:
x+=1
threading.Thread(target=fun).start()
So I decided to take this idea one step further with Tkinter.
import tkinter as tk
from tkinter import *
from tkinter import messagebox as mb
import threading
def fun():
x=0
while x<10000900:
x+=1
if x == 50:
print(x)
def main():
while True:
fun()
if mb.askquestion('Replay', 'Would you like to play another round?') != 'yes':
root.destroy()
break
root = tk.Tk()
root.geometry('600x600')
threading.Thread(target=main).start()
root.mainloop()
The idea being, that when the main function was broken (based on the user response), the thread would also close automatically. Additionally, so would the Tkinter window. However, when the above script is run, the Tkinter window does close, but the terminal still indicates something, which I assume is the thread. What I don't understand is why in the first case where I use threading, the program ends properly, whereas the 2nd one doesn't.
When you execute root.destroy() you kill the main thread (mainloop) as well as the extra thread running your main funtion. That way the break statement never gets executed - the thread that would execute that statement is abruptly ended.
If you replace root.destroy() with root.after(10, root.destroy) the program acts as expected. This is because you're delaying the call to root.destroy() by some time (10 ms). This delay allows the break statement to be executed since the thread is still alive for 10 ms.

Tkinter .after runs forever and .mainloop never runs

I'm creating a python program on my RPi3 that changes the frequency of a GPIO pin depending on a Tkinter scale.
Here is my code:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
from Tkinter import *
import time
freq = 1.0
master = Tk()
def update():
period = 1.0/float(freq)
GPIO.output(8, GPIO.HIGH)
time.sleep(period/2.0)
GPIO.output(8, GPIO.LOW)
time.sleep(period/2.0)
master.after(0, update)
scale = Scale(master, from_=1, to=20000, orient=HORIZONTAL, variable=freq)
scale.pack()
GPIO.setup(8, GPIO.OUT)
master.after(0, update)
master.mainloop()
GPIO.cleanup()
For some reason, master.after(0, update) runs forever and master.mainloop() never runs. I can tell because the scale never shows up and pin 8 turns on for half a second, then turns off for half a second, and the cycle repeats. If I press Ctrl+C then master.after(0, update) stops running and master.mainloop() starts running, the scale appears, but nothing happens when I drag the slider left and right.
I ran the program by typing sudo python tone.py in the terminal then pressing enter.
Fix/Alternative?
Processing events
You are making two somewhat common mistakes: you shouldn't do after(0, ...), and you shouldn't call sleep.
after(0, ...) means that every time you process the event, you immediately add another event. The event loop never has a chance to process other events in the queue, including events to handle the slider, screen updates, etc.
When you call sleep, the GUI does exactly that: it sleeps. While it is sleeping it can't process any events.
The solution is to use only after with a reasonable time span, and not call sleep at all.
For example:
def update():
...
# set the pin high
GPIO.output(8, GPIO.HIGH)
# set the pin low in half the period
master.after(period/2, GPIO.output, 8, GPIO.LOW)
# do this again based on the period
master.after(period, update)
Another way, if you continually want to toggle the pin every half second, would be this:
def update(value=GPIO.HIGH):
GPIO.output(8, value)
next_value = GPIO.LOW if value == GPIO.HIGH else GPIO.HIGH
master.after(500, update, next_value)
Using the slider
When you use the variable attribute of the slider, the variable must be an instance of one of the special tkinter variables, such as IntVar. You will then need to call get or set to get or set the value.
For example:
freq = IntVar()
freq.set(1)
def update(value=GPIO.HIGH):
period = 1.0/float(freq.get())
...
mainloop() is running, but Tkinter won't update views unless it is idle. KeyboardInterrupt will tell it to cleanup, at which point it finishes its event queue (which includes updating interface) and exits.
Solution: Give mainloop time to be idle- you really just need to change your after(0, update) to have some milliseconds inside OR tell master to .update_idletasks() to update the GUI.
A slightly better solution would be to make your high/low parts into their own functions which call each after the needed delay- sleeping in the mainloop gui is a bad idea, as your GUI cannot update whatsoever if the program is sleeping. (nor can it take input et al.) You would have millisecond frames to change input before it updated again, while using two functions that call each other after the chosen milliseconds would let you adjust the timings et al while it's waiting to flip to the other on/off.

Can I run a while loop in the background at the same time as other things? [duplicate]

I am trying to run a While Loop in order to constantly do something. At the moment, all it does is crash my program.
Here is my code:
import tkinter
def a():
root = tkinter.Tk()
canvas = tkinter.Canvas(root, width=800, height=600)
while True:
print("test")
a()
It will loop the print statement, however the actual canvas refuses to open.
Are there any viable infinite loops that can work alongside Tkinter?
Extra Information
When I remove the While True statement, the canvas reappears again.
Tkinter hangs unless it can execute its own infinite loop, root.mainloop. Normally, you can't run your own infinite loop parallel to Tkinter's. There are some alternative strategies, however:
Use after
after is a Tkinter method which causes the target function to be run after a certain amount of time. You can cause a function to be called repeatedly by making itself invoke after on itself.
import tkinter
#this gets called every 10 ms
def periodically_called():
print("test")
root.after(10, periodically_called)
root = tkinter.Tk()
root.after(10, periodically_called)
root.mainloop()
There is also root.after_idle, which executes the target function as soon as the system has no more events to process. This may be preferable if you need to loop faster than once per millisecond.
Use threading
The threading module allows you to run two pieces of Python code in parallel. With this method, you can make any two infinite loops run at the same time.
import tkinter
import threading
def test_loop():
while True:
print("test")
thread = threading.Thread(target=test_loop)
#make test_loop terminate when the user exits the window
thread.daemon = True
thread.start()
root = tkinter.Tk()
root.mainloop()
But take caution: invoking Tkinter methods from any thread other than the main one may cause a crash or lead to unusual behavior.

Python tkinter time.sleep()

How come when I run my code, it will sleep for 3 seconds first, then execute the 'label' .lift() and change the text? This is just one function of many in the program. I want the label to read "Starting in 3...2...1..." and the numbers changing when a second has passed.
def predraw(self):
self.lost=False
self.lossmessage.lower()
self.countdown.lift()
self.dx=20
self.dy=0
self.delay=200
self.x=300
self.y=300
self.foodx=self.list[random.randint(0,29)]
self.foody=self.list[random.randint(0,29)]
self.fillcol='blue'
self.canvas['bg']='white'
self.lossmessage['text']='You lost! :('
self.score['text']=0
self.countdown['text']='Starting in...3'
time.sleep(1)
self.countdown['text']='Starting in...2'
time.sleep(1)
self.countdown['text']='Starting in...1'
time.sleep(1)
self.countdown.lower()
self.drawsnake()
It does this because changes in widgets only become visible when the UI enters the event loop. You aren't allowing the screen to update after calling sleep each time, so it appears that it's sleeping three seconds before changing anything.
A simple fix is to call self.update() immediately before calling time.sleep(1), though the better solution is to not call sleep at all. You could do something like this, for example:
self.after(1000, lambda: self.countdown.configure(text="Starting in...3"))
self.after(2000, lambda: self.countdown.configure(text="Starting in...2"))
self.after(3000, lambda: self.countdown.configure(text="Starting in...1"))
self.after(4000, self.drawsnake)
By using after in this manner, your GUI remains responsive during the wait time and you don't have to sprinkle in calls to update.

Categories

Resources