How to run a ros publisher without a while loop? in python? - python

I am writing tkinter python program for turning on and off the publisher. I need to turn on and off the publisher using a button. If am writing using while loop, the entire gui freezes, I am unable to operate anything. Please give me some suggestions.
dummy = Button(master, text="dummy", relief='groove',command=self.pub_values)
def pub_values(self):
self.pub = rospy.Publisher('/gateway/RigPLC2Seq', Int32MultiArray, queue_size=10)
rospy.init_node('talker_int', anonymous=True)
rate = rospy.Rate(1) # 10hz
self.var = Int32MultiArray()
self.var.data = []
ls = self.extract_values()
self.var.data = ls
self.on = True
while self.on == True:
rospy.loginfo(self.var)
self.pub.publish(self.var)
rate.sleep()

Your solution is not far fetched.
you set self.on to True, then you have a while loop that checks to the value of self.on which if True should freeze(.sleep()) your GUI.
Your code is obeying just what you typed in.
Solution:
You must have a condition in your while loop that changes the value of self.on to False, if you don't the while loop will never stop being executed. Example:
counter=1
self.on = True
while self.on == True:
rospy.loginfo(self.var)
self.pub.publish(self.var)
rate.sleep()
if counter==10:
self.on=False
counter++
With the above code, I want the loop to run 10 times, after that self.on becomes False and the while loop exits, which also unfreezes your GUI(.sleep() no longer runs)
More importantly is that suggested by #j_4321 above, .sleep() freezes the GUI as long as it is running, you will be better served using the .after() method.
This should help you learn about the .after() method if you so desire to use it.

You can use after to replace the while loop. While self.on is true, publish is executed and rescheduled. The button command toggles self.on and relaunches publish when self.on is set back to true:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.on = False
self.button = tk.Button(self, text='Start', command=self.start_stop)
self.button.pack()
def start_stop(self):
if self.on:
self.button.configure(text='Start')
self.on = False # stop publish
else:
self.button.configure(text='Stop')
self.on = True
self.publish() # relaunch publish
def publish(self):
if self.on:
print('publish')
self.after(1000, self.publish)
App().mainloop()

Related

Tkinter- How to stop a loop that is in another file with a stop button?

I want to stop a loop when I press a button. If I do it all in the same files I don´t have a problem but when I put the loop as a routine from another file It doesn´t work and I don´t know why. I hope you can help me! Thanks!!
First of all, this is subroutine.py, the file where is the loop allocated
subroutine.py
def routine (pressed):
while True:
print ("hello")
if pressed== 1:
break #Break while loop when stop = 1
And this is the "main" file
import tkinter as tk
import threading
import subroutine
pressed = 0
def Start_Scan():
global pressed
pressed = 0
# Create and launch a thread
t = threading.Thread (target = subroutine.routine (pressed))
t.start()
def Stop():
global pressed
pressed = 1
#main
master = tk.Tk()
app = tk.Frame(master)
app.grid()
start = tk.Button(app, text="Start Scan",command=Start_Scan)
stop = tk.Button(app, text="Stop",command=Stop)
start.grid()
stop.grid()
app.mainloop()
I use thread and a global variable, I thought about not using a global variable but I didn't find a way for the two threads to communicate that was not blocking. Thanks!!
You can use threading.Events to pass a flag between the main thread and the worker thread
# main.py
stop_event = threading.Event()
def Start_Scan():
# Create and launch a thread
t = threading.Thread (
target=subroutine.routine,
args=(stop_event,) # pass the Event object to the worker target
)
t.start()
def Stop():
stop_event.set()
Just update your subroutine to keep an eye on that event
# subroutine.py
def routine(stop_event):
while not stop_event.is_set(): # loop until the Event is 'set'
print ("hello")

Issue synchronizing multiple countdown timers with Python Tkinter

I'll start by saying that I'm completely new to tkinter and I've been trying to wrap my head around a minor issue I'm experiencing - it might be very simple/I'm approaching it incorrectly, but some insight would be very appreciated!
Essentially I have an app with multiple items, each item has a button. When you click a button for an item, it starts a countdown timer by calculating the duration in a function, then using .after(1000, ...) to refresh my label every second.
The issue is, when you click on a button and start a timer, it counts down every second from when the button is clicked. So if you click the next button "off cycle" from the last countdown, the countdown is NOT SYNCED up the way it should be.
Currently it counts down a second from when the button is pressed, instead of updating every "true second", if that makes sense. Any idea how I can approach this?
def handle_growth_time(n, harvest_time):
time_now = datetime.datetime.now()
time_now = time_now.replace(microsecond=0)
crop = allotments[n]
if not crop["reset"]:
if harvest_time >= time_now:
remaining_time = harvest_time - time_now
crop["remaining_label"].configure(text=remaining_time, font='ariel 12', foreground="black")
crop["remaining_label"].after(1000, handle_growth_time, n, harvest_time)
else:
crop["label_name"].configure(background="#86d474")
crop["active"] = False
else:
crop["reset"] = False
crop["active"] = False
I have tried a couple different approaches, such as when I capture time_now I grab time_now.microseconds/1000 and waiting for that before starting the countdown but I think working with time in that way is unreliable because I still can't really capture true :00.00
I also tried resetting all the timers when a new one is started, refreshing their 1 sec cycle, but I did so by going through an array and restarting them - as you can imagine this causes them to be offset still.
I recommend having only a single timekeeper that keeps track of passing seconds. Each timer can have a tick function that is called by this single timer. It is then responsible for updating all timers at the same time so that the update at the same time.
The following example illustrates the point. It's not production quality code, but it illustrates the point: multiple timer objects that all "tick" during a single time loop.
import tkinter as tk
class Timer(tk.Label):
def __init__(self, parent, seconds):
super().__init__(parent, text=str(seconds), width=8)
self.time = seconds
self.running = False
def tick(self):
self.time -= 1
self.configure(text=self.time)
if self.time == 0:
self.running = False
def start(self):
self.running = True
def stop(self):
self.running = False
class Timekeeper:
def __init__(self):
self.timers = []
def new_timer(self, seconds):
t = Timer(root, seconds)
t.pack(side="top", fill="x")
self.timers.append(t)
t.start()
def tick(self):
root.after(1000, self.tick)
# update existing timers
for timer in self.timers:
if timer.running:
timer.tick()
timekeeper = Timekeeper()
root = tk.Tk()
root.geometry("200x600")
button_15 = tk.Button(root, text="New 15 second timer", command=lambda: timekeeper.new_timer(15))
button_30 = tk.Button(root, text="New 30 second timer", command=lambda: timekeeper.new_timer(30))
button_15.pack(side="top")
button_30.pack(side="top")
# add a few timers as an example
timekeeper.new_timer(15)
timekeeper.new_timer(30)
timekeeper.new_timer(45)
timekeeper.new_timer(60)
root.after(1000, timekeeper.tick)
root.mainloop()

Program Crashing When Using Stop Button

I'm pretty new to python, but I have a working version of this in MATLAB but struggling to get the start/stop buttons to work in python.
I have tried to simplify the code I have posted below as much as I think I can, but basically I want to run a series of bleeps for a fitness test. There is a set order the beeps need to occur in order to signify different intensities of exercise.
I have used functions to write the different types of exercise (I haven't included all of that in the code below to save space), and then another to combine them into the desired protocol. Then I have created buttons to start and stop the protocol running.
The only thing I want to include that is not in the example I have put in what I have already tried is it must include a timer. The exercise sections are variable length but need to fit into a time period rather than a certain number of iterations.
I have already tried several answers on the site, the nearest one to my problem I think is;
Previous Answer
The first solution here posted by Mike - SMT works on my PC, it also looks a lot like what I am trying to achieve but I have tried to match it as closely as I can but the start button still remains depressed and the stop button crashes the work.
# Import all modules
import time
import winsound
import tkinter
# Define Global Variables
runTime = 20
numberBlocks = 5
atime = 1
tracker = False
# Define Movement Functions
def walk()
def jog()
def cruise()
def sprint()
def ar()
def rest()
# callback functions
def start():
global numberBlocks, runTime, atime, tracker
tracker = False
t = time.time()
i = 0
while i < numberBlocks - 1 and tracker == False:
while (time.time() - t) < runTime and tracker == False:
if tracker == False and (time.time() - t) < runTime:
walk()
if tracker == False and (time.time() - t) < runTime:
sprint()
if tracker == False and (time.time() - t) < runTime:
ar()
if tracker == False and (time.time() - t) < runTime:
jog()
if tracker == False and (time.time() - t) < runTime:
cruise()
rest()
i += 1
def stop():
global tracker
tracker = True
# run GUI
root = tkinter.Tk()
tracker = False
root.title('LIST')
# create all of the main containers
bottom_center_frame = tkinter.Frame(root)
bottom_center_frame.pack(side="top", fill="x")
# create widgets for the button frame
button_stop = tkinter.Button(bottom_center_frame, text='Stop', command=stop).pack(side="bottom", fill="x")
button_start = tkinter.Button(bottom_center_frame, text='Start', command=start).pack(side="bottom", fill="x")
# loop gui
root.mainloop()
The bleeps all work ok it just crashes when I try and stop it.
So, the main problem here is that once you click start and launch a command in the mainloop, tkinter will wait until that command is over before allowing you to do something else (in this case, press stop).
A possible solution is using .after() like this:
import tkinter as tk
class FitnessApp(tk.Tk):
def __init__(self, tracker):
tk.Tk.__init__(self)
self.runTime = 20
self.numberBlocks = 5
self.atime = 1
self.tracker = tracker
# create all of the main containers
self.bottom_center_frame = tk.Frame(self)
self.bottom_center_frame.pack(side="top", fill="x")
# create widgets for the button frame
self.button_stop = tk.Button(self.bottom_center_frame, text='Stop', command=self.stop)
self.button_stop.pack(side="bottom", fill="x")
self.button_start = tk.Button(self.bottom_center_frame, text='Start', command=self.start)
self.button_start.pack(side="bottom", fill="x")
def walk(self):
print("walking")
def start(self):
self.tracker = False
self.move()
def stop(self):
print("Stopping run")
self.tracker = True
def move(self):
if self.tracker is False:
self.walk()
self.after(1000, self.move)
# run GUI
tracker = False
root = FitnessApp(tracker)
root.title('LIST')
# loop gui
root.mainloop()
When start is clicked, the tracker is set to False, and then the move function is called, which prints "walking".
Then the self.after(1000, self.move) recursively calls the move function every 1000 ms, which allows you to press the stop button, set the tracker to True and stop the simulation.
For more complicated situations, I recommend that you use the threading module, which allows you to launch your simulation in a secondary thread while keeping control over the main loop. This way you can use a variable to track the progress of your simulation and act accordingly.

How to interrupt a thread/process by making a correct "Stop" button in Tkinter (Python)?

I want some basics on the problem of making some sort of "Stop" button that in my case terminates the series of beeps:
from tkinter import *
import winsound
from random import randint
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.widgets()
def widgets(self):
self.beep = Button(self, text = "Beep", command = play_beep)
self.beep.pack()
self.stop = Button(self, text = "Stop", command = stop_beep)
self.stop.pack()
go_on = True
def play_beep():
count = 10
while go_on == True and count != 0:
winsound.Beep(randint(100, 2500), 200)
count -= 1
def stop_beep():
go_on = False
root = Tk()
app = App(root)
root.mainloop()
When I press the "Beep" button it gets stuck as well as all the GUI until the beeps end. Could anyone tell me how to fix it?
I don't use TKinter, but I believe your button press is not creating a separate thread or process. The reason why your button gets stuck is because your play_beep loop is blocking your GUI execution loop. So we use threading. The thread executes at the same time as your GUI, so you can basically do two things at once (listen for GUI events and play beep noises).
import threading
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.is_playing = False
self.pack()
self.widgets()
def widgets(self):
self.beep = Button(self, text = "Beep", command = self.play_beep)
self.beep.pack()
self.stop = Button(self, text = "Stop", command = self.stop_beep)
self.stop.pack()
def play_beep(self):
self.is_running = True
self.beep_th = threading.Thread(target=self.run)
self.beep_th.start()
def run(self):
count = 10
while self.is_running == True and count != 0:
winsound.Beep(randint(100, 2500), 200)
count -= 1
def stop_beep(self):
try:
self.is_running = False
self.beep_th.join(0)
self.beep_th = None
except (AttributeError, RuntimeError): # beep thread could be None
pass
def closeEvent(self, event): # This is a pyside method look for a TKinter equivalent.
"""When you close the App clean up the thread and close the thread properly."""
self.stop_beep()
super().closeEvent(event)
First off, your question has nothing to do about threads or processes. Tkinter is single-threaded.
If you want to run some function periodically in a tkinter program, you must give the event loop a chance to process events. The typical solution is to do it like this:
def play_beep(count=10):
if go_on and count != 0:
winsound.Beep(randint(100, 2500), 200)
root.after(1000, play_beep, count=1)
This will cause the beep to play every second (1000ms) for ten iterations. In between each call, the event loop will have a chance to process other events.
Now, if the code you are running takes a long time, you're going to have to run that code in a separate thread or process. I know nothing about winsound.Beep so I don't know if that's necessary or not.
Second, to be able to interrupt it, you need to make go_on global, otherwise you're simply setting a local variable that never gets used.
def stop_beek():
global go_on
go_on = False

Run an infinite loop in the backgroung in Tkinter

I would like the code to run in the background and to update my GUI periodically. How can I accomplish this?
For example, suppose I want to execute something like this in the background of the GUI code you can see below:
x = 0
while True:
print(x)
x = x + 1
time.sleep(1)
This is the GUI code:
class GUIFramework(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
self.master.title("Volume Monitor")
self.grid(padx=10, pady=10,sticky=N+S+E+W)
self.CreateWidgets()
def CreateWidgets(self):
textOne = Entry(self, width=2)
textOne.grid(row=1, column=0)
listbox = Listbox(self,relief=SUNKEN)
listbox.grid(row=5,rowspan=2,column=0,columnspan=4,sticky=N+W+S+E,pady=5)
listbox.insert(END,"This is an alert message.")
if __name__ == "__main__":
guiFrame = GUIFramework()
guiFrame.mainloop()
It is a little unclear what your code at the top is supposed to do, however, if you just want to call a function every second (or every the amount of seconds you want), you can use the after method.
So, if you just want to do something with textOne, you'd probably do something like:
...
textOne = Entry(self, width=2)
textOne.x = 0
def increment_textOne():
textOne.x += 1
# register "increment_textOne" to be called every 1 sec
self.after(1000, increment_textOne)
You could make this function a method of your class (in this case I called it callback), and your code would look like this:
class Foo(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.x = 0
self.id = self.after(1000, self.callback)
def callback(self):
self.x += 1
print(self.x)
#You can cancel the call by doing "self.after_cancel(self.id)"
self.id = self.after(1000, self.callback)
gui = Foo()
gui.mainloop()
If you truly want to run a distinct infinite loop you have no choice but to use a separate thread, and communicate via a thread safe queue. However, except under fairly unusual circumstances you should never need to run an infinite loop. Afte all, you already have an infinite loop running: the event loop. So, when you say you want an infinite loop you are really asking how to do an infinite loop inside an infinite loop.
#mgilson has given a good example on how to do that using after, which you should consider trying before trying to use threads. Threading makes what you want possible, but it also makes your code considerably more complex.

Categories